[C#]비동기 소켓 채팅 프로그램 - 서버편

개발 노트/C#  2013.09.28 10:40


안녕하세요... 명월입니다...


정말 오랫만에 하는 포스팅입니다. 그동안 프로젝트가 너무 바쁜 바람에 제때 포스팅을 하지도 못하네요... 일일 하루 포스팅의 꿈은 내년으로..ㅎㅎ


이번 포스팅에서는 저번에 어떤분이 비동기 소켓을 조금 더 자세하고 예제를 곁드려서(?) 설명을 부탁하기에 했습니다.


먼저 비동기 소켓은


[C#] 비동기 소켓 접속


에 기본적인 설명은 해 두었으니 참고 해 주세요..


일단 전체 실행화면을 먼저 확인 하고 소스를 확인 하겠습니다.



채팅 프로그램 환경은 일단 콘솔 프로그램으로 맞추었습니다. 위는 서버이고 아래는 클라이언트 입니다. 


일단 결과 화면을 봤으니 서버 소스를 확인 하겠습니다.


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

namespace AsyncServer
{
    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 AsyncServer
    {
        private List m_Client = null;
        private List m_Display = null;
        private int m_Line;

        static void Main(string[] args)
        {
            new AsyncServer();
        }
        public AsyncServer()
        {
            m_Display = new List();
            m_Line = 0;

            Socket _server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            IPEndPoint _ipep = new IPEndPoint(IPAddress.Any, 8000);
            _server.Bind(_ipep);
            _server.Listen(20);

            SocketAsyncEventArgs _args = new SocketAsyncEventArgs();
            _args.Completed += new EventHandler(Accept_Completed);

            _server.AcceptAsync(_args);

            DataInput();
        }
        public void DataInput()
        {
            
            String sData;
            Telegram _telegram = new Telegram();
            m_Client = new List();
            SendDisplay("ChattingProgram ServerStart", ChatType.System);
            while (true)
            {
                sData = Console.ReadLine();
                if (sData.CompareTo("exit") == 0)
                {
                    break;
                }
                else
                {
                    foreach (Socket _client in m_Client)
                    {
                        if (!_client.Connected)
                        {
                            m_Client.Remove(_client);
                        }
                        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;
                            _client.SendAsync(_sendArgs);
                        }
                    }
                }
            }
        }
        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 - 終了): ");
        }

        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 Accept_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket _client = e.AcceptSocket;

            Telegram _telegram = new Telegram();
            SocketAsyncEventArgs _receiveArgs = new SocketAsyncEventArgs();
            _receiveArgs.UserToken = _telegram;
            _receiveArgs.SetBuffer(_telegram.GetBuffer(), 0, 4);
            _receiveArgs.Completed += new EventHandler(Recieve_Completed);
            _client.ReceiveAsync(_receiveArgs);
            m_Client.Add(_client);
            SendDisplay("Client Connection!",ChatType.System);

            Socket _server = (Socket)sender;
            e.AcceptSocket = null;
            _server.AcceptAsync(e);
        }

        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);
            }
            else
            {
                SendDisplay("Connection Failed.", ChatType.System);
            }
            if (_client.Connected)
            {
                _client.ReceiveAsync(e);
            }
            else
            {
                m_Client.Remove(_client);
            }
        }
    }
}


소스 설명을 하겠습니다.


DataInput(), SendDisplay(String nMessage,ChatType nType) 함수는 단지 콘솔에 메시지 표시하는 함수 입니다.



위 화면은 생성자에서 소켓을 선언하는 부분입니다. 먼저 Bind 를 시키고 비동기 소켓 이벤트Args를 선언 후 AcceptAsync 함수를 호출하였습니다.....



먼저 Accept_Completed 이벤트를 확인하겠습니다. Accept_Completed 이벤트는 클라이언트가 접속이 되었을때 호출 되는 이벤트 입니다. 이벤트가 끝나면 소켓에 다시 이벤트를 거는 것이 주요 포인트 입니다.


그리고 중간에 보면 접속이 완료가 되면 Recieve 이벤트를 걸어 놓습니다.

 Recieve 이벤트는 클라이언트로 부터 Send 전문이 날라오면 호출 되는 이벤트 입니다.


Receive 이벤트를 보면 구조가 이벤트를 받고 동기 Receive도 걸려있습니다.

이는 채팅 글이 가변이기 때문에 메시지를 첫 메시지는 메시지의 크기를 보내고 두번째 메시지 형태는 화면에 표시하는 형태로 나타나겠습니다.


Send 이벤트는 DataInput 함수에서 Send 이벤트를 선언을 하는데 이 이벤트는 선언과 동시에 호출되는 형태 입니다.



예제 소스를 첨부하겠습니다.


궁금한점이나 질문이 있으면 댓글이나 메일을 보내주세요... 다음 포스팅은 클라이언트 편입니다.


AsyncServer.zip






