==================================================================
모든 출처는
- 유니티 개발자를 위한 C#으로 온라인 게임 서버 만들기 - 저자 이석현, 출판사 한빛미디어
그리고 URL :
http://lab.gamecodi.com/board/zboard.php?id=GAMECODILAB_Lecture_series&no=62
==================================================================
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | public class CNetworkService { // 클라이언트의 접속을 받아들이기 위한 객체 CListener client_listener; // 메시지 수신, 전송시 필요한 오브젝트 SocketAsyncEventArgsPool receive_event_args_pool; SocketAsyncEventArgsPool send_event_args_pool; // 메시지 수신, 전송시 .Net 비동기 소켓에서 사용할 버퍼를 관리하는 객체 BufferManager buffer_manager; // 클라이언트의 접속이 이루어졌을 때 호출되는 델리게이트 public delegate void SessionHandler(CUserToken token); public SessionHandler session_created_callback { get; set; } } | cs |
----------------------------------------------------------------------------------------------------------------
클라이언트의 접속을 받아들이는 CListener 객체가 선언되어있습니다.
SocketAsyncEventArgs 라는 클래스는 .Net 비동기 소켓에서 사용하는 개념으로
비동기 소켓 메소드를 호출할 때 항상 필요한 객체입니다.
매번 IAsyncResult를 생성하지 않고 풀링하여 사용할 수 있어 메모리 재사용이 가능한 것이 장점입니다.
MSDN 문서중에 객체를 풀링하여 쓰는 것이 기존 native c++에서 하는 풀링 만큼의
효율을 가져오지 않지만 서버가 살아있는 동안 계속 사용할 메모리이기 때문에 풀링을 사용합니다.
BufferManager는 이름에서도 알 수 있듯이 데이터를 송, 수신할 때 사용할 버퍼를 관리하는 매니저 객체입니다.
소켓 관련된 책을 보면 송, 수신 버퍼 이야기를 자주 듣게 되는데 TCP에서 데이터를 보내고 받을 때
소켓마다 버퍼라는 것이 할당되는 것입니다.
이것은 OS에서 구현되어 있는 부분이라 따로 신경 쓸 필요는 없습니다.
이 소켓 버퍼로부터 메시지를 복사해오고(수신) 밀어 넣는(전송) 작업을 할 때 사용할 버퍼를 설정해주면 됩니다. 이 버퍼 역시 네트워크 통신이 지속되는 동안에 계속해서 사용하는 메모리입니다.
.Net 환경에서는 가비지 컬렉션이 작동되므로 꼭 풀링하지 않아도 되지만
버퍼라는건 거의 매순간 쓰인다고 봐도 되니 풀링하기로 합니다.
그 다음 델리게이트가 정의되어 있는데 클라이언트가 접속했을 때 어딘가로 통보해주기 위한 수단입니다.
----------------------------------------------------------------------------------------------------------------
1-2. 클라이언트의 접속 처리하기
클라이언트의 접속을 처리하기 위한 코드를 작성하겠습니다.
TCP서버 구현의 흐름은 bind -> listen -> accept 순으로 진행됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | class CListener { // 비동기 Accept를 위한 EventArgs SocketAsyncEventArgs accept_args; // 클라이언트의 접속을 처리할 소켓 Socket listen_socket; // Accept 처리의 순서를 제어하기 위한 이벤트 변수 AutoResetEvent flow_control_event; // 새로운 클라이언트가 접속했을 때 호출되는 콜백 public delegate void NewclientHandler(Socket client_socekt, object token); public NewclientHandler callback_on_newclient; public CListener() { this.callback_on_newclient = null; } public void start(string host, int port, int backlog) { // 소켓 생성 this.listen_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPAddress address; if (host == "0.0.0.0") { address = IPAddress.Any; } else { address = IPAddress.Parse(host); } IPEndPoint endpoint = new IPEndPoint(address, port); try { // 소켓에 host 정보를 바인딩 시킨 뒤 Listen 메소드를 호출하여 준비 listen_socket.Bind(endpoint); listen_socket.Listen(backlog); this.accept_args = new SocketAsyncEventArgs(); this.accept_args.Completed += new EventHandler<SocketAsyncEventArgs>(on_accept_completed); // 클라이언트가 들어오기를 기다림 // 비동기 메소드이므로 블로킹 되지 않고 바로 리턴 // 콜백 메소드를 통해서 접속 통보를 처리 this.listen_socket.AcceptAsync(this.accept_args); } catch (Exception e) { //Console.WriteLine(e.Message); } } } | cs |
IPEndPoint 라는 객체는 끝점이라고 말할 수 있는데 도착 지점으로 이해하시면 됩니다.
클라이언트가 도착할 지점은 서버가 됩니다. 서버의 IP, Port 정보로 IPEndPoint가 구성됩니다.
이 서버가 Host가 되며 클라이언트는 Peer라고 말할 수 있습니다.
1 2 | this.accept_args = new SocketAsyncEventArgs(); this.accept_args.Completed += new EventHandler<SocketAsyncEventArgs>(on_accept_completed); | cs |
SocketAsyncEventArgs 객체를 사용할 때가 왔습니다.
Completed 프로퍼티에 이벤트 핸들러 객체를 연결시켜 주고 AcceptAsync호출시 파라미터로
넘겨주기만 하면 됩니다. Completed라는 이름에서 알 수 있듯이 accept처리가 완료 되었을 때
호출되는 델리게이트입니다.
.Net 비동기 소켓에서는 이처럼 메소드 호출 -> 완료 통지 개념으로 이루어집니다.
1 | this.listen_socket.AcceptAsync(this.accept_args); | cs |
이제 accept처리 부분입니다. 이 강좌에서는 비동기 메소드를 사용하기로 하였으므로 AcceptAsync를
호출하게 됩니다. 이 메소드는 호출한 직후 바로 리턴되며 accept결과에 대해서는 콜백 메소드로 통보하게 됩니다. 따라서 프로그램이 블로킹 되지 않고 통보를 기다리며 다른 일을 할 수 있게 됩니다.
1 | this.listen_socket.AcceptAsync(this.accept_args); | cs |
이 부분을 아래처럼 바꾸겠습니다.
1 2 | Thread listen_thread = new Thread(do_listen); listen_thread.Start(); | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | void do_listen() { // accept처리 제어를 위해 이벤트 객체를 this.flow_control_event = new AutoResetEvent(false); while (true) { // SocketAsyncEventArgs를 재사용하기 위해서 null로 만듦 this.accept_args.AcceptSocket = null; bool pending = true; try { // 비동기 accept를 호출하여 클라이언트의 접속을 받아들임 // 비동기 메소드이지만 동기적으로 수행이 완료될 경우도 있으니 // 리턴 값을 확인하여 분기시켜야함 pending = listen_socket.AcceptAsync(this.accept_args); } catch (Exception e) { //Console.WriteLine(e.Message); continue; } // 즉시 완료되면 이벤트가 발생하지 않으므로 리턴 값이 false일 경우 콜백 메소드를 직접 호출 // pending상태라면 비동기 요청이 들어간 상태이므로 콜백 메소드를 기다림 // http://msdn.microsoft.com/ko-kr/library/system.net.sockets.socket.acceptasync%28v=vs.110%29.aspx if (!pending) { on_accept_completed(null, this.accept_args); } // 클라이언트 접속 처리가 완료되면 이벤트 객체의 신호를 전달받아 다시 루프를 수행하도록함 this.flow_control_event.WaitOne(); } } | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | void on_accept_completed(object sender, SocketAsyncEventArgs e) { if (e.SocketError == SocketError.Success) { // 새로 생긴 소켓을 보관 Socket client_socket = e.AcceptSocket; // 다음 연결을 받음 this.flow_control_event.Set(); // 이 클래스에서는 accept까지의 역할만 수행하고 클라이언트의 접속 이후의 처리는 // 외부로 넘기기 위해서 콜백 메소드를 호출해주도록 함 // 이유는 소켓 처리부와 컨텐츠 구현부를 분리하기 위함임 // 컨텐츠 구현부분은 자주 바뀔 가능성은 있지만, 소켓 Accept 부분은 상대적으로 변경이 적은 부분이기 때문에 // 양쪽을 분리시켜 주는 것이 좋음. // 또한 클래스 설계 방침에 따라 Listen에 관련된 코드만 존재하도록 하기 위한 이유도 있음 if (this.callback_on_newclient != null) { this.callback_on_newclient(client_socket, e.UserToken); } return; } else { //todo:Accept 실패 처리 //Console.WriteLine("Failed to accept client."); } // 다음 연결을 받아들임 this.flow_control_event.Set(); } | cs |
'- Programming > - C#' 카테고리의 다른 글
★ 6. c# 네트워크 개발 p5 (0) | 2017.02.10 |
---|---|
★ 5. c# 네트워크 개발 p4 (0) | 2017.02.09 |
★ 4. c# 네트워크 개발 p3 (0) | 2017.02.09 |
★ 3. c# 박싱과 언박싱 (0) | 2017.02.08 |
★ 2. c# 네트워크 개발 p2 (0) | 2017.02.08 |