NDC 참관기: 네트워크 비동기 통신, 합의점의 길목에서

넥슨 마비노기 2팀 김재석 씨의 강연이었다.

_네트워크 통신 일반론_이 아니라 이전에 참관기를 쓴 마영전 서버 구조를 생각하면서 내용 요약을 읽어주기 바란다. 네트워크 프레임웍 개발을 목표로 데브캣 스튜디오의 게임을 개발하면서 얻은 경험에대한 설명이었다. 강연 중에도 부연 설명한 부분이지만, 이 이하에서 주로 얘기하는 두 개의 주체는 다음 의미다

  • 서비스: 비동기 네트워크 메시지를 처리하는 주체
  • 클라이언트: 비동기 네트워크 메시지를 보내는 주체

이하는 강연 요약.

목표

  • 네트워크 코딩 스트레스를 주지 말자
  • 지나친 하위 레이어의 지식을 요구하지 말자

문제가 되는 부분은,

  • 네트워크 통신은 비지니스 로직이 아니라는 것
  • 정보가 필요한 시점에 ‘즉시 계산되는 것’으로 생각할 수 있으면 좋겠다
  • C++과 C#을 모두 지원해야하는 점 ((이건 데브캣 프로젝트 들의 언어 선택 문제인 듯 합니다))

방향

  • 네트워크 코드 생성
  • C/C++ 상호 운용
  • 논리 흐름에 따라 코드를 생성한다 (흐름을 끊지 말자)
  • 저수준의 네트워크 기술 지식은 숨기자

시스템 프로그래머와 응용 프로그램 프로그래머의 ‘설계 목표’가 상충된다.

그리고 이런 문제의 본질은 ‘적당히 돌아가면 그냥 쓰고 미완이 되는’ 데에 있다.

1차 시도 메시지 생성 / 프로토콜 반복기(enumerator)

메시지 생성

  • C#: 2.0 Lightweight code generation 사용 (4.0이라면 ExpressionTree)
  • C++: VS 매크로 혹은 하드 코딩으로 ((나중에는 주로 하드 코딩하게 되었다고 한다))

pros.

  • C#에서 데이터 포맷만 정의하면 끝남

cons.

  • C++ 코드 생산성이 떨어진다
  • C# 형식의 가변성 혹은 generic처리의 문제

프로토콜 반복기

// (Client)
var op = new Operation()
op.onComplete += () => { /* ... */ }
op.onFail += () => { /* ... */ }
RequestOperation(op)

// (Service)
IEnumerable<Object> Run() {
  yield return info.ID;
  yield return info.Name
}

pros.

  • 클라이언트 코드를 짤 때 연관 코드가 한 자리에 모인다

cons.

  • cons. enumerator 를 배우는데 드는 비용
  • C++ 에서 사용할 수 없다.1
  • 중간 처리가 필요한 비동기 작업이 적기 때문에, 굳이 enumerator를 쓸 이유가 없다

2차 시도 delegate 기반으로 코드 생성

마영전에 사용한 방법이라고 한다.

C# delegate를 IDL로 사용했다.  Client용 코드는 C++로, Service용 코드는 CIL을 생성하게 했다.

Hindley-Milner type inference를 사용한다. 불변성은 ref 가변성에 대해 공변성은 out, return 반공변성은 in 이용;

pros.

  • C# IDL만 가지고 클래스가 자동 생성된다

cons.

  • 프로토콜의 연관성을 기술할 수 없고,
  • 클라이언트/서버가 대칭성이 없고
  • 배경 지식이 많이 요구되며  유지 보수가 좀 불편하다 (C++/CIL)

3차 시도: interface 기반 코드 생성

차기 프로젝트에 사용하고 있는 방법이라 한다.

C# ‘interface’를 IDL로 사용해서 추상 클래스를 양쪽(C++/C#)에 생성한다.

  • Client/Service CodeDOM을 생성
  • C++/CIL을 대칭적으로 생성한다
  • Microsoft Asynchronous Programming Design Patterns의 권고안 대로 네이밍 및 기타 등등
  • Trivial한 serialize 형식은 C++에 복제해서 기타 작업을 간편하게

pros:

  • 대부분의 코드를 자동 생성하고
    • C++/Common Language Spec. 양쪽에서 일관성있게 작업

