C# Multi Thread를 이용한 다중 Client 접속 Server

Software/C#|2023. 2. 11. 12:44
반응형

C#에서 TCP/IP 를 이용한 서버 동작을 위한 클래스를 구현했습니다.  이 서버 클래스는 다중 클라이언트 접속을 허용하도록 제작하였고 클라이언트가 접속을 하면 Thread를 생성하여 개별 클라이언트와 통신하도록 구현되어 있습니다.

이번 구현은 클라이언트와 ehco 기능으로 구현되어 있어서 클라이언트에서 서버에 접속 후 메시지를 보내면 클라이언트로 보낸 메시지가 반송되도록  코딩되어 있습니다. 

클라이언트 메시지에 대한 동작 구현은 아래 코드에서 ehco 기능의 함수 부분을 수정하시면 됩니다. 

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

namespace Server
{
    public class ServerEventArgs : EventArgs
    {
        public string text { get; }    // 파라메터로 넘겨 줄 데이타

        public ServerEventArgs(string text)   // 생성자에서 변경된 Text 정보를 넘겨받는다.
        {
            this.text = text;
        }
    }
    //------------------------------------------------------------------------------------------------------------
    public delegate void ServerEventHandler(object sender, ServerEventArgs e);
    //------------------------------------------------------------------------------------------------------------
     public class ClientHandle
    {
        private Socket socket;
        private NetworkStream? networkstream = null;
        private StreamReader? streamReader = null;
        private StreamWriter? streamWriter = null;
        //--------------------------------------------------------------------------------------------------------
        public ClientHandle(Socket client)
        {
            this.socket = client;
            networkstream = new NetworkStream(client);

            streamReader = new StreamReader(networkstream!, Encoding.GetEncoding("utf-8"));
            streamWriter = new StreamWriter(networkstream!, Encoding.GetEncoding("utf-8")) { AutoFlush = true };
        }
        //--------------------------------------------------------------------------------------------------------
        public void Run()
        {
            string? receiveData;        // 데이타 수신이 null 이 될수 있으므로 null 참조 가능으로 선언한다. 

            while (true)
            {
                receiveData = streamReader!.ReadLine();     // NetworkStream 으로 들어오는 데이타의 끝은 CR, LF 가 필요하다. 
                                                            // CR LF 가 없으면 버퍼가 overflow 될때 까지 함수 내부에서 빠져 나오지 못한다. 
                if (receiveData is null) break;             // Client 연결이 끊어지면 null 이 리턴된다. 

                WriteClientData(receiveData);  // Echo 기능 입니다. 다은 특별한 동작이 필요하면 이코드를 삭제하고 구현하시면 됩니다. 
            }

            DisConnect();       // Client 연결과 관련된 부분을 모두 닫아 버린다. 
        }
        //--------------------------------------------------------------------------------------------------------
        public void DisConnect()
        {
            streamReader?.Close();
            streamWriter?.Close();
            networkstream?.Close();

            socket.Close();
        }
        //--------------------------------------------------------------------------------------------------------
        public void WriteClientData(string Message)
        {
            streamWriter?.WriteLine(Message);
        }
        //--------------------------------------------------------------------------------------------------------
    }
    //------------------------------------------------------------------------------------------------------------
    public class MultiClientServer
    {
        private TcpListener? server = null;
        public event ServerEventHandler? OnClientConnected = null;
        public event ServerEventHandler? OnServerClosed = null;

        public string? IpAddress { set; get; } = null;
        public int Port { set; get; } = 5000;
        //--------------------------------------------------------------------------------------------------------
        public MultiClientServer()
        {
            this.IpAddress = GetLocalIP();
            Enable();
        }
        //--------------------------------------------------------------------------------------------------------
        public MultiClientServer(int port)
        {
            this.IpAddress = GetLocalIP();
            this.Port = port;

            Enable();
        }
        //--------------------------------------------------------------------------------------------------------
        private static string GetLocalIP()
        {
            IPHostEntry host = Dns.GetHostEntry(Dns.GetHostName());
            string ip = string.Empty;

            foreach (var address in host.AddressList)
            {
                if (address.AddressFamily == AddressFamily.InterNetwork)
                {
                    ip = address.ToString();
                    break;
                }
            }

            return ip;
        }
        //--------------------------------------------------------------------------------------------------------
        public bool Enable()
        {
            if (this.IpAddress == null) { return false; }
            else
            {
                IPEndPoint local = new IPEndPoint(IPAddress.Parse(IpAddress), Port);
                server = new TcpListener(local);

                return true;
            }
        }
        //--------------------------------------------------------------------------------------------------------
        public void Stop()
        {
            server?.Stop();     // server 동작을 종료한다. 
        }
        //--------------------------------------------------------------------------------------------------------
        public void Start()
        {
            if (server is not null)
            {
                server.Start();              // Server 동작             

                Thread connect = new Thread(WaitClientConnect);  // Client 접속 대기, Data 수신
                connect.Start();         
            }
        }
        //--------------------------------------------------------------------------------------------------------
        private void WaitClientConnect()
        {
            while (true)
            {
                try
                {
                    Socket? client = server?.AcceptSocket();   // Client  접속을 기다린다. 

                    if (client is not null)
                    {
                        var ipClient = ((IPEndPoint)client.RemoteEndPoint!).ToString();  // 접속된 Client의 IP 확인 

                        if (OnClientConnected is not null) OnClientConnected(this, new ServerEventArgs(ipClient));   // Client 연결에 대한 이벤트 함수 실행 

                        Thread connectedClient = new(new ThreadStart(new ClientHandle(client).Run));  // 접속된 Client 의 객체를 생성 Thread 를 사용하여 동작 수행
                        connectedClient.Start();
                    }
                }
                catch(Exception ex)         // server 기능을 stop 하게되면 예외가 발생한다. 
                {
                    if(OnServerClosed is not null)
                    {
                        OnServerClosed(this, new ServerEventArgs(ex.ToString()));  // server 기능 멈충에 대한 이벤트 발생 
                    }

                    break;
                }
            }
        }
        //--------------------------------------------------------------------------------------------------------
    }
}

 

사용방법    

public partial class Form1 : Form
{
    private MultiClientServer server = new MultiClientServer(5000) ; //로컬 IP는 자동으로 찾고 Port 5000 으로 서버 생성

    public Form1()
    {
        InitializeComponent();
        server.OnClientConnected += Server_OnClientConnected;  // 클라이언트가 접속되었을 경우 발생하는 이벤트
    }

    private void Server_OnClientConnected(object sender, ServerEventArgs e)
    {
      // 클라이언트가 연결되면 클라이언트 IP 를 화면에 보여 줍니다.
        textClientIP.Invoke(() => { textClientIP.Text = e.text; });  
    }

    private void button1_Click(object sender, EventArgs e)
    {
        server.Start();  // 서버 동작 시작 
    }
    private void button3_Click(object sender, EventArgs e)
    {
        server.Stop();  // 서버 동작 종료
    }
}
반응형

댓글()