[C#] 소켓통신 - 서버에서 특정 클라이언트만 메시지 전송


Development note/C#  2015. 5. 14. 22:46

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


참 오랫만에 C# 코드를 작성하는 것 같습니다. 요즘 우분투와 자바 매력에 흠뻑(?) 빠져들어서 C#코드는 거의 다루지를 못하고 있습니다.

소켓 통신에 있어서 특정 클라이언트만 메시지를 보내고 싶다라는 질문이 있어서 간단한 예제 소스를 작성해 봤습니다.

결과를 확인 해보시면 위쪽부터 3개는 클라인트 맨 밑은 서버로 구성되어있습니다.

그럼 메시지를 전송해 보겠습니다.

자 결과를 보시면 test1한테는 hi, test2는 hello, test3은 gogogo를 보냈는데 원하는 클라이언트에게만 메시지가 전송이 되었습니다.

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

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

namespace server
{
  class Program
  {
    static void Main(string[] args)
    {
      new Program();
    }

    private Dictionary<string, socket> m_client = null;

    public Program()
    {
      initialize();
    }
    protected void initialize()
    {
      m_client = new Dictionary<string, socket="">();
      Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      server.Bind(new IPEndPoint(IPAddress.Any, 10000));
      server.Listen(20);

      SocketAsyncEventArgs sockAsync = new SocketAsyncEventArgs();
      sockAsync.Completed += new EventHandler<socketasynceventargs>(sockAsync_Completed);
      server.AcceptAsync(sockAsync);

      Command();
    }

    private void sockAsync_Completed(object sender, SocketAsyncEventArgs e)
    {
      Socket server = (Socket)sender;
      Socket client = e.AcceptSocket;
      byte[] name = new byte[100];
      client.Receive(name);
      String strName = Encoding.Unicode.GetString(name).Trim().Replace("\0", "");
      m_client.Add(strName, client);
      SocketAsyncEventArgs recieveAsync = new SocketAsyncEventArgs();
      recieveAsync.SetBuffer(new byte[4], 0, 4);
      recieveAsync.UserToken = client;
      recieveAsync.Completed += new EventHandler<socketasynceventargs>(recieveAsync_Completed);
      client.ReceiveAsync(recieveAsync);
      Console.WriteLine();
      Console.WriteLine("***********" + strName + " Connected ***********");
      Console.Write("Sender ?");
      e.AcceptSocket = null;
      server.AcceptAsync(e);
    }
    private void recieveAsync_Completed(object sender, SocketAsyncEventArgs e)
    {
      Socket client = (Socket)sender;
      if (client.Connected && e.BytesTransferred > 0)
      {
        int length = BitConverter.ToInt32(e.Buffer, 0);
        byte[] data = new byte[length];
        client.Receive(data, length, SocketFlags.None);
        String data2 = Encoding.Unicode.GetString(data);
        String Name = searchSocket(client);
        if (Name != null)
        {
          Console.WriteLine();
          Console.WriteLine("***********" + Name + " receive ***********");
          Console.WriteLine(data2);
          Console.Write("Sender ?");
        }
        else
        {
        }

      }
      client.ReceiveAsync(e);
    }
    protected String searchSocket(Socket sender)
    {
      foreach (String key in m_client.Keys)
      {
        if (m_client[key] == sender)
        {
          return key;
        }
      }
      return null;
    }
    protected void Command()
    {
      while (true)
      {
        Console.Write("Sender ?");
        String sender = Console.ReadLine();
        if (m_client.ContainsKey(sender))
        {
          Console.Write("Message ?");
          String message = Console.ReadLine();
          Socket client = m_client[sender];
          byte[] data = Encoding.Unicode.GetBytes(message);
          client.Send(BitConverter.GetBytes(data.Length));
          client.Send(data, data.Length, SocketFlags.None);
        }
        else
        {
          Console.WriteLine("Not Socket");
        }
      }
    }
  }
}

위 소스는 일단 서버 쪽 소스입니다. 급하게 작성한 것이라 예외처리도 없고 엉망이지만 개념만 이해하시고 프로젝트에 맞게 작성하시면 될 듯 싶습니다.


다음은 클라이언트 소스입니다.

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

namespace client
{
  class Program
  {
    static void Main(string[] args)
    {
      new Program();
    }
    public Program()
    {
      initialize();
      Console.WriteLine("************Message**********");
      Console.ReadLine();
    }
    private String m_Name;
    public void initialize()
    {
      Console.Write("Name?");
      String strName = Console.ReadLine();
      if (strName.Trim().Length > 0)
      {
        m_Name = strName;
        Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        client.Connect(IPAddress.Parse("127.0.0.1"), 10000);

        byte[] name = new byte[100];
        byte[] buffer = Encoding.Unicode.GetBytes(strName);
        for (int i = 0; i < 100; i++)
        {
          if (i < buffer.Length)
          {
            name[i] = buffer[i];
          }
        }
        client.Send(name);
        SocketAsyncEventArgs reciveAsync = new SocketAsyncEventArgs();
        reciveAsync.Completed += new EventHandler<socketasynceventargs>(reciveAsync_Completed);
        reciveAsync.SetBuffer(new byte[4], 0, 4);
        reciveAsync.UserToken = client;
        client.ReceiveAsync(reciveAsync);
      }
    }

    private void reciveAsync_Completed(object sender, SocketAsyncEventArgs e)
    {
      Socket client = (Socket)sender;
      if (client.Connected && e.BytesTransferred > 0)
      {
        byte[] lengthByte = e.Buffer;
        int length = BitConverter.ToInt32(lengthByte, 0);
        byte[] data = new byte[length];
        client.Receive(data, length, SocketFlags.None);
        StringBuilder sb = new StringBuilder();
        sb.Append(m_Name);
        sb.Append("  -  ");
        sb.Append(Encoding.Unicode.GetString(data));
        Console.WriteLine(sb.ToString());
      }
      client.ReceiveAsync(e);
    }
  }
}

클라이언트는 그냥 일반 클라이언트 소스이기에 특별히 설명을 하지 않겠습니다.

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


서버의 경우는 일반 서버와 비슷한데 서버를 찾는 작업이 필요하겠군요.

이전에는 클라이언트 List를 ArrayList에 저장했지만 이번에는 키가 필요한 관계로 Dictionary에 저장을 했습니다.


이제부터는 뭐랄까 제 나름대로의 규약(프로토콜)을 작성하겠습니다.

접속을 할때는 무조건 byte(100)짜리의 데이터로 이름을 보내야 접속이 되는 것으로 정했습니다. 클라이언트 소스를 보시면 접속을 할때 이름을 넣고 byte[100]으로 전문을 전송합니다.

그럼 서버쪽에서는 이름을 맞고 그 이름을 키로 등록을 하여ㅕ Dictionary에 등록을 합니다.


이제는 반대로 메시지를 보낼때 형식입니다.

보낼때는 어떤 클라이언트에게 보낼 지 검색을 해야겠지요. 그래서 유저를 검색을 하겠습니다. 그 키로 검색을 소켓에 Message를 넣어서 전송하면 해당클라이언트만 데이터가 수신이 되겠지요.. 이상입니다.


위 소스는 기본적으로 접속이 끊길 때의 처리,예외상황처리 등이 되어있지 않습니다. 즉 실 프로젝트에 적용하실때는 그 사양에 맞게 적용시키시기 바랍니다.

(예제 소스를 올리면 안 된다는 메일이 많이 와서 ㅜㅜ..예제 소스일 뿐이지 적용을 하실 때는 꼭 프로젝트 사양에 맞게 수정하여 사용해 주세요.)

Socket.zip