[C#] TCP 서버란? 코드 작성하면서 이해하기

Arthur·2023년 6월 17일
0
post-thumbnail

공부하게 된 계기


TCP/IP라는 단어를 네트워크 관련 책을 공부하면서 꼭 한번 들어본 키워드입니다.
많이 들어는 봤지만 TCP/IP관련 코드를 직접 구현해 본 적이 없습니다.
아무래도 웹 개발을 하면서 HTTP 통신 위주로만 공부를 해서 그런 것 같습니다.

이번에 게임 서버 프로그래머를 준비하면서 TCP/IP 소켓 프로그래밍을 사용해 서버를 구현했습니다.

게임 서버를 구축하는 과정에서 배운 개념과 내용을 글과 코드로 작성해봤습니다.


소켓 프로그래밍이란?


컴퓨터 네트워크에서 통신하는 데 사용되는 소프트웨어 개발 기술이며, 주로 인터넷을 통한 통신에 사용됩니다. 이 기술은 소켓(Socket)을 이용하여 데이터를 주고받는 방식으로 작동합니다.

소켓 프로그래밍에서 알아야 할 기본적인 키워드는 5 가지 입니다.

  • 소켓(Socket)
  • 서버(Server)와 클라이언트(Client)
  • TCP와 UDP

소켓(Socket)이란?

네트워크 통신을 위한 종단점(endpoint)으로, 소켓을 통해 데이터를 읽고 쓸 수 있습니다.
소켓은 주로 IP 주소와 포트 번호의 조합으로 식별됩니다.



서버(Server)와 클라이언트(Client)

  • 서버(Server)
    네트워크를 통해 클라이언트 요청에 응답하는 프로그램 또는 컴퓨터입니다.
    서버는 특정 포트에서 클라이언트의 연결을 받아들이고, 요청에 따라 응답합니다.

  • 클라이언트(Client)
    서버에 연결하여 특정 서비스를 요청하는 프로그램 또는 컴퓨터입니다.
    클라이언트는 서버에 요청을 보내고, 서버로부터 응답을 받습니다.



소켓 통신의 종류 : TCP와 UDP

  • TCP(Transmission Control Protocol)
    연결 지향적인 프로토콜로, 데이터의 신뢰성을 보장하며 순서를 유지합니다.
    TCP 소켓은 클라이언트와 서버 간에 신뢰성 있는 양방향 통신을 제공합니다.

  • UDP(User Datagram Protocol)
    연결이 없는 프로토콜로, 데이터 전송에 대한 신뢰성은 낮지만 빠른 속도를 제공합니다.
    UDP 소켓은 데이터를 패킷 단위로 전송합니다.



TCP/IP란?


TCP/IP는 컴퓨터 네트워크에서 데이터 통신을 위한 프로토콜 스택이며, 인터넷을 비롯한 많은 네트워크에서 사용됩니다. 데이터를 안정적으로 전송하기 위한 표준 프로토콜이며, 주로 클라이언트와 서버 간의 통신에 사용됩니다.

  • TCP는 근거리 통신망이나 인트라넷, 인터넷에 연결된 컴퓨터에서 실행되는 프로그램 간에 일련의 옥텟을 안정적으로, 순서대로, 에러 없이 교환할 수 있게 한다.
  • TCP는 OSI 7 Layer에서 전공 계층(Transport layer)에 위치한다.
  • 웹 브라우저들이 월드 와이드 웹에서 서버에 연결할 때 사용되며, 이메일 전송이나 파일 전송에도 사용된다.


TCP 프로토콜의 작동 흐름


TCP 프로토콜의 작동은 크게 세 가지 흐름으로 구분한다.

  1. 연결 생성 (Connection establishment)
    • 연결을 생성하기 위해, 3 Way-Handshake
  2. 자료 전송 (Data transfer)
  3. 연결 종료 (Connection termination)
    • 연결을 종료하기 위해, 4 Way-Handshake

3 Way-Handshake

  • 데이터를 주고 받는 기기 간에 교환이 이뤄지기 전에 동기화 필요
  • TCP 클라이언트와 TCP 서버가 커넥션을 통해 데이터를 전송한 준비를 보장
  • 커넥션을 확립하기 위해 동기화 목적의 패킷을 교환
    - SYN(synchronize sequence numbers; 일련번호를 일치시키다)
    • ACK(acknowledgement; 응답을 확인하다)
  • 커넥션을 위한 두 개의 패킷 정보는 비트로 이루어져 있으며 TCP 세그먼트 헤더에서 관리

4 Way-Handshake



C# 코드로 직접 작성해보기


소켓 서버(Socket Server)

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

namespace BasicSocketServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Basic TCP Server");
            StartListening();
        }

        public static void StartListening()
        {
            // (1) 소켓 객체 생성 (TCP 소켓)
            Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                // (2) 엔드포인트에 소켓 바인드(Bind)
                IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7000);
                listener.Bind(localEndPoint);

                // (3) Listen소켓의 대기열의 길이를 설정합니다.
                listener.Listen(10);

                Console.WriteLine("Waiting for a connection...");

                // (4) 연결을 받아들여 새 소켓 생성
                Socket handler = listener.Accept();

                // 클라이언트로부터 데이터를 받기 위한 버퍼(Buffer) 바이트 배열 초기화
                byte[] bytes = new byte[8192];

                Console.WriteLine("Client Connected...");

                while (true)   // 키 누르면 종료
                {

                    // (5) 소켓 수신(Receive)
                    int bytesRec = handler.Receive(bytes);

					// 클라이언트로부터 받은 데이터를 UTF8 string으로 인코딩합니다
                    string data = Encoding.UTF8.GetString(bytes, 0, bytesRec);
                    Console.WriteLine("Text received : {0}", data);

                    if (data.Equals("Quit"))
                    {
                        Console.WriteLine("Client Disconnected");
                        break;
                    }

                    // (6) 소켓 송신
                    handler.Send(bytes, 0, bytesRec, SocketFlags.None);  // echo
                }

                // (7) 소켓 닫기
                handler.Close();
                listener.Close();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }

            Console.WriteLine("\nPress ENTER to continue...");
            Console.Read();
        }
    }
}


