본문 바로가기

네트워크/멀티 플레이어 게임 프로그래밍

3장 버클리 소켓 정리

버클리 소켓은 인터넷으로 데이터를 주고받는데 가장 널리 쓰이는 라이브러리이다. 플랫폼맏 라이브러리의 인터페이스가 조금씩 다르긴 하지만 핵심 요소는 같다.

 

라이브러리의 핵심 자료형은 sockaddr 로, 여러 네트워크 계층 프로토콜의 주소를 나타낼 수 있다. 발신자나 수신자의 주소를 지정할 필요가 있을 때 이것을 사용하면 된다.

 

UDP 소켓은 연결이 유지되지 않고 내부 상태도 없다. SOCK_DGRAM으로 UDP 소켓을 만들수 있다. UDP 패킷을 받으려면 운영체제가 포트를 하나 바인드 해주어야한다.

 

TCP 소켓은 내부 상태가 있고 데이터 전송 전 연결을 먼저 한다. 

 

소켓은 동작 중 호출 스레드를 블로킹하게 된다. 이것은 실시간 프로그램에서는 문제다,

따라서, 멀티 스레딩, 논블로킹I/O, select() 함수를 사용하여 이를 해결한다.

 

 

위의 내용을 밑에 상세히 적겠다.

버클리 소켓은 API로 TCP/IP 스택의 여러 계층 사이에 표준 인터페이스로 쓰기 위해 제공된다.

 

소켓을 만들려면 socket() 를 호출해야한다.

socket(
    _In_ int af,
    _In_ int type,
    _In_ int protocol
    );

 

af = 주소 패밀리, 네트워크 계층 프로토콜을 지정

type = 패킷의 타입 형식을 설정 

protocol = 실제 프로토콜 설정 (0으로 해두면 type 에 따른 전송 계층 프로토콜 설정 UDP,TCP등)

 

TCP 소켓을 해제할려면 나가고 들어오는 잔여 데이터 전부가 전송이 끝난 후 ACK까지 마친 상태에서 끝내는게 중요.

 

UDP 소켓

UDP 소켓은 만든 즉시 데이터를 보낼 수 있다. 바인딩하지 않은 상태라면 네트워크 모듈이 동적 포트 범위에 남아 있는 포트를 자동으로 찾아 바인딩한다. 

별다른 옵션이 없을 경우, 소켓에 아직 수신된 데이터그램이 없으면 스레드가 블로킹되어 데이터그램을 수신할 때 까지 기다린다.

UDP 는 서버에서 UDP 소켓을 하나만 만들어도 된다. 그리고 만든 발신자가 하나의 주소 및 포트에 패킷을 보낼 수 있다. 그래서 각각의 데이터그램이 어느 발신자꺼인지 알아야할 필요가 있기 때문에 파라미터에서 주소 및 포트를 알려줘야한다.

 

TCP 소켓

TCP 소켓은 먼저 연결을 3-WAY 핸드셰이킹을 이용해서 연결한다.

연결을 위해 리스닝 소켓을 생성한다. 리스닝 소켓은 서버 애플리케이션이 클라이언트로부터의 연결 요청을 수신할 준비를 할 때 생성된다. 이는 서버가 클라이언트와의 통신을 시작하기 위한 첫 단계이다.

 

당연히 리스닝 '소켓'이라서 생성 후 포트 바인딩을 해야한다. 리스닝 소켓으로 연결 신호가 오면 ACCEPT 를 통해 연결을 받아드린다. 이때, 새로운 소켓이 생성되는데 이 소켓을통해서 클라이언트와 송수신이 가능하다.

 

블로킹, 논블로킹 I/O

소켓 관련 함수는 대부분 블로킹 호출이다. 받을 데이터가 없으면 블로킹되어 데이터가 수신될 때까지 기다린다.

게임에서 치명적인 문제인데 게임이 멈춘것같은 버그를 야기할수있다.

그래서 소켓에는 논블로킹으로 설정할 수 있는 함수가 존재한다. 다만, 논블로킹으로 설정하고 소켓이 블로킹하지 않고 즉시 리턴한다. 여기서 리턴한 소켓 안에 데이터가 없으면 에러 코드를 반환하는데 이것은 그냥 통신할 데이터가 없다는 것이기 때문에 따로 예외 처리 해주어야한다.

매 프레임마다 논블로킹 모드 소켓을 통해 새로운 데이터가 있는지 확인해야한다.

 

멀티스레딩

멀티스레딩을 통해 ㅔ블로킹 문제를 해결하기도한다. 메인 스레드와 리스닝 스레드 클라이언트별로 소통할 소켓과 관련한 각각의 스레드들을 만들어 놓으면 게임 루프에 지장 없이 블로킹 문제를 해결한다. 클라이언트별 스레드에서 데이터를 받으면 이것을 메인 스레드에 보내는 것으로 게임 루프에 영향을 주면서 블로킹 문제를 해결 가능하다.

 

select함수

select 함수로 소켓 저장공간을 생성한다. 이 안에 리스닝 소켓을 넣어놓는다. 이제 클라이언트들이 통신 연결 신호를 보낼 때 소켓 저장공간 안에 클라이언트와 관련한 소켓이 없다면 리스닝 소켓와 관련한 이벤트를 보낸다. select 함수는 리스닝 소켓 이벤트가 왔으면 아직 해당 클라의 소켓이 없으니 리스닝 소켓에서 새로운 소켓을 생성한다. 클라이언트와 관련한 소켓에 이벤트가 온것이면 해당 소켓을 통해 클라와 통신을 시작한다.