본문 바로가기
게임개발/게임서버

네트워크 기초

by do_ng 2024. 4. 22.

네트워크란?

노드(네트워크에 속한 컴퓨터 또는 통신장비)들이 데이터를 주고받을 수 있도록 디지털 전기 통신망으로 연결한 것

 

인터넷

전 세계를 연결하는 범위가 큰 네트워크

ex) www -> 인터넷을 통해 웹과 관련된 데이터를 공유하는 것

 

네트워크 범위

1. LAN(근거리 통신망)

같은 네트워크 대역에 있는 노드들끼리 통신

 

2. WAN 

네트워크 대역이 다른 노드들이 통신할 때 통신 범위를 넓혀서 네트워크 대역이 다르더라도 통신이 가능하게 만듬

 

ex) 집에서 컴퓨터로 구글 웹사이트를 들어갔을 때 네트워크 상에서는 다음과 같이 표시된다.

 

 

네트워크 통신 방식

1. 유니 캐스트

1:1로 통신하는 방식(특정한 하나의 대상과의 통신)

 

2. 멀티 캐스트

같은 네트워크 대역에서 특정한 다수를 대상으로 통신

 

3. 브로드 캐스트

같은 네트워크 대역에 있는 모든 노드를 대상으로 통신

 

노드가 통신하기 위해서 특정 노드를 찾아내는 방법

상황에 맞는 프로토콜을 이용해서 통신한다. 

 

1. 가까운 곳 (이더넷, Mac)

2. 멀리 있는 곳 (IP 주소, ARP)

3. 특정 프로그램과 통신 (TCP, UDP)

 

 

패킷

네트워크 상에서 전달되는 데이터를 통칭하는 말

보통 헤더와 페이로드 형태로 패킷을 보내며 페이로드에는 보내고자 하는 데이터가 들어가 있다.

 

데이터를 패킷이라는 작은 조각으로 쪼갠 뒤 주고받는데 이렇게 패킷으로 쪼개는 이유는 만약 무거운 데이터를 통으로 보낸다면 전송 도중에 회선에 문제가 생길 경우 다시 데이터를 보내야 되는데 무거운 데이터의 경우 많은 리소스를 낭비할 수 수 있다.

이렇게 패킷 단위로 전송하면, 각 패킷은 다양한 경로를 사용하여 이동하고 중간에 회선이 끊켜도 다른 패킷들은 안정적으로 전송이 된다.

다만 문제는 데이터를 패킷으로 보낼때 모든 패킷이 정상적으로 도착했는지, 순서대로 도착했는지 확인하기 힘들기 때문에 "TCP" 라는 프로토콜을 이용해서 패킷의 순서를 맞추거나 빠진 패킷이 있는지 체크할 수 있다.

 

참고 : https://brunch.co.kr/@swimjiy/35

 

패킷을 보내는 과정)

클라이언트가 네이버 웹툰을 요청한다고 가정했을 때 다음과 같이 네이버 서버로 패킷을 보낸다.

 

1. 응용계층에서 TCP(전송 계층)으로 페이로드를 넘김

2. TCP에서는 응용계층에서 건내받은 페이로드에 헤더를 추가하고 IP(네트워크 계층)으로 넘김

3. IP에서는 TCP에서 건네받은 "헤더 + 페이로드"를 페이로드로 삼아서 그 앞에 다시 헤더를 붙이고 이더넷(데이터 링크 계층)으로 넘김

4. 이더넷에서는 IP에서 건네받은 "헤더 + 페이로드"를 페이로드로 삼아서 그 앞에 이더넷이라는 헤더를 붙인 후 패킷을 보냄

 

 

패킷을 받았을 때 확인하는 과정)

 

하위 프로토콜 부터 하나씩 까보면서 확인

ex) 어떤 노드가 보냈는지?(2계층) -> 어떤 네트워크 대역에서 보냈는지?(3계층) -> 어떤 프로그램을 요청했는지?(7계층) -> 그 프로그램에서 어떤 데이터를 요청했는지?

 

프로토콜 데이터 단위(PDU)

패킷을 구성하는 헤더와 페이로드는 계층에 따라 바뀌기 때문에 계층마다 패킷을 지칭하는 용어가 따로 존재함

"패킷"이라는 용어가 네트워킹에서 계층에 상관없이 통용적으로 쓰이는 말이지만, 어떤 네트워크 계층에서 발생하는 패킷인지를 구체적으로 지칭하는 용어를 PDU라고 한다.

 

 

참고 : https://arcadia.tistory.com/28

 

OSI 계층별 프로토콜

2계층

 

2계층은 같은 네트워크(LAN) 대역에 있는 노드들끼리 통신할 때 사용한다. 

다른 네트워크 대역에 있는 노드와 통신하기 위해서는 3계층이 필요함

 

 

 

 

 

 

 

 

2계층에서 사용하는 주소로는 Mac 주소가 있다.

Mac 주소는 모든 통신 장비들이 각자 다르게 가지고 있는 하드웨어 상의 물리적인 주소

 

 

이더넷 프로토콜