cons:

  • 상속을 써서 생기는 제약

촌평.

강연자체는 시도했던 방법들을 분석했다는 점만으로도 매우 훌륭하고, 전반적으로 말이 좀 빠른게 불만이긴했지만, 각 방법들을 간결하게 분석한 점이 괜찮았다.

다만 내가 기대한게 ‘일반적인 상황’에서의 비동기 통신 방법에 대해 얘기할거라고 생각한거여서 약간 망했음. 일단 여기서 언급한 건 (대부분의 경우) 마영전 서버에서 언급한 것과 같은 구조를 배경에 깔고 있다. 내가 만드는 서버 런타임에서 목표로하는 추상화 수준과 너무 달라서 Orz. 사실 이 세션 들을 때 기대한 것은 현재 참여하고 있는 한 프로젝트에서 클라이언트-서버 인터페이스 (네트워크 레이어와 그 래퍼 수준) 를 좀 달리 해볼까 하는 고민에서였는데,일단 서버 구조가 목표로하는 추상화 수준과, 이 추상화에서 내보이려는 부분이 마비노기 영웅전 서버와는 상당히 상이하기 때문에 GG. 내가 만드는 서버 런타임은 전반적인 비동기 메시지 ‘처리’에 집중하고 있고, 이쪽은 ‘통신 추상화’에 집중하고 있어서;;제목에서 기대한 거랑, 내가 가서 들은게 약간 덜 겹쳤다는 것?

잘 이해가 안가는 점: ‘왜 C#으로 중간 단계를 기술하는 것(IDL로 사용하는 것)에 집착할까?

나는 꽤나 다른 방법으로 근 몇 년간 시도해서 잘 쓰고 있다. 데이터를 설명하는 종류의 언어 ((lua (6개월), xml(2년),yaml(2년) 순으로 바뀌었고, 최종적으론 계속해서 yaml을 쓸 듯 하다.)) 를 IDL로 써서 주고 받는 코드를 몽땅 생성하는 것. 물론 기반 런타임은 필요한 언어마다 짜긴 해야하지만, 이건 안 바뀌는 부분이라..

대략 데이터 포맷 파일인 yaml 파일이 적당한 생성기를 거치고나면, C++, C#, lua, python, 매우 실험적으로 Google go 소스코드로 변환되고 이 과정은 IDE나 스크립트 수준에서 빌드할 때 자동으로 생성; 이 중 어느 언어 쌍을 골라도 서로 통신할 수 있게 만들었다.사실 이 생성기가 위에서 언급한 불변성/공변성을 몇 가지 경우에 대해 만족하지 못하긴 하지만, ((메시지 보낼 때 vector를 vector대신 못 넣는건 확실히 불편하다))  실용적인 목적에서 큰 차이는 없을 것 같다. 일단 이 대칭적인 코드 생성기가 lua/python에서 안되면 팀에서 쓰는 부하 테스트 도구(ServerBurner)를 못 쓰기 때문에 약간 불편한 부분이있긴하지만, 일단 ‘돌아가고’ ‘쉽게 (다른 사람이) 확장할 수 있는’게 목적이니 나는 큰 불만이 없다(완전히 신입으로 들어온 사람도 잘 적응하는거 보니 그렇게 느껴짐).

또한, C#의 enumeration 개념을 이해하는게 프로그래머들에게 벽이면, 왜 C++에서 우회할 방법 — 아마 맨 처음 기반 코드는 C++이었을텐데 — 이 많고 (boost function, c++ 0x lambda) 러닝커브도 큰 차이 없을텐데 왜 그건 시도하지 않았을까 하는 점. 2차 시도의 비동기 메시지 처리 형태는 boost::function과 (이후에) C++ 0x lambda로 쉽게 만들 수 있었고 (그리고 쉽게 쓰고 있고), 3차 시도의 구현 자체는 IDL이 data 기술 언어란 점만 빼면 시연 과정과 비교해봐도 큰 차이가 없는데.


  1. 세션 중에 연사가 말한 내용이기도 하고, 내가 예전부터 했던 일이기도 한데, 이 부분은 C++ 라이브러리나 C++0x의 기능을 쓰면 충분히 쓸 수 있다. ↩︎