소켓 클라이언트(Socket Client)

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

namespace BasicSocketClient
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Baisc TCP Client");
            StartClient();
        }

        private static void StartClient()
        {
            // (1) 소켓 객체 생성 (TCP 소켓)
            Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            try
            {
                // (2) 서버의 엔드포인트 값을 객체를 생성하고 서버에 연결 시도
                IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 7000);
                sender.Connect(remoteEP);

                string cmd = string.Empty;
                // 서버로부터 데이터를 받기 위한 버퍼(Buffer) 바이트 배열 초기화
                byte[] receiverBuff = new byte[8192];

                Console.WriteLine("Connected... Enter Quit to exit");

                while (true)
                {
                	// 서버에 보낼 문자열 데이터를 콘솔에 입력
                    cmd = Console.ReadLine();
                    // 콘솔에 입력된 문자열을 바이트 배열로 인코딩
                    byte[] buff = Encoding.UTF8.GetBytes(cmd);

                    // (3) 서버에 데이터 전송(Send)
                    int bytesSent = sender.Send(buff);

                    // (4) 서버에서 데이터 수신(Receive)
                    int bytesRec = sender.Receive(receiverBuff);

					// 서버에서 받은 데이터 UTF8로 인코딩해서 콘솔에 출력
                    Console.WriteLine("Echoed test = {0}", Encoding.UTF8.GetString(receiverBuff, 0, bytesRec));

					// 콘솔에 Quit를 입력 시 반복문 종료
                    if (cmd.Equals("Quit"))
                    {
                        break;
                    }
                }

                // (5) 소켓 닫기
                sender.Shutdown(SocketShutdown.Both);
                sender.Close();
            }
            catch (ArgumentNullException ane)
            {
                Console.WriteLine("ArgumentNullException : {0}", ane.ToString());
            }
            catch (SocketException se)
            {
                Console.WriteLine("SocketException : {0}", se.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine("Unexpected exception : {0}", ex.ToString());
            }
        }
    }
}


참고자료


  • csharpstudy C# 네트워크 Socket 서버 => 링크
  • csharpstudy C# 네트워크 Socket 클라이언트 => 링크
  • MS Docs - 소켓을 사용하여 TCP를 통해 데이터 보내기 및 받기 => 링크
  • 블로그 <개발자를 위한 레시피> - 소켓 프로그래밍. (Socket Programming) => 링크
  • 깃허브 레포 - edu_com2us_CSharpNetworkProgramming => 링크
  • 위키피디아 - 전송 제어 프로토콜(TCP) => 링크
  • Tecoble - TCP/IP => 링크
profile
기술에 대한 고민과 배운 것을 회고하는 게임 서버 개발자의 블로그입니다.

0개의 댓글