Project History/다중 기능을 포함한 채팅 프로그램

[C# 채팅 프로그램 #01] IOCP - EAP 패턴을 이용한 비동기 TCP/IP 서버 구현 (SocketAsyncEventArgs)

JeongKyun 2022. 2. 10.
반응형

이번글에서는 비동기 소켓 이벤트인 SocketAsyncEventArgs를 활용하여 서버를 구현을 해보려한다.

 

SocketAsyncEventArgs는 보통 유니티 게임 서버 개발을 할때 사용하는 경우가 있다고 하며, 나도 사실 이번 프로젝트 진행하면서 처음 사용해본 클래스이다.

 

이 클래스는 닷넷 비동기 소켓에서 사용하는 개념으로 비동기 소켓 메서드를 호출할 때 마다 반드시 필요한 객체이다. 닷넷 3.5 이전 버전에서는 Begin과 End계열의 API를 사용했지만 닷넷 3.5이상 버전부터는 해당 클래스의 API를 사용하여 소켓 프로그래밍을 구현할 수 있다고 한다.

 

나도 여지껏 사용해왔던 Begin ~ End 계열의 API를 사용할 경우 IAsyncResult라는 객체를 사용했지만 해당 이벤트 클래스를 이용하면 객체를 풀링하여 사용할 수 있기 때문에 메모리를 재사용할 수 있다는 장점이 있다고 한다.

 

 

서버 열기

서버를 여는 부분이다.

기본 포트는 12000으로 잡았으며 백로그는 10으로 설정해주었다.

해당 작성한 소스에 대해서 알아보면,

 

IPEndPoint 객체는 끝점을 의미하며 도착 지점이라고 보면된다. 여기서 클라이언트가 도착할 지점은 바로 서버이기때문에 해당 파라미터는 서버의 IP, Port를 입력해주면 된다.

 

서버는 항상 Bind -> Listen 순으로 진행되며, 서버 정보를 소켓에 바인드 시키고 Listen을 호출하여 클라이언트가 접속할 수 있게 만들어주는 것이다.

 

Completed 프로퍼티에 콜백함수의 이벤트 핸들러 객체를 연결해주고 AcceptAsync를 호출할 때 해당 객체를 파라미터로 넘겨주면 되는 방식이다.

 

이제 AcceptAsync 메서드를 호출하면 서버가 대기 상태에 있다가 클라이언트가 접속하는 순간 콜백 메서드로 통지가 오게된다. 

 

정리하면 Completed는 Accept처리가 완료되었을 때 호출되는 대리자(Delegate)이다. 닷넷 비동기 소켓은 이처럼 메서드 호출 -> 완료 통지 순으로 이루어진다. 

 

이렇게 비동기 메서드를 사용하여 작성할 경우 프로그램이 블로킹 되지않고 통보를 기다리면서 다른 작업들을 수행할 수 있는 상태가 된다. AcceptAsync까지 호출하면 클라이언트의 접속을 받아들일 수 있는 상태가 되는 것이다.

 

아래는 Completed의 콜백함수 내용이다. 내용이 길어 이미지가 아닌 원본 소스로 올린다.

        /// <summary>
        /// 연결 될 시 발생되는 이벤트
        /// </summary>
        /// <param name="sender">이벤트 발생시킨 소켓 </param>
        /// <param name="e"></param>
        private void sockAsync_Completed(object sender, SocketAsyncEventArgs e)
        {
            try
            {
                Socket server = (Socket)sender;
                Socket client = e.AcceptSocket;
                byte[] name = new byte[100];
                client.Receive(name);
                
                //16진수 -> string형으로 parsing
                String str_data = Encoding.UTF8.GetString(name).Trim().Replace("\0", "");
                
                //string -> JObject parsing
                JObject jobj = JObject.Parse(str_data);
                
		//json형식에서 key가 text인 value값을 string형에 담기
                string user = jobj["text"].ToString();
				
                /* 
                   특정 클라이언트를 찾기 위해 사용자 이름을 클라이언트에서 
                   받아서 Dictionary에 넣는다. 
                   " Dictionary<string,Socket> dic_client "
                */
                dic_client.Add(user, client);
                SocketAsyncEventArgs receiveAsync = new SocketAsyncEventArgs();
                receiveAsync.Completed += new EventHandler<SocketAsyncEventArgs>(receiveAsync_Completed);
                receiveAsync.SetBuffer(new byte[4096], 0, 4096);
                receiveAsync.UserToken = client;
                client.ReceiveAsync(receiveAsync);

                StringBuilder sb = new StringBuilder();
                sb.AppendLine("*********** " + user + "- Connected ***********");
                sb.AppendLine();

                Invoke((MethodInvoker)delegate
                {
                    rtb_text.AppendText(sb.ToString());
                    lb_client_list.Items.Add(user);
                });

                e.AcceptSocket = null;
                server.AcceptAsync(e);

            }
            catch (Exception ex)
            {
                MessageBox.Show("Error : " + ex.Message);
            }

        }

위의 내용도 알아보면 이후에 작성하겠지만 해당 메세지를 Json 형식으로 주고 받고 있기 때문에 Newtonsoft를 참조하고 JObject 클래스를 이용하여 Json형식을 String형으로 파싱하는 과정이다.

 

그리고 특정 클라이언트에게 보내야하기때문에 사용자 이름을 먼저 받고 해당 이름과 소켓을 컬렉션(Dictionary)에 담아둔다. 담아둔 후 나중에 컬렉션에서 사용자 이름을 key값으로 소켓을 찾아 Send할 생각이다.

 

그 아래의 SetBuffer 메서드는 SocketAsyncEventArgs 객체에 버퍼를 설정해주는 역할을 하며 하나의 버퍼를 설정한 다음에는 index 값을 증가시켜 다음 버퍼 위치를 가르킬 수 있도록 처리하는 메서드이다. 예를 들어 넓은 땅에 선을 그어서 한쪽은 내 것 다른 한쪽은 네것하는 식으로 나눈다고 보면된다.

 

UserToken은 원격지의 클라이언트 하나당 한 개씩 매칭되는 유저 객체라고 보면된다. 실제로 해당 클래스를 사용하진 않았지만 해당 클래스의 역할은 클라이언트의 접속을 받아들이기 위해 서버에서 Listener를 설정하고 AcceptAsync메서드를 통해서 접속을 수락하는 역할을 한다. 채팅프로그램과 같이 단순한 서버라면 이러한 토큰 객체가 필요 없는 경우가 많지만 만약 수천의 동시 접속을 고려해야 하는 게임 서버를 개발할 때는 번거롭지만 해당 토큰 객체도 사용하여 여러 작업들이 필요하다고 한다.

 

그리고 그 아래의 소스들은 컨트롤에 접속한 사용자 정보를 넣어주는 소스이니 간단히 참고만하면 될것같다.

 

SocketAsyncEventArgs 클래스 참고 서적 (유니티 개발자를 위한 C#)

http://preview.hanbit.co.kr/2748/sample_ebook.pdf

댓글

💲 많이 본 글