통신 대상 Mac 주소 : "6C-29-95 04-AB-A1"

16진수를 이루는 숫자 하나당 이진수로 표현하기 위해서는 4bit(2의 4승)가 필요함

Destination Address가 차지하는 영역은 총 6byte(48bit)가 되고 Mac주소가 그에 알맞게 들어간다.

Source Address는 통신을 요청한 Mac 주소인 6byte가 들어간다. 

 

받는 노드의 Mac 주소(6byte) + 보내는 노드의 Mac 주소(6byte) + 이더넷 타입(2byte) + Data(2byte)

 

만약에 패킷을 3계층 프로토콜인 IP 주소로 보내야 되는 경우 DATA 안에 IP 프로토콜이 있기 때문에 이더넷 타입에서는 DATA 안에 IP 프로토콜이 있다는 것을 알려줘야 한다.

데이터를 받는 쪽에서는 이더넷 프로토콜을 확인했는데 그 다음 상위 프로토콜을 알지 못하면 최종 데이터를 확인하지 못하기 때문에 상위 프로토콜이 있다면 무조건 알려주는 Flag가 있어야 한다.

 

 

4계층

송신자의 프로세스와 수신자의 프로세스를 연결하는 통신 서비스를 제공

쉽게 말하자면 영수와 영호가 카톡을 주고받기 위해서 영수의 카톡 프로세스와 영호의 카톡 프로세스간의 연결이 되야지 통신이 가능해진다.

 

포트번호

프로세스와 프로세스간의 통신을 하기 위해서 사용함

내 컴퓨터(포트번호가 중복될 수 없는 정확한 범위는 어디인가?)에 있는 모든 프로세스는 동일한 포트번호를 가질 수 없다. A라는 프로세스가 B라는 프로세스와 통신하기 위해서 B라는 것을 알 수 있는 포트번호로 연결을 시도하는데 중복되는 포트번호가 여러개 있으면 어느 프로세스와 통신할지 모르기 때문에 포트번호는 중복되면 안됨

 

클라이언트 단에서 크롬, 엣지 .. 등 웹 브라우저 프로그램을 실행해서 네이버 홈페이지를 들어갔다고 했을 때 네이버라는 웹서버가 사용중인 포트번호는 보통 HTTP/HTTPS인 80번과 443번을 사용한다. 

클라이언트에서 실행중인 웹 브라우저 프로그램은 49152 ~ 65535 사이의 포트번호를 할당받게 된다.

 

 

 

UDP(User Datagram Protocol)

 

TCP 보다는 빠르지만 안전성이 떨어짐

 

 

포트번호는 0~65535 사용되기 때문에 2byte(65536 가지의 수)로 패킷이 전송된다.

 

TCP(Transmisson Control Protocol)

 

UDP 보다는 안전하지만 느리다.

 

1. Sequence Number

전송하는 패킷 순서를 의미하며, 수신자는 쪼개진 세그먼트(패킷)의 순서를 파악하여 올바른 순서로 패킷을 재조립해서 온전한 하나의 데이터를 얻을 수 있게 된다.

송신자가 최초로 데이터를 전송할 때는 Sequence Number를 랜덤한 수로 초기화 하며, 이후 자신이 보낼 데이터의 패킷들을 1 Byte당 시퀀스 번호를 1씩 증가시키면서 데이터의 순서를 표현한다.

 

2. AckKnowledgment Number 

 

AckKnowledgment Number(승인 번호)는 데이터를 받은 수신자가 예상하는 다음 시퀸스 번호를 의미한다.

통신하기 위해서 연결을 설정하는 경우에는 "상대방이 보낸 시퀀스 번호 + 1"로 자신의 Ack 번호를 만들어낸다.

네이버 웹서버 측에서는 클라이언트 A가 보낸 패킷에 대해서 Ack 번호를 1로 만들어낸다.

 

 

수신자는 100 Bytes 만큼의 패킷을 받았으며 다음에 받아야 할 시퀀스 번호는 100이 되고 Ack 번호를 송신자에게 전달하면 송신자가 다음에 보내야 될 데이터는 "100(Ack) + 1byte"인 101부터 200까지 시퀀스 번호에 Bytes(보내는 데이터 크기)만큼 Ack 번호를 만들어서 보낸다. 

 

 

TCP Flags 용도

 

9개의 비트 플래그로 현재 세그먼트의 속성을 나타낸다.

 

참고 : https://evan-moon.github.io/2019/11/10/header-of-tcp/#source-port-destination-port

 

TCP의 헤더에는 어떤 정보들이 담겨있는걸까?

저번에 HTTP/3는 왜 UDP를 선택한 것일까? 포스팅을 진행하며 TCP에 대해 간단한 언급을 했었지만, 해당 포스팅에서는 기존의 HTTP에서 사용하던 TCP에 어떤 문제가 있었는지에 집중해서 이야기했었

evan-moon.github.io

 

 

TCP의 연결 지향

