C# Socket Programming

선비Sunbei·2023년 2월 11일
0

C#

목록 보기
18/18
post-thumbnail

Server는 Accept용 Socket을 갖고 있다.
해당 Socket을 Bind한 후, Listen 및 Accept를 해서 해당 클라이언트로 보낼 Socket을 얻어내야 한다.
즉, 서버가 해야 할 작업은 다음과 같다.
1. Bind
2. Listen
3. Accept

클라이언트에서의 Socket은 간편하다 Socket 생성 후 Connect만 진행해주면 된다.

// Server.cs
using System;
using System.Net.Sockets;
using System.Net;
using System.Text;

namespace Server
{
    class Program
    {
        static void Main(string[] args)
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7585);
            Socket listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            listenSocket.Bind(endPoint);
            listenSocket.Listen(10);

            while (true)
            {
                Socket clientSocket = listenSocket.Accept();
                byte[] recvBuff = new byte[1024];
                int recvBytes = clientSocket.Receive(recvBuff);
                string recvData = Encoding.UTF8.GetString(recvBuff, 0, recvBytes);
                Console.WriteLine($"Client : {recvData}");

                byte[] sendBuff = Encoding.UTF8.GetBytes(recvData);
                clientSocket.Send(sendBuff);

                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Dispose();
            }

        }
    }
}
// Client.cs
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7585);

            Socket socket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

            socket.Connect(endPoint);
            Console.WriteLine("Connect ...");

            byte[] sendBuff = Encoding.UTF8.GetBytes("Hello World");
            int sendBytes = socket.Send(sendBuff);

            byte[] recvBuff = new byte[1024];
            int recvBytes = socket.Receive(recvBuff);
            string recvData = Encoding.UTF8.GetString(recvBuff,0,recvBytes);
            Console.WriteLine($"Server : {recvData}");

            socket.Shutdown(SocketShutdown.Both);
            socket.Dispose();
        }
    }
}

이러한 방식으로 한 프로그래밍을 블록 소켓 프로그래밍이라고 한다.
이는 정해진 순서대로만 작성할 수 있다는 단점이 존재한다.
그 이유는 Send를 보낼 경우 스레드가 wait상태가 되고 Recieve할 수 없기 때문이다.
이를 해결하기 위해서는 비동기의, 논-블록 소켓으로 만들어야 한다.

비동기를 사용하기 위해서는 SocketAsyncEventArgs를 이용해서 Socket.*Async() 함수를 이용해야 한다.

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

namespace AsyncServer
{
    class Listener
    {
        Socket ListenSocket;
        Action action;
        public void Init(IPEndPoint endPoint, Action action)
        {
            ListenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            this.action += action;

            ListenSocket.Bind(endPoint);

            ListenSocket.Listen(10);

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
            RegisterAccept(args);
        }

        void RegisterAccept(SocketAsyncEventArgs args)
        {
            args.AcceptSocket = null;

            bool pending = ListenSocket.AcceptAsync(args);
            if (pending == false)
                OnAcceptCompleted(null, args);
        }
        
        void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            if(args.SocketError == SocketError.Success)
                action.Invoke();
            else
                Console.WriteLine(args.SocketError.ToString());
            RegisterAccept(args);
        }
    }
}

Async Socket의 Listenr.cs는 다음과 같이 만들 수 있다.
SocketAsyncEventArgs는 System.Net.Sockets.Socket의 비동기 패턴을 제공하는 클래스로 *Async() 함수를 통해 넘겨준다.

Completed는 C#의 이벤트로서 *Async()를 통해서 넘겨준 작업이 끝나면 Invoke 된다.
개인적으로 AcceptAsync(args)의 리턴을 받아서 false이면 OnAcceptCompleted를 호출하는 것이 가장 이해가 안 됐었다.

해당 함수의 반환 값은 다음과 같다.

  • I/O 작업이 보류 중인 경우 true로 반환되고, 작업이 완료되면 매개 변수에 args에 대한 Completed 이벤트가 발생한다.
  • I/O 작업이 동기적으로 완료된 경우 false로 반환된다. 이 경우에는 매개 변수 args에 대한 Completed 이벤트가 발생하지않는다.

다음과 같이 Completed 이벤트가 발생하지 않을 경우 수동적으로 호출해주려는 것이다.

SocketAsyncEventArgs의 몇 가지 속성 및 메서드를 작성하겠다.

  • UserToken은 Object 클래스로 원하는 값을 무엇이든 사용자가 넣을 수 있다.
  • SocketError은 비동기 소켓 작업의 결과를 가져온다. 이 속성은 모든 비동기 소켓 *Async 메서드와 같이 사용된다.
  • Buffer는 비동기 소켓 메서드에 사용할 데이터 버퍼를 나타내는 Byte 배열로 get만 가능하다.
  • SetBuffer : 비동기 소켓 메서드에 사용할 데이터 버퍼를 초기화 시킨다.

0개의 댓글