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


Development note/C#  2013. 9. 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<socket> m_Client = null;
    private List<stringbuilder> m_Display = null;
    private int m_Line;

    static void Main(string[] args)
    {
      new AsyncServer();
    }
    public AsyncServer()
    {
      m_Display = new List<stringbuilder>();
      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<socketasynceventargs>(Accept_Completed);

      _server.AcceptAsync(_args);

      DataInput();
    }
    public void DataInput()
    {
      String sData;
      Telegram _telegram = new Telegram();
      m_Client = new List<socket>();
      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<socketasynceventargs>(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<socketasynceventargs>(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