[C#]비동기 소켓 채팅 프로그램 - 클라이언트 편

개발 노트/C#  2013.09.29 01:21


안녕하세요 명월입니다.


이번 포스팅에서는 클라이언트에 대해 설명하겠습니다..

저번 포스팅에서도 확인했지만 먼저 결과화면부터 설명 들어가겠습니다.



그럼 소스를 확인해 보도록 하겠습니다.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace AsyncClient
{
    public struct Telegram
    {
        private int m_DataLength;
        private byte[] m_Data;

        public void SetLength(byte[] Data)
        {
            if (Data.Length < 4)
                return;
            m_DataLength = BitConverter.ToInt32(Data, 0);
        }
        public int DataLength
        {
            get { return m_DataLength; }
        }
        public void InitData()
        {
            m_Data = new byte[m_DataLength];
        }
        public byte[] Data
        {
            get { return m_Data; }
            set { m_Data = value; }
        }
        public String GetData()
        {
            return Encoding.Unicode.GetString(m_Data);
        }
        public byte[] GetBuffer()
        {
            return new byte[4];
        }
        public void SetData(String Data)
        {
            m_Data = Encoding.Unicode.GetBytes(Data);
            m_DataLength = m_Data.Length;
        }
    }
    enum ChatType
    {
        Send,
        Receive,
        System
    }
    class AsyncClient
    {
        private Socket m_Client = null;
        private List m_Display = null;
        private int m_Line;

        static void Main(string[] args)
        {
            new AsyncClient();
        }
        public void DataInput()
        {
            String sData;
            Telegram _telegram = new Telegram();
            SendDisplay("ChattingProgram ClientStart", ChatType.System);
            while (true)
            {
                sData = Console.ReadLine();
                if (sData.CompareTo("exit") == 0)
                {
                    break;
                }
                else
                {
                    if (m_Client != null)
                    {
                        if (!m_Client.Connected)
                        {
                            m_Client = null;
                            SendDisplay("Connection Failed!", ChatType.System);
                            SendDisplay("Press Any Key...", ChatType.System);
                        }
                        else
                        {
                            _telegram.SetData(sData);
                            SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
                            _sendArgs.SetBuffer(BitConverter.GetBytes(_telegram.DataLength), 0, 4);
                            _sendArgs.Completed += new EventHandler(Send_Completed);
                            _sendArgs.UserToken = _telegram;
                            m_Client.SendAsync(_sendArgs);
                        }
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
        public void SendDisplay(String nMessage, ChatType nType)
        {
            StringBuilder buffer = new StringBuilder();
            switch (nType)
            {
                case ChatType.Send:
                    buffer.Append("SendMessage : ");
                    break;
                case ChatType.Receive:
                    buffer.Append("ReceiveMessage : ");
                    break;
                case ChatType.System:
                    buffer.Append("SystemMessage : ");
                    break;
            }
            buffer.Append(nMessage);
            if (m_Line < 20)
            {
                m_Display.Add(buffer);
            }
            else
            {
                m_Display.RemoveAt(0);
                m_Display.Add(buffer);
            }
            m_Line++;
            Console.Clear();
            for (int i = 0; i < 20; i++)
            {
                if (i < m_Display.Count)
                {
                    Console.WriteLine(m_Display[i].ToString());
                }
                else
                {
                    Console.WriteLine();
                }
            }
            Console.Write("Input Message (exit - 終了): ");
        }
        public AsyncClient()
        {
            m_Display = new List();
            m_Line = 0;

            Socket _client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint _ipep = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8000);

            SocketAsyncEventArgs _args = new SocketAsyncEventArgs();
            _args.RemoteEndPoint = _ipep;
            _args.Completed += new EventHandler(Connect_Completed);

            _client.ConnectAsync(_args);

            DataInput();
        }

        private void Connect_Completed(object sender, SocketAsyncEventArgs e)
        {
            m_Client = (Socket)sender;

            if (m_Client.Connected)
            {
                Telegram _telegram = new Telegram();
                SocketAsyncEventArgs _receiveArgs = new SocketAsyncEventArgs();
                _receiveArgs.UserToken = _telegram;
                _receiveArgs.SetBuffer(_telegram.GetBuffer(), 0, 4);
                _receiveArgs.Completed += new EventHandler(Recieve_Completed);
                m_Client.ReceiveAsync(_receiveArgs);
                SendDisplay("Server Connection Success", ChatType.System);
            }
            else
            {
                m_Client = null;
                SendDisplay("Connection Failed!", ChatType.System);
                SendDisplay("Press Any Key...", ChatType.System);
            }
        }

        private void Send_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket _client = (Socket)sender;
            Telegram _telegram = (Telegram)e.UserToken;
            _client.Send(_telegram.Data);
            SendDisplay(_telegram.GetData(), ChatType.Send);
        }
        private void Recieve_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket _client = (Socket)sender;
            Telegram _telegram = (Telegram)e.UserToken;
            _telegram.SetLength(e.Buffer);
            _telegram.InitData();
            if (_client.Connected)
            {
                _client.Receive(_telegram.Data, _telegram.DataLength, SocketFlags.None);
                SendDisplay(_telegram.GetData(), ChatType.Receive);
                _client.ReceiveAsync(e);
            }
            else
            {
                SendDisplay("Connection Failed!", ChatType.System);
                m_Client = null;
            }
        }
    }
}


생성자 부분을 확인 해 보도록 하겠습니다.



클라이언트 접속 부분입니다. 선언부분만 서버와 다르네요.... 이 부분은 굳이 비동기가 아니어도 서버와 클라이언트는 이런 식으로 흐릅니다.

서버는 바인딩을 해서 Listen 을 하고 클라이언트는 Connect 를 하는 것입니다.



이 부분은 접속 Send, Recieve 이벤트 부분입니다. 서버와 크게 다른게 없습니다.

단지 차이라고 하면 서버는 다중접속이 가능하게 하므로 Accept 완료 후에 재 대기 형태로 진입하지만 클라이언트는 하나의 커넥션만 유지하는 형태이기 때문에 접속이 완료 대고 재 Connection을 호출하지는 않습니다.( 물론 개발의 사양에 따라선 다중 접속이 필요할때도 있습니다. 프로그램은 정답이 없습니다. ^^)


관련 소스를 올리겠습니다.


질문이나 궁금한점이 있으면 메일이나 댓글 주세요...



AsyncClient.zip








댓글 10개가 달렸습니다.
댓글쓰기
  1. MINS
    2013.11.06 16:50 신고 |  수정/삭제  댓글쓰기

    안녕하세요 글 잘보고 있습니다. ^^ 질문 하나 드리고싶은데

    서버에서 한번에 패킷을 여러개 보낼시에 ( 연달아 보낼경우 )

    클라이언트에서 ReceiveAsync로 버퍼 받은후 처리하게되면 패킷이 끊길때가 있는데 이경우

    어떤식으로 처리하시는지 궁금합니다. ㅎㅎ;

    • 明月 v명월v
      2013.11.10 23:56 신고 |  수정/삭제

      패킷이 끊긴다는 의미를 정확하게 모르겠네요...
      아랫 글도 마찬가지지만 퍼포먼스 차이 같네요..
      이 부분은 수신속도가 송신속도를 앞지를 때 나는 현상?

  2. MINS
    2013.11.06 17:08 신고 |  수정/삭제  댓글쓰기

    서버에서는 모든 패킷을 보내는데 약간의 시간 타임? 이 있으면 모든 패킷을

    클라이언트가 받지만 약간의 타임을 주지않을경우 클라이언트가 받지를 못하더라구영 ㅠ

    • 明月 v명월v
      2013.11.10 23:54 신고 |  수정/삭제

      안녕하세요... 블로그 방문 감사합니다...
      그 문제는 송신 퍼포먼스에 맞추어해야 하는거지요.. 요 내용은 그냥 요런 방식으로 하면 된다.. 이런 식이라.. 그런 퍼포먼스까지는 생각을 못한게 많아요...
      실제 개발을 하신다면 그런 부분까지 세심하게 해야하는게 맞지요..

  3. jibbb
    2015.07.24 09:36 신고 |  수정/삭제  댓글쓰기

    안녕하세요! 많이 옛날 글이지만 한가지 질문을 하려고 합니다...
    제가 이 코드의 클라이언트와 서버를 조금 고쳐서 해보구 있는데요!
    클라이언트는 유니티로 씨샵으로 하고 있습니다.
    근데 클라이언트가 종료가 안되더라구요.. 서버가 종료가 되야 클라이언트도 그제서야 종료가 되는데.. 해결방법이 혹시 없을까요..?ㅠㅠ

    • 明月 v명월v
      2015.07.26 19:15 신고 |  수정/삭제

      안녕하세요 명월입니다.
      블로그 방문 감사합니다. 먼저 제가 어떤 상황인지 정확하게 인지기 되지 않아 정확하게 알려드릴 수가 없는데...
      서버와 클라이언트가 연결되어있어서 그런게 아닐까요???
      그럼 무언가의 액션으로 그냥 연결을 끊으면 될 꺼 같은데..

    • jibbb
      2015.07.27 12:38 신고 |  수정/삭제

      client.close() 이렇게 하니까 바로 되더라구요! 감사합니다!!!

  4. jjsing
    2016.01.12 14:46 신고 |  수정/삭제  댓글쓰기

    명월님의 블로그가 소켓 프로그래밍을 이해하는데 많은 도움이 되었습니다. 정말 감사합니다.

    조금 궁금한게 있는데. 지금 이 채팅 서버에는 첫번째 패킷의 텔레그램의 데이터 사이즈를 보내고 두 번째로 패킷을 받을 때는 첫 번째로 받았던 데이터 사이즈만큼의 버퍼를 Receive()를 통해 받는 걸로 되어 있는게 맞나요?

    만약 패킷의 일부데이터만 먼저 도착하는 경우에는 기다렸다가 SocketAsyncEventArgs의 버퍼가 데이터 사이즈 만큼 채워졌을때 Receive를 호출하게 되는 건가요?


    -강의는 정말 감사합니다.

    • 明月 v명월v
      2016.03.04 10:16 신고 |  수정/삭제

      답변이 매우 늦었네요... 버퍼가 채워졌을 떄 호출이 되는 것은 아닐 껍니다(?) 솔직히 저도 테스트를 안해봐서 정확하게 답변을 드리기가 힘드네요... 후에 시간나면 관련 테스트를 해보고 답변 드릴께요.

  5. 부기
    2016.09.19 23:09 신고 |  수정/삭제  댓글쓰기

    클라이언트 글 잘보았습니다. 그런데 제가 만든 서버에서 여러개의 데이터를 하나씩 보내주는데 클라이언트는 8개까지 밖에 못받는데 해결방안이 있나요??