기기와 다른 기기를 연결했다고 할 때 가장 일반적으로 떠오르는 생각은 컴퓨터와 모니터를 케이블을 이용해서 물리적으로 연결하는 것을 상상하지만, TCP의 연결 지향은 논리적인 연결을 의미한다.

 

전화를 예로 들자면, 전화가 전화선에 연결되어있는 것이 물리적인 연결이고 다른 전화기와 통화를 하고 있는 상황이 논리적인 연결을 의미한다. TCP는 통신하기 위해서 전화의 통화방식처럼 논리적인 연결 상태를 유지하는데 왜 이런 연결 상태를 유지하는 걸까? 

 

TCP는 모든 데이터를 한번에 보내는게 아니라 패킷 단위로 데이터를 보낸다. 

패킷을 보낼 때는 하나의 회선만 사용하지 않고 여러 개의 회선으로 동시에 송신이 가능한데 그 패킷을 받는 수신측에서는 이 패킷이 누가 보냈으며 그것이 몇 번째 패킷이라는 정보가 없으면 송신자가 보내려고 하는 데이터가 무엇인지 파악하기 어렵기 때문에 상대방과의 연결 상태를 유지하는 것이다. 

 

TCP는 상대방과 연결 상태를 만들거나 해제하기 위해 특별한 과정을 거치는데, 이 과정을 핸드쉐이크라고 한다.

 

  • 수신자(서버)
namespace ServerCore
{
    internal class Listener
    {
        Socket listenSocket;
        
        Action<Socket> onAcceptHandler;

        public void Init(IPEndPoint ipEndPoint, Action<Socket> onAcceptHandler) {
            // 수신자(서버)는 요청자(클라이언트)와 통신하기 위한 소켓 생성
            listenSocket = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listenSocket.Bind(ipEndPoint);
            listenSocket.Listen(10); // 요청자(클라이언트)가 연결 요청을 보내기 전까지는 수신자는 Listen 상태로 대기

            // 수신자와 요청자의 Accpet 과정이 정상적으로 끝났을 때, 발생할 callback
            this.onAcceptHandler += onAcceptHandler;

            // 수신자와 요청자의 연결시도가 완료된 시점에 호출할 callback
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
                       
            RegisterAccept(args);
        }

        public void RegisterAccept(SocketAsyncEventArgs args) 
        {
            args.AcceptSocket = null; // ** 이해가 안됨 **            

            // 비동기 Accpet 처리
            // 요청자(클라이언트)의 연결 요청이 들어오면 수신자(서버)와 연결시도를 비동기 방식으로 한다.
            bool pending = listenSocket.AcceptAsync(args);
            // 연결이 보류중이면 true 완료되면 false를 return 한다.            
            if(pending == false)
            {
                // pending이 false이면 Accpet한 동시에 연결이 바로 된 경우라서 OnAcceptCompleted() 실행하지만
                // 그렇지 않은 경우는 요청자와 수신자의 연결시도가 완료되었을 시점에 callback(이벤트 발생) 방식으로 대리자를 호출해서 OnAcceptCompleted()을 실행한다.
                OnAcceptCompleted(null, args);
            }

            // Accept()는 동기 방식으로 연결시도가 끝나기 전까지 다른 작업으로 넘어가지 못함
            // 만약에 연결시도 과정이 오래걸린다면 다른 작업을 하지 못하기 때문에 상황에 따라서 비동기 방식이 효율적일 수 있다.
            // listenSocket.Accept();
        }

        public void OnAcceptCompleted(object sender, SocketAsyncEventArgs args) 
        {            
            if (args.SocketError == SocketError.Success)
            {                
                this.onAcceptHandler.Invoke(args.AcceptSocket);
            }
            else 
            {
                // 소켓 에러가 있으면 에러메세지 반환
                Console.WriteLine(args.SocketError.ToString());                 
            }

            // 수신자(서버)는 다른 요청자(클라이언트)로부터 연결 요청을 받아야 되기 때문에 Accept 함수를 호출한다.
            RegisterAccept(args);
        }
       
    }
}

 

1. 요청자(클라이언트)가 연결 요청을 보내기 전까지는 수신자(서버)는 Listen 상태로 대기

 

2. 요청자(클라이언트)가 수신자(서버)에게 연결 요청을 하면서 랜덤 숫자인 시퀀스 번호를 생성 후 SYN 플래그를 패킷에 담아 보낸다. 

 

"SYN" 플래그는 상대방과의 연결을 생성할 때, 시퀀스 번호의 동기화를 맞추기 위한 세그먼트이다.

시퀀스 번호를 이용하면 쪼개진 세그먼트(패킷)의 순서를 파악해서 온전한 하나의 데이터를 확인할 수 있기 때문에 요청자와 수신자는 시퀀스 번호를 사용하여 데이터를 확인한다.

 

 

'게임개발 > 게임서버' 카테고리의 다른 글

멀티 스레드 환경에서 소켓 프로그래밍  (0) 2024.05.18
동기화  (2) 2024.04.18
멀티스레드 InterLocked  (1) 2024.04.18
메모리 배리어  (0) 2024.04.18
멀티 쓰레드 프로그래밍  (0) 2024.04.17