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


Development note/C#  2013. 9. 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<stringbuilder> 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<socketasynceventargs>(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<stringbuilder>();
      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<socketasynceventargs>(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<socketasynceventargs>(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;
      }
    }
  }
}

생성자 부분을 확인하겠습니다.

클라이언트 접속 부분입니다. 선언 부분은 서버와 다르네요. 이 부분은 굳이 비동기가 아니어도 상관없기 때문에 동기식처럼 직접 Connect 클래스를 받고 처리했습니다.

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

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

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


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


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


AsyncClient.zip