댓글 7개가 달렸습니다.
댓글쓰기
  1. 스마일러
    2013.10.06 01:30 신고 |  수정/삭제  댓글쓰기

    안녕하세요
    명월님 C# 관련해서 소켓 웹 서버를 구축할일이 생겨서 검색하다보니 흘러들어오게 됬습니다.
    명월님이 올려주신 예제로 어떻게든 해결해보자... 하고 매달렸지만
    C#은 적응되지 않네요 ...
    제가 해결하고 싶은 내용은 ... Windows Phone 8에 Socket Web Server를 생성하는 프로그램을 짜고싶은데
    서버를 찾지 못하는건 아닌데 '해당 웹페이지를 사용할 수 없음' 이라고 데이터를 받지 못하고 그냥 꺼져버립니다.

    코딩은 ...


    Socket main_socket;

    SocketAsyncEventArgs main_events;
    IPEndPoint IPEndPoint;
    // 생성자
    public MainPage()
    {
    //main_socket.ReceiveAsync
    InitializeComponent();

    Listen(true);



    }

    public void Listen(Boolean First)
    {
    if (First)
    {
    IPEndPoint = new IPEndPoint(IPAddress.Any, 1234);
    //test55.Text = test55.Text + "\n" + ipe.AddressFamily;
    main_socket = new Socket(IPEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
    main_socket.Bind(IPEndPoint);
    main_socket.Listen(50);

    main_events = new SocketAsyncEventArgs();
    main_events.Completed += new EventHandler<SocketAsyncEventArgs>(main_events_completed);

    while (true)
    {
    Socket te = main_events.AcceptSocket;
    }
    }
    main_socket.AcceptAsync(main_events);
    }

    private void main_events_completed(object sender, SocketAsyncEventArgs e)
    {

    //throw new NotImplementedException();
    try
    {
    Listen(false);
    // main_socket.
    }
    catch
    {
    }
    new sub_sockets(e.AcceptSocket);
    }

    class sub_sockets
    {
    Thread thread;
    Socket socket;
    public sub_sockets(Socket getSocket)
    {
    socket = getSocket;
    thread = new Thread(Sender);
    thread.Start();
    }

    public void Sender()
    {
    SocketAsyncEventArgs header = new SocketAsyncEventArgs();
    String strHead = "HTTP/1.0 100 BedRequest ok\r\n";
    strHead += "server: ArcticHare\r\n";
    strHead += "content-type:text/html\r\n";
    strHead += "\r\n";
    byte[] bufHead = Encoding.UTF8.GetBytes(strHead);

    SocketAsyncEventArgs header_event = new SocketAsyncEventArgs();
    header_event.AcceptSocket = socket;
    header_event.SetBuffer(bufHead, 0, bufHead.Length);
    header_event.Completed += header_event_completed;
    socket.SendAsync(header_event);

    }

    public void header_event_completed (object sender, SocketAsyncEventArgs e){


    String StrData = "테스트 ";
    //ttttt += i.ToString();
    //i++;
    byte[] bufData = Encoding.UTF8.GetBytes(StrData);

    SocketAsyncEventArgs DataEvents= new SocketAsyncEventArgs();
    DataEvents.SetBuffer(bufData, 0, bufData.Length);
    DataEvents.AcceptSocket = e.AcceptSocket;
    DataEvents.Completed += DataEvents_Completed;
    e.AcceptSocket.SendAsync(DataEvents);
    }

    private void DataEvents_Completed(object sender, SocketAsyncEventArgs e)
    {
    //throw new NotImplementedException();
    e.AcceptSocket.Close();
    }
    }
    }

    이렇게 해뒀는데 ..
    Bad Request라고 보냈다고 안뜰리는 없고...
    어떤 부분이 이상한 걸까요...
    조언 부탁드립니다...
    시작부터 막히니 막막하네요 ...

    • 明月 v명월v
      2013.10.07 00:04 신고 |  수정/삭제

      안녕하세요...블로그 방문 감사합니다.. 질문이.....기네요... 메일로 보내주심 더 좋았을 것을....

      소스만 가지고는 제가 어떤 상황인지 정확하게 진단하기 힘듬니다..환경적인 요인도 존재하니깐요....

      웹서버를 만드시는 거라면 만든게 있긴 있습니다면 그땐 동기형으로 만들었네요...앞의 포스팅을 확인해 주세요...(아마 프로토콜 조사할 일이 있어서 했던걸로 기억..ㅎㅎ) 소스를 대충 훑어보기엔 프로토콜 부분이 제대로 안되있는 듯 싶네요... 그 부분 살펴보시구요 도저히 모르겠으면 메일 주세요...
      비동기로는 예전에 만들어 논게 있는데... 찾아봐야 할 듯..

    • 스마일러
      2013.10.07 22:17 신고 |  수정/삭제

      감사합니다 ^-^..
      몇번만 더 생각해보고. ..
      도저히안되면..

  2. 모가리
    2014.07.02 21:05 신고 |  수정/삭제  댓글쓰기

    안녕하세요..회사에서 서버프로그램을 짜야하는데 잘보고 갑니다..

    공부해야할께 많네요 전..;;

    궁금한게 있는데 위 소스는 쓰레드 형식은 아닌건가요??

    accept 후 이벤트에 이벤트 호출로 메시지를 계속 수신받는거같은데..

    쓰레드 생성없이도 이게 계속 수신이 되는건가요?ㅎㅎ

  3. 쪼재
    2014.12.19 17:58 신고 |  수정/삭제  댓글쓰기

    안녕하세요.

    저는 android(클라이언트) + c#(서버) 조합으로 채팅앱을 만들어볼꺼 하는데요.

    서버 프로그램 개발할 때 동기식으로 해야한다고 알고 있었는데...

    어떤 식으로 개발해야할지 방향제시 부탁드립니다.

    • 明月 v명월v
      2015.01.12 11:33 신고 |  수정/삭제

      블로그 방문 감사합니다.
      서버프로그램은 Socket Listen 은 비동기 처리는 동기식으로 처리해야 문제가 없습니다...