TCP 통신 – Echo 서버 클래스 구현, 이벤트 정의 및 콜백 처리 [C#]

안녕하세요. 언제나 휴일에 언휴예요.

이번 실습은 Echo 서버 클래스를 구현하는 실습이예요.

여기서 만들 서버 클래스는 라이브러리로 제작하기 위한 목적으로 사전 작업하는 것이예요.

서버 클래스를 사용하는 곳에서 연결 수락 및 닫기, 메시지 수신에 관한 이벤트 처리를 할 수 있게 정의합니다. 대리자 및 이벤트 인자를 정의하고 Echo 서버에 이벤트 멤버를 정의하여 콜백 처리를 하는 것이죠.

1. 연결 수락 이벤트 인자 및 대리자 소스

using System;
using System.Net;

namespace 에코_서버_응용___클래스로_정의
{
    public delegate void AcceptedEventHandler(object sender, AcceptedEventArgs e);
    public class AcceptedEventArgs:EventArgs
    {
        public IPEndPoint RemoteEP
        {
            get;
            private set;
        }
        public string IPStr
        {
            get
            {
                return RemoteEP.Address.ToString();
            }
        }
        public int Port
        {
            get
            {
                return RemoteEP.Port;
            }
        }
        public AcceptedEventArgs(IPEndPoint remote_ep)
        {
            RemoteEP = remote_ep;
        }
    }
}

2. 연결 닫기 이벤트 인자 및 대리자 소스

using System;
using System.Net;

namespace 에코_서버_응용___클래스로_정의
{
    public delegate void ClosedEventHandler(object sender, ClosedEventArgs e);
    public class ClosedEventArgs:EventArgs
    {
        public IPEndPoint RemoteEP
        {
            get;
            private set;
        }
        public string IPStr
        {
            get
            {
                return RemoteEP.Address.ToString();
            }
        }
        public int Port
        {
            get
            {
                return RemoteEP.Port;
            }
        }
        public ClosedEventArgs(IPEndPoint remote_ep)
        {
            RemoteEP = remote_ep;
        }
    }
}

3. 메시지 수신 이벤트 인자 및 대리자 소스

using System;
using System.Net;

namespace 에코_서버_응용___클래스로_정의
{
    public delegate void RecvedMsgEventHandler(object sender, RecvedMsgEventArgs e);
    public class RecvedMsgEventArgs:EventArgs
    {
        public IPEndPoint RemoteEP
        {
            get;
            private set;
        }
        public string IPStr
        {
            get
            {
                return RemoteEP.Address.ToString();
            }
        }
        public int Port
        {
            get
            {
                return RemoteEP.Port;
            }
        }
        public string Msg
        {
            get;
            private set;
        }
        public RecvedMsgEventArgs(IPEndPoint remote_ep,string msg)
        {
            RemoteEP = remote_ep;
            Msg = msg;
        }
    }
}

4. Echo 서버 클래스 소스

using System.IO;
using System.Net;
using System.Net.Sockets;

namespace 에코_서버_응용___클래스로_정의
{
    public class EchoServer
    {
        public event AcceptedEventHandler AcceptedEventHandler = null;
        public event ClosedEventHandler ClosedEventHandler = null;
        public event RecvedMsgEventHandler RecvedMsgEventHandler = null;
        public string IPStr
        {
            get;
            private set;
        }
        public int Port
        {
            get;
            private set;
        }

        public EchoServer(string ipstr, int port)
        {
            IPStr = ipstr;
            Port = port;
        }

        Socket sock = null;
        public bool Start()
        {
            try
            {
                sock = new Socket(
                    AddressFamily.InterNetwork,
                    SocketType.Stream,
                    ProtocolType.Tcp
                    );//소켓 생성

                //인터페이스와 결합
                IPAddress addr = IPAddress.Parse(IPStr);
                IPEndPoint iep = new IPEndPoint(addr, Port);
                sock.Bind(iep);

                //백로그 큐 크기 설정
                sock.Listen(5);
                AcceptLoopAsyn();
                return true;
            }
            catch
            {
                return false;
            }
        }
        public void Close()
        {
            if (sock != null)
            {
                try
                {
                    sock.Close();
                }
                catch
                {
                }
            }
        }

        delegate void AcceptDele();
        private void AcceptLoopAsyn()
        {
            AcceptDele dele = AcceptLoop;
            dele.BeginInvoke(null, null);
        }
        void AcceptLoop()
        {
            Socket dosock = null;
            while (true)
            {
                dosock = sock.Accept();
                DoItAsync(dosock);
            }
        }

        delegate void DoItDele(Socket dosock);
        private void DoItAsync(Socket dosock)
        {
            DoItDele dele = DoIt;
            dele.BeginInvoke(dosock, null, null);
        }
        private void DoIt(Socket dosock)
        {
            IPEndPoint remote_ep = dosock.RemoteEndPoint as IPEndPoint;
            if(AcceptedEventHandler != null)
            {
                AcceptedEventHandler(this, new AcceptedEventArgs(remote_ep));
            }

            try
            {
                byte[] packet = new byte[1024];                
                while (true)
                {
                    dosock.Receive(packet);
                    MemoryStream ms = new MemoryStream(packet);
                    BinaryReader br = new BinaryReader(ms);
                    string msg = br.ReadString();
                    br.Close();
                    ms.Close();
                    if(RecvedMsgEventHandler != null)
                    {
                        RecvedMsgEventHandler(this, new RecvedMsgEventArgs(remote_ep, msg));
                    }                    
                    dosock.Send(packet);
                }
            }
            catch
            {

            }
            finally
            {                
                dosock.Close();
                if (ClosedEventHandler != null)
                {
                    ClosedEventHandler(this, new ClosedEventArgs(remote_ep));
                }
            }
        }

    }
}

5. Program. cs

using System;

namespace 에코_서버_응용___클래스로_정의
{
    class Program
    {
        static void Main(string[] args)
        {
            EchoServer es = new EchoServer([서버 IP 주소], 서버 Port);
            es.RecvedMsgEventHandler += Es_RecvedMsgEventHandler;
            es.AcceptedEventHandler += Es_AcceptedEventHandler;
            es.ClosedEventHandler += Es_ClosedEventHandler;
            if(es.Start()==false)
            {
                Console.WriteLine("서버 가동 실패");
                return;
            }
            Console.ReadKey();
            es.Close();
        }

        private static void Es_ClosedEventHandler(object sender, ClosedEventArgs e)
        {
            Console.WriteLine("{0}:{1}에서 연결을 닫음",e.IPStr,e.Port);
        }

        private static void Es_AcceptedEventHandler(object sender, AcceptedEventArgs e)
        {
            Console.WriteLine("{0}:{1}에서 연결 했음", e.IPStr, e.Port);
        }

        private static void Es_RecvedMsgEventHandler(object sender, RecvedMsgEventArgs e)
        {
            Console.WriteLine("{0}:{1} →{2}", e.IPStr, e.Port,e.Msg);
        }
    }
}

6. Echo 클라이언트 측 Program.cs

다음은 이전 강의에서 작성했던 Echo 클라이언트의 소스 코드입니다.

이번 실습에서는 별도의 클라이언트를 제작하지 않고 이전 강의에서 작성한 것을 사용합니다.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace 에코_클라이언트_응용
{
    class Program
    {
        static void Main(string[] args)
        {           
            Socket sock  = new Socket(
                    AddressFamily.InterNetwork,
                    SocketType.Stream,
                    ProtocolType.Tcp
                    );//소켓 생성
            //인터페이스 결합(옵션)
            //연결
            IPAddress addr = IPAddress.Parse([서버 IP 주소]);
            IPEndPoint iep = new IPEndPoint(addr, [서버 Port 번호]);
            sock.Connect(iep);
            string str;
            string str2;
            byte[] packet = new byte[1024];
            byte[] packet2 = new byte[1024];
            while (true)
            {
                Console.Write("전송할 메시지:");
                str = Console.ReadLine();
                MemoryStream ms = new MemoryStream(packet);
                BinaryWriter bw = new BinaryWriter(ms);
                bw.Write(str);
                bw.Close();
                ms.Close();
                sock.Send(packet);
                if(str == "exit")
                {
                    break;
                }
                sock.Receive(packet2);
                MemoryStream ms2 = new MemoryStream(packet2);
                BinaryReader br = new BinaryReader(ms2);
                str2 = br.ReadString();
                Console.WriteLine("수신한 메시지:{0}", str2);
                br.Close();
                ms2.Close();
            }
            sock.Close();//소켓 닫기
        }
    }
}