서버 코드

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using System.Text;

namespace ServerCore
{


    class Program
    {

        

        static Listener _listener = new Listener();


        static void OnAcceptHandler(Socket clientSocket)
        {
            try
            {
                //클라이언트로부터 받아오는 처리
                byte[] recvBuff = new byte[1024];
                int recvBytpes = clientSocket.Receive(recvBuff);
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytpes);
                Console.WriteLine($"[From Client] {recvData}");


                //클라이언트로 보내는 처리
                byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome to server!");
                clientSocket.Send(sendBuff);

                clientSocket.Shutdown(SocketShutdown.Both);     //연결된 소켓 끊기, 듣기와 말하기를 하지 않겠다는 것
                clientSocket.Close();   //클라와 서버간의 연결 끊기
            }
            catch (System.Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        static void Main(string[] args)
        {
            //DNS : Domain Name System
            // 도메인을 하나 등록해서 해당하는 IP 를 찾아오면 관리가 쉬워짐
            //www.google.com => 

            string host = Dns.GetHostName();
            //host = "google.com";            //ipHost.AddressList[0] == {172.217.161.238}
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            //이렇게 GetHostEntry 로 주소를 얻어오는 건 DNS 서버를 통해서 얻어 올 수 있게 됨

            // ipHost.addressList[0] = IPAddress.Parse("        경우에 따라서 ip 주소는 여러개 일 수도 있다 부하 분산을 위해서 addressList
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);     //최종 주소

            
            try
            {

                _listener.Init(endPoint, OnAcceptHandler);
                

                while (true)
                {
                }
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }
}

 

 

리스터 코드 : 비동기 accept

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

namespace ServerCore
{
    class Listener
    {
        Socket _listenSocket;

        Action<Socket> _onAcceptHandler;
        
        public void Init(IPEndPoint iPEndPoint, Action<Socket> onAcceptHandler)
        {
            //AddressFamily ip version 4,6 에 대한 것 , 위에서 자동으로 만들어줌, 
            //tcp 로 할 경우 stream, tcp 로 설정해준다
            //리슨 하는 자체가 소켓을 하나 만들어야 한다
            _listenSocket = new Socket(iPEndPoint.AddressFamily , SocketType.Stream, ProtocolType.Tcp);
            _onAcceptHandler += onAcceptHandler;

            _listenSocket.Bind(iPEndPoint);        //소켓에 ip 와 포트 할당


            //최대 동시 대기 수, 동시에 들어올대 10명까지만 처리 가능하고 그 위로는 실패가 된다
            _listenSocket.Listen(10);

            //이건 한번 사용하고 재사용이 가능하다
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);

            //최초 한번은 등록해준다
            RegisterAccept(args);
            
        }

        void RegisterAccept(SocketAsyncEventArgs args)
        {
            //재사용이 됨으로 null 로 처리한다
            args.AcceptSocket = null;

            bool pending = _listenSocket.AcceptAsync(args);     //비동기 임으로 예약만 하고 넘어간다, accdept 완료는 eventHandler 를 통해서 완료된다
            if (pending == false)   //false 면 pending 없이 바로 완료 됐다는 얘기임
                OnAcceptCompleted(null, args);
        }

        void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            if(args.SocketError == SocketError.Success)
            {
                //accept 되어 새로 생성된 소켓을 
                _onAcceptHandler.Invoke(args.AcceptSocket); //넘겨준다
            }
            else
            {
                Console.WriteLine(args.SocketError.ToString());
            }

            //위에 까지 처리가 된것은 accept 가 완료 된것임으로 새로운 accept 를 위해서
            //RegisterAccept 를 다시 호출하여 OnAcceptCompleted 이벤트를 받아 들을 수 있는 상태로 만든다 
            RegisterAccept(args);
        }

        public Socket Accept()
        {

            //return _listenSocket.Accept();    
            //클라의 접속이 있다면 받아오는 처리, 접속이 있을때까지 계속 대기, 즉 다음으로 넘어가지 않는다
            //클라로부터 접속이 왔다면 accept 되어 클라와 별도 통실한 socket 이 생성되어 리턴된다
            //return _listenSocket.Accept();    

            //async 는 비동기로 처리 된다
            //return _listenSocket.AcceptAsync()
            return null;
        }
    }
}

 

 

더미 클라이언트 코드

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace DummyClient
{
    class Program
    {
        static void Main(string[] args)
        {
            string host = Dns.GetHostName();
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);     
            
            while (true)
            {
                try
                {
                    Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

                    //소켓으로 서버에 연결한다 , 서버 입장에선 accept 가 된다
                    socket.Connect(endPoint);
                    Console.WriteLine($"connected to {socket.RemoteEndPoint.ToString()}");


                    //서버로 보낸다
                    byte[] sendBuff = Encoding.UTF8.GetBytes("Hello world!");
                    int bytesSent = socket.Send(sendBuff);

                    //서버에서 받는다
                    byte[] recvBuff = new byte[1024];
                    int recvBytes = socket.Receive(recvBuff);
                    string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                    Console.WriteLine($"received : {recvData}");

                    //서버와 통신을 위해 생성했던 소켓 종료 처리
                    socket.Shutdown(SocketShutdown.Both);
                    socket.Close();

                }
                catch (System.Exception ex)
                {
                    Console.WriteLine(ex);
                }
                Thread.Sleep(100);
            }
           

        }
    }
}

 

 

AcceptAsync : 비동기 방식으로 클라의 접속을 말한다

AcceptAsync 로 처리하면 서버에서 클라의 접속을 받아 Accept() 함수 처럼 accept 하게 될때 해당 라인에서 무한 대기 하지 않고 클라와 통신하기 위한 전용 socket 을 만들어 리턴하고 서버에선 바로 다음 로직을 처리 할 수 있게 된다

 

bool pending = _listenSocket.AcceptAsync(args);

 

 

결과화면

반응형

'서버(Server) > Server' 카테고리의 다른 글

멀티스레드를 고려한 SendAsync 보내기  (0) 2022.12.24
_socket.ReceiveAsync 비동기 처리  (0) 2022.12.23
기본적인 소켓 프로그래밍  (0) 2022.12.23
소켓 통신  (0) 2022.12.22
통신 모델 OSI 7 계층  (0) 2022.12.22

+ Recent posts