[네트워크] 논블로킹 소켓
이 포스트는 “게임 서버 프로그래밍 교과서”를 참고하여 작성된 포스트입니다.
논블로킹 소켓이란 이름 그대로 블로킹하지 않는 소켓입니다.
이번 포스트에서는 논블로킹 소켓은 무엇이고 왜 필요한지에 대해 작성해 보겠습니다.
블로킹 소켓에 대한 내용은 다른 포스트에서 다룹니다.
막힘없는 소켓 통신
송신자와 수신자가 각각 1명뿐인 일대일 네트워킹 프로그램을 개발할 떄는 블로킹 소켓을 사용해도 문제 없습니다.
그러나 네트워킹의 대상이 늘어나 1만명 이상일 경우에도 블로킹 소켓을 사용한다면 어떻게 해야할까요?
가장 쉽게 할 수 있는 방법은 네트워킹 대상의 수만큼 스레드를 생성하는 것입니다.
그리고 각 스레드에서 네트워킹 대상과 각자 통신을 수행하면 됩니다.
이 방법은 네트워킹 대상 수가 적을 때는 상관 없지만 대상의 수가 수백, 수천 개 이상으로 증가한다면
각 스레드의 저장공간은 물론 스레드간 문맥전환으로 자원 낭비가 심각해질 수 있습니다.
또한 송신 버퍼, 수신 버퍼의 상태에 따라 언제 블로킹될지 모르기 때문에 문제가 될 수 있습니다.
따라서 이 문제를 개선한 방법 중 하나가 논블로킹 소켓(Non-blocking socket)입니다.
논블로킹 소켓은 크게 아래와 같은 방법으로 사용할 수 있습니다.
- 생성한 소켓을 논블로킹 소켓 모드로 전환
- 블로킹 소켓처럼 연결, 송신, 수신을 진행
- 논블로킹 함수는 연결, 송신, 수신 함수 호출에 대해 즉시 ‘성공’ 혹은 ‘would block’ 반환
여기서 would block 이라는 것은
“블로킹이 걸려야 할 상황이지만, 블로킹을 걸지 않았다.” 라는 의미입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
s = socket(TCP);
// ...
s.connect(...);
s.SetNonBlocking(true);
while(true) {
ret = s.send(data);
if(ret == EWOULDBLOCK) {
// 블로킹이 걸려야할 상황, data를 송신하지 않음
continue;
}
if(ret == OK) {
// 전송 성공
} else {
// 전송 실패, 오류 처리
}
{
// ...
}
}
이 의사 코드는 논블로킹 소켓을 사용해 데이터를 전송하는 코드입니다.
논블로킹 소켓을 사용했기 떄문에 s.send(data)를 호출하면 블로킹 없이 즉시 값이 반환됩니다.
여기서 값은 전송의 성공 여부를 담고 있습니다.
반환값(ret)이 would block(EWOULDBLOCK)인 경우, 송신을 하지 않은 상태로 다시 송신 함수를 호출해야 합니다.
송신이 성공적으로 됬다면 반환값(ret)은 송신한 데이터(data)의 크기를 반환합니다.
그 외에 would block도 송신한 데이터의 크기도 아니라면 또 다른 문제가 발생한 것으로
이에 대한 오류 처리가 필요합니다.
논블로킹 소켓으로 여러 소켓 다루기
블로킹 소켓 여러 개를 한 스레드에서 다룰 경우,
현재 접근중인 소켓에 블로킹 되어있으면 다른 소켓이 수신 혹은 송신을 완료해도
현재 접근중인 소켓의 작업이 완료될 때 까지 강제로 대기해야 합니다.
그로 인해서 상대의 프로그램도 같이 대기해야하는 최악의 상황까지 갈 수 있습니다.
그러나 논블로킹 소켓을 사용하면 한 스레드로 여러 소켓을 한 번에 다룰 수 있습니다.
블로킹 소켓과 다르게 송신, 수신 함수를 호출해도 즉시 반환되기 때문에
지연 시간 없이 비교적 빠르게 여러 소켓에 접근이 가능합니다.
반환값이 would block 이라도 나중에 다시 함수를 호출하면 되기 때문입니다.
1
2
3
4
5
6
7
8
9
for(s : sockets) {
(ret, data) = s.receive();
if(data.length > 0) {
// 정상 수신 완료
}
else if(ret != EWOULDBLOCK) {
// 오류 처리
}
}