Programming/C#

[네트워크 프로그래밍] TCP/IP 예제

lee308812 2019. 2. 27. 22:06

- 서버측에서의 Binding까지는 UDP 서버 소켓 사용하는 방법과 동일함.


- TCP/IP 통신을 할 때 Socket의 생성자는 아래와 같이 지정한다.

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


- TCP/IP 서버는 아래와 같이 동작함.


Binding -> Listen(연결 받을 수 있는 상태) -> Accept(클라이언트 접속 큐에서 하나 꺼내옴)


- TCP/IP에서는 Send / Receive를, UDP에서는 SendTo / ReceiveFrom을 사용하여야 한다. 이미 Accept로 클라이언트 소켓 정보를 알고있으므로, Send / Receive에는 접점 정보를 알아내기 위한 IPEndPoint 인자가 없다. 


[ TCP 서버측 소켓 ] 

    private static void serverFunc(object obj)
    {
        using (Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            IPEndPoint clientEP = new IPEndPoint(IPAddress.Any, 10200);

            serverSocket.Bind(clientEP);

            serverSocket.Listen(10); // 10 : 클라이언트 연결 큐 크기

            while(true)
            {
                Socket clientSocket = serverSocket.Accept();

                byte[] receiveByte = new byte[1024];

                int nRecv = clientSocket.Receive(receiveByte);
                string txt = Encoding.UTF8.GetString(receiveByte, 0, nRecv);

                byte[] sendByte = Encoding.UTF8.GetBytes("Hello:" + txt);

                clientSocket.Send(sendByte);
                clientSocket.Close(); // Close 꼭 해줄 것.
            }
        }
    }

[ TCP 클라이언트측 소켓 ] 


- UDP와의 차이점은, Connect 함수가 추가된다는 것.

private static void clientFunc(object obj)
    {
        using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            EndPoint serverEP = new IPEndPoint(IPAddress.Loopback, 10200);

            socket.Connect(serverEP); // TCP/IP에서, Client가 Server 연결시 필요

            byte[] buf = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
            socket.Send(buf);

            byte[] receiveBytes = new byte[1024];
            int nRecv = socket.Receive(receiveBytes);
            string txt = Encoding.UTF8.GetString(receiveBytes, 0, nRecv);

            Console.WriteLine(txt);
        }

        Console.WriteLine("TCP/IP Client Closed.");
    }


[ TCP 서버 개선 - 다중 스레드와 비동기 통신 ]


- 그러나 이러한 방식은 Send/Receive에서 blocking되므로 빠르게 Accept 할 수 없다는 단점이 있다. 개선을 위해, Accept로 받은 클라이언트 처리를 별도의 스레드에 위임할 수 있다. 클라이언트 하나 당 하나의 스레드가 필요하므로 부하가 가중될 수도 있다.

    private static void serverFunc(object obj)
    {
        using (Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            EndPoint clientEP = new IPEndPoint(IPAddress.Any, 10200);
            serverSocket.Bind(clientEP);
            serverSocket.Listen(10);

            while (true)
            {
                Socket clientSocket = serverSocket.Accept();

                ThreadPool.QueueUserWorkItem(clientSocketProcess, clientSocket);
            }
        }
    }

    private static void clientSocketProcess(object state)
    {
        Socket clientSocket = state as Socket;

        byte[] receiveBytes = new byte[1024];
        int nRecv = clientSocket.Receive(receiveBytes);

        string txt = Encoding.UTF8.GetString(receiveBytes, 0, nRecv);

        clientSocket.Send(Encoding.UTF8.GetBytes(txt));
        clientSocket.Close();
    }


- 위 단점은 비동기 통신으로 해결할 수 있다.

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

class Program
{
    static void Main(string[] args)
    {
        Thread serverThread = new Thread(serverFunc);
        serverThread.IsBackground = true;
        serverThread.Start();
        Thread.Sleep(500); // 소켓 서버용 스레드가 실행될 시간을 주기 위해

        Thread clientThread = new Thread(clientFunc);
        clientThread.IsBackground = true;
        clientThread.Start();

        Console.WriteLine("종료하려면 아무키나 누르세요.");
        Console.ReadLine();
    }

    public class AsyncStateData
    {
        public byte[] buffer;
        public Socket socket;
    }

    private static void serverFunc(object obj)
    {
        using (Socket srvSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Any, 10200);
            srvSocket.Bind(endPoint);
            srvSocket.Listen(10);

            while(true)
            {
                Socket clientSocket = srvSocket.Accept();

                AsyncStateData data = new AsyncStateData();
                data.buffer = new byte[1024];
                data.socket = clientSocket;

                // public IAsyncResult BeginReceive(byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state)
                clientSocket.BeginReceive(data.buffer, 0, data.buffer.Length, SocketFlags.None, asyncReceiveCallback, data);
            }
        }
    }

    public static void asyncReceiveCallback(IAsyncResult asyncResult)
    {
        AsyncStateData receiveData = asyncResult.AsyncState as AsyncStateData;

        int nRecv = receiveData.socket.EndReceive(asyncResult);

        string txt = Encoding.UTF8.GetString(receiveData.buffer, 0, nRecv);

        byte[] sendBytes = Encoding.UTF8.GetBytes("Hello:" + txt);

        receiveData.socket.BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None, asyncSendCallback, receiveData.socket);
    }

    public static void asyncSendCallback(IAsyncResult asyncResult)
    {
        Socket socket = asyncResult.AsyncState as Socket;
        socket.EndSend(asyncResult);

        socket.Close();
    }

    private static void clientFunc(object obj)
    {
        using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
        {
            EndPoint serverEP = new IPEndPoint(IPAddress.Loopback, 10200);

            socket.Connect(serverEP); // TCP/IP에서, Client가 Server 연결시 필요

            byte[] buf = Encoding.UTF8.GetBytes(DateTime.Now.ToString());
            socket.Send(buf);

            byte[] receiveBytes = new byte[1024];
            int nRecv = socket.Receive(receiveBytes);
            string txt = Encoding.UTF8.GetString(receiveBytes, 0, nRecv);

            Console.WriteLine(txt);
        }

        Console.WriteLine("TCP/IP Client Closed.");
    }
}


- 인터넷이 갑자기 끊기거나 등의 상황이 있을 수 있다. Receive/Send를 호출할 때는 try/catch로 SocketException을 처리할 수 있도록 구현해야 한다.


출처 : 시작하세요! C# 7.1 프로그래밍(위키북스, 정성태님 저)

'Programming > C#' 카테고리의 다른 글

MSSQL Database 연동(2)  (0) 2019.03.11
MSSQL Database 연동(1)  (0) 2019.03.05
app.config  (0) 2019.03.05
[네트워크 프로그래밍] Http 통신  (0) 2019.03.02
[네트워크 프로그래밍] UDP 예제  (0) 2019.02.27