개요
어플리케이션 프로그래밍에서 네트워크 통신은 특정 포트와 바인딩된 소켓이라는 자원을 이용하여 구현한다. 이러한 소켓을 이용한 프로그래밍을 소켓 프로그래밍이라고 한다.
소켓
소켓은 다음과 같은 연산이 가능하다.
TCP, UDP 소캣으로서 생성
특정 포트에 바인딩
(TCP) TCP 연결 받기
(TCP) TCP 연결 요청
데이터 송신 : 송신 버퍼에 공간이 없거나(TCP), 부족하면 (UDP) 불가능
데이터 수신 : 수신 버퍼에 데이터가 없으면 불가능
소켓 닫기
이때 데이터 송수신은 각각 해당 소켓의 송신 버퍼, 수신 버퍼에 데이터를 채우거나 가져오는 처리를 한다.
또한 데이터를 송신했는데 상대 수신 버퍼가 가득 찼으면 다음과 같이 처리된다.
TCP : 재전송 (또한 매번 상대 수신 버퍼의 여유 공간에 비례하여 송신 버퍼에서 전송함)
UDP : 유실
블로킹 소켓, 논블로킹 소켓
소켓은 크게 블로킹 소켓과 논블로킹 소켓으로 나뉜다. 블로킹 소켓은 송신 수신등의 함수를 호출하면 수행이 가능할 때 까지 대기하다가 수행이 끝내고 결과를 리턴하는 소켓이며 논블로킹 소켓은 수행이 가능하면 바로 수행하여 리턴하고 수행이 불가능하면 블로킹 발생 에러를 리턴하며 수행을 하지않는 소켓이다.
블로킹 소켓에서 다음 연산들은 다음과 같이 처리된다.
데이터 송신 : 송신 버퍼에 공간이 생길때 까지 기다렸다가 송신 후 결과 리턴
데이터 수신 : 수신 버퍼에 데이터가 생길때 까지 기다렸다가 수신 후 결과 리턴
TCP 연결 받기 : TCP 요청일 올때 까지 기다렸다가 연결 후 연결된 소캣 리턴
TCP 연결 하기 : TCP 연결을 보내고 연결이 될때 까지 기다렸다가 연결되면 리턴
(기다리는 동안은 sleep상태가 되며 cpu는 다른 스레드가 있다면 스레드를 교체한다.)
논 블로킹 소켓은 당음과 같이 처리된다.
데이터 송신 : 송신 버퍼가 비었으면 송신 후 결과리턴, 없으면 would blocking 에러 리턴
데이터 수신 : 수신 버퍼에 데이터가 있으면 수신 후 결과 리턴, 없으면 would blocking 에러 리턴
TCP 연결 받기 : TCP 요청이 있으면 연결 후 소캣 리턴, 없으면 would blocking 에러 리턴
TCP 연결 하기 : TCP 연결을 보내고 리턴, 이후 0바이트 보내기로 연결 완료 여부 확인 가능
블로킹 소켓의 경우 한 스레드에서 여러 소켓을 사용하기 어렵다. 한 소켓에서 블로킹이 걸리면 다른 소켓에 대한 처리를 할 수 없기 때문이다. 다라서 블로킹 소켓을 사용하려면 소켓별로 스레드를 만들어야하는데 이는 너무 많은 스레드에 의해 스택 공간 부담 및 문맥 교환 비용때문에 사실상 큰 규모의 서버에서는 불가능하다. 따라서 한 스레드에서 여러 소켓에 대해 처리하려면 논 블로킹 소켓을 사용해야한다.
이때 논 블로킹 소켓에도 문제가 있는데 바로 해당 스레드가 관리하는 소켓이 모두 블로킹이면 would blocking을 리턴하는 송수신 함수를 루프로 계속 처리하게 되며 cpu를 계속 잡아먹게 된다. 차라리 블로킹에 빠지면 sleep 상태가 되겠지만 논 블로킹 소켓은 계속 cpu를 차지하여 연산 낭비가 발생한다. 이때 select나 poll등의 함수를 사용할 수 있는데 select의 경우 소캣 리스트와 특정 시간을 매개변수로 넘겨주면 해당 소캣 리스트의 소캣에 모든 소캣이 blocking 상태면, 즉 io 불가능 상태면 특정 시간까지 sleep을 해준다. 중간에 io 가능한 소캣이 생겨나면 즉시(cpu를 차지한 순간)sleep을 깨우고 다시 진행한다. 이를 이용하여 cpu 연산 낭비를 막을 수 있다. 하지만 UDP 송신의 경우 송신 버퍼가 비어있더라도 메세지 크기만큼 비어있지않을때 would block이 발생하는데 select는 이는 잡아주지 못하기에 cpu 낭비가 발생 할 수 있다.
Overlapped I/O
논블록 소켓은 다음과 같은 단점이 있다.
1. UDP 송신등 특정 상황에 CPU 낭비가 발생한다.
2. 송수신에서 송수신 버퍼와 메모리간의 복사 연산이 발생한다.
윈도우의 Overlapped I/O 기능은 이를 해결해준다.
Overlapped I/O는 다음과 같이 사용한다.
- io상태 구조체를 만든다.
- 소켓에 Overlapped I/O 함수로 데이터와 io상태 구조체 참조를 남겨주며 송수신을 한다.
- 그러면 운영체제 백그라운드에서 해당 송수신이 진행된다.
- 이후 GetOverLappedResult함수에 해당 소캣과 io상태 객체를 넘겨주면 진행 여부 및 성공 시 데이터를 리턴받을 수 있다.
5. 이때 io 진행중에는 데이터 블록 및 io상태 객체를 수정, 삭제 하면 안된다.
이를 프레임 단위별로 생각하면 각 소켓에 최초 overlapped io를 걸어주고 루프를 돌며 처리가 끝난 소캣은 새 io를 걸고 아직 안끝난 소캣은 넘어가는 식으로 사용하면 된다.
이때 해당 소캣의 송수신 버퍼를 0으로 설정하면 넘겨준 데이터 블록을 일시적인 송수신버퍼로 사용하여 송수신을 진행하게 되고 이로인해 송수신 버퍼와 메모리간에 복사 오버헤드가 제거된다.
epoll
epoll은 논블로킹 소켓만 사용하거나 Overlapped I/O를 사용할때의 단점인 매 프레임 별로 모든 소켓을 루프해야한다는 단점을 극복하게 해주는 기능이다. epoll은 리눅스와 안드로이드에서 사용가능하며 윈도우는 대신 Overlapped를 보조해주는 IOCP라는 기능을 대신 써야한다.(뒤에 설명)
epoll은 등록된 소캣 리스트에서 현재 IO가능한 소캣들만을 걸러서 주는 기능을 제공해준다. epoll은 다음과 같이 사용한다.
- epoll 객체를 만든다.
- epoll에 소캣들과 각 소캣에 대한 커스텀 추가정보를 넣는다.
- epoll의 wait(time)을 호출하면 time까지 기다리며 io가능한 소캣들이 생기면 해당 소캣들을 events라는 것으로 묶어 리턴해준다.
- events의 각 event는 각 소캣의 송신 또는 수신 가능함을 나타내며 event로 부터 소캣, 추가정보, 송신인지 수신인지등의 정보를 가져올 수 있다.
- 해당 이벤트들에 대해서만 루프를 돌며 송수신을 처리한다.
이때 송신의 경우 평소에 대부분 송신가능상태일 확률이 높아 성능이 크게 오르지 않을 수 있다. 이를 대비하여 가능 상태가 아닌 불가능 -> 가능으로의 상태 변화에만 이벤트를 발동시키는 방식도 존재한다.
IOCP
IOCP는 윈도우에서 Overlapped I/O에서도 전체 소캣 루프를 할 필요없게 해주는 기능이다.
IOCP는 등록된 소캣에서 완료 신호가 발생한 소캣들을 큐에 넣고 이를 wait함수로 꺼내갈 수 있게하는 기능을 제공한다. 다음과 같이 사용한다.
1. iocp 객체 생성
2. 해당 객체에 소캣과 추가정보를 넣고 해당 소캣들에 최초 overlapped io를 실행시킨다.
3. 해당 객체에 wait(time)을 호출하면 해당 소캣에서 발생한 완료 신호가 큐에 들어가면 그것을 꺼낸다.
4. 꺼내진 이벤트들을 처리하고 해당 소캣에 다음 io를 실행시켜준다.
이러면 완료가 이루어진 소캣들만 꺼내어져 처리된다.
이때 해당 시점에 io가능한 모든 이벤트(소캣)을 주는 epoll가 달리 발생한 완료신호를 큐에 넣고 꺼내어 사용하는 IOCP가 스레드 풀링을 구현하기 훨씬 쉽다.
참고서적
게임 서버 프로그래밍 교과서
'네트워크 > 게임 서버 프로그래밍' 카테고리의 다른 글
[게임 서버][6장]프라우드넷 (0) | 2023.03.21 |
---|---|
[게임 서버][5장]게임 네트워킹 (1) | 2023.03.21 |
[게임 서버][4장]게임 서버와 클라이언트 (0) | 2023.03.21 |
[게임 서버][2장]컴퓨터 네트워크 (0) | 2023.03.21 |
[게임 서버][1장]멀티 스레딩 (0) | 2023.03.21 |