Written

소켓 프로그래밍 #2 본문

Server/소켓

소켓 프로그래밍 #2

steeringhead 2023. 7. 13. 16:12
비동기로 구현하기

 

 

사실상 bloking 방식의 코드가 실제로 현업에서 쓰일일은 거의 없다고 봐도 무방하다. 

#1은 단순히 소켓 통신이 무엇인지에 대한 이해를 위한 코드였고, 실제로는 언제 통신이 발생할지 모르기 때문에 non-blocking 계열의 함수들을 사용하여 소켓 통신을 구현해야한다.

 

=> Listener라는 클래스를 새로 만들어 코드의 분리 시작. 서버 측 소켓의 동작들을 비동기 로직을 포함하여 구현해둔 클래스

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Listener
    {
        //소켓 생성 코드를 분리시키기 위한 작업
        Socket _listenSocket;
        //Action은 반환값이 없는 인자만 존재하는 delegate
        Action<Socket> _onAcceptHandler; 
 
        //소켓 생성을 위해 EndPoint가 필요하기 때문에 인자로 받는다
        public void Init(IPEndPoint endPoint, Action<Socket> onAcceptHandler)
        {
            _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            _onAcceptHandler += onAcceptHandler;
            //소켓을 생성했으니 EndPoint와 연결(Bind) + 대기(Listen)진행
            _listenSocket.Bind(endPoint);
            _listenSocket.Listen(10);
 
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
            RegisterAccept(args);
        }
 
        //비동기 방식으로 Accept를 처리해줘야함. Blocking방식은 한계가 많음.
        //비동기 방식으로 Accept를 완료했을 때, 콜백함수를 실행시켜 그 후의 동작들을 설계하자.
        //그렇다면 SocketAsyncEventArgs는 무엇이냐 ? -> 비동기 방식으로 동작하는 소켓의 로직들을 이벤트 형식으로 사용할수 있게 도와주는 클래스 
        public void RegisterAccept(SocketAsyncEventArgs args)
        {
            //null로 밀어주는 이유는 그전의 통신으로 인해 AcceptSocket에 데이터가 이미 사용했던 저장되어 있기 때문!
            args.AcceptSocket = null;
 
                       
            bool pending = _listenSocket.AcceptAsync(args);
            if (pending == false//pending이 true면 대기하다 Accept가 실행될때,이벤트를 통해 콜백을 실행시켜주겠단 뜻
            {
                OnAcceptCompleted(null,args);
            }
        }
 
        public void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            //connect과 Accept가 성공했을 때, 하고 싶은 동작들을 여기에 구현하는것.
            if(args.SocketError == SocketError.Success)
            {
                //TODO
                //여기선 뭘해야하냐 -> Accept까지 이루어졌으니 Recv Send의 과정이 들어가야겠지.
                //Recv와 Send를 어떻게 구현할지 생각해보기.
 
                //onAcceptHandler에 Accept이후 해야 할 것들을 담아두고, 여기서 Invoke를 통해 동작시키기.
                _onAcceptHandler.Invoke(args.AcceptSocket); //onAcceptHandler가 Socket을 인자로 받으니까 AcceptSocket으로 Socket을 뱉어주고 있다.
 
 
            }
            else
            {
                Console.WriteLine(args.SocketError.ToString());
            }
            //하나의 통신이 성공했으니, 다음 통신을 위해 Accept를 다시 예약해줌.
            RegisterAccept(args);
        }
        
 
    }
cs

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
static Listener _listener = new Listener();
        static void OnAcceptHandler(Socket clientSocket)
        {
            try
            {
                //받기와 보내기 Recv와 Send
                //받으려면 받아낼 바구니가 필요함. 그 바구니가 RecvBuff다!                
                //Receive함수가 int를 반환한다 -> 몇 바이트를 받았는지 리턴해줌.
                byte[] recvBuff = new byte[1024];
                int recvBytes = clientSocket.Receive(recvBuff);
 
                //클라에서 보낸 문자열 데이터가 recvBuff에 들어갈때 byte 배열로 들어갔는데, 그걸 다시 문자열로 복원해주는 함수
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"[From Client : {recvData}");
 
                //보낸다 -> 문자열을 Bytes로 변환해주는 함수를 통해 sendBuff에 저장함
                byte[] sendBuff = Encoding.UTF8.GetBytes("Welcome MMO SERVER");
                clientSocket.Send(sendBuff);
 
                //연결 종료.
                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
            }
        }
       
        static void Main(string[] args)
        {
            
            //Socket은 인자를 넘겨줘야함. IP주소 , TCP인지UDP인지 정해줘야함
            //Dns사용 
            string host = Dns.GetHostName(); //내 IP 추출
            IPHostEntry ipHost = Dns.GetHostEntry(host);
            IPAddress ipAddr = ipHost.AddressList[0];
            IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
 
            _listener.Init(endPoint,OnAcceptHandler);
            Console.WriteLine("Listening...");
 
 
            while (true)
            {
                ;
            }
 
            
 
        }
cs

 

 

'Server > 소켓' 카테고리의 다른 글

소켓 프로그래밍 #1  (0) 2023.07.12
Comments