해당 내용은 게임 서버 프로그래밍교과서의 내용을 참고했습니다.
해당 설명에 사용된 코드는 모두 의사 코드입니다.
디바이스에 처리 요청을 걸어 놓고 응답을 대기하는 함수를 호출할 때 스레드에서 발생하는 대기현상
블로킹이 발생하는 스레드에서는 CPU연산을 하지 않는다.
스레드에서는 디스크에 접근 할 시에 블로킹이 된다.
소켓에서는 네트워크 수신을 하는 함수를 호출할 시 응답이 올 때 까지 블로킹된다.
TCP통신은 일대일 통신만 허락한다.
main()
{
s=socket(TCP); //1
s.bind(any_port); //2
s.connect("55.66.77.88:5959"); //3
s.send("hello"); //4
s.close(); //5
}
첫번째 줄은 TCP 소켓 s를 생성한다.
두번째 줄은 65535개의 포트 중에서 사용 가능한 빈 포트를 차지한다.
만약 빈 포트가 없을 경우 다른 곳에서 점유한 포트를 공유한다.
세번째 줄은 상대방과의 TCP통신을 시도한다.
이는 블로킹이 발생하는데, 상대방이 연락을 수락하거나, 거절하거나, 존재하지 않는 경우 리턴을 한다.
네번째 줄은 데이터를 전송한다.
자기 컴퓨터의 운영체제에서 상대방 컴퓨터로 데이터를 전송하는 처리가 완료되면 리턴한다.
하지만 위의 코드는 상대방이 수신하기 전에 즉시 리턴한다.
다섯번째 줄은 TCP연결을 해제한다.
일련의 바이트 배열, 크기는 고정되어 있으나 마음대로 변경할 수 있다.
FIFO형태로 작동하며, send를 호출하면 일단 송신 버퍼에 채워진다.
송신 버퍼는 queue처럼 작동하며, send()함수에 들어간 내용을 들어온 순서대로 보낸다.
만약 송신 버퍼가 가득 차면 블로킹이 발생하지만, 그 외의 경우에는 블로킹이 발생하지 않는다.
main()
{
s=socket(TCP); //1
s.bind(5959); //2
s.listen(); //3
s2=s.accept(); //4
print(getpeeraddr(s2)); //5
while(true)
{
r=s2.recv(); //6
if(r.length<=0) //7
break;
print(r);
}
s2.close(); //8
}
데이터가 수신되는 것이 있을 때 마다 채운다.
꽉 차면 더 이상 데이터를 받지 않는다.
소켓에서 데이터를 수신하는 함수를 호출하면 수신 버퍼에 있는 데이터를 꺼내올 수 있다.
수신 버퍼가 비어 있으면 데이터를 수신하는 함수는 블로킹이 일어난다.
수신 함수인 recv()는 1바이트라도 수신할 수 있으면 즉시 리턴한다.
수신 버퍼에 남은 공간이 없을때 까지 완전히 채워진다. 수신 버퍼가 꽉 차면, 데이터를 보내는 쪽에서 송신 함수인 send()가 블로킹 된다.
TCP연결은 유지된 채 TCP통신은 멈춘다.
데이터그램이 최소 1개 도착해 있으면 즉시 리턴한다.
UDP의 데이터그램이 수신하는 측에 도착하지만, 수신 버퍼 안에 데이터그램을 담을 공간이 없으면 데이터는 버려진다.
송신함수의 블로킹이 발생하지 않는다.
송신자가 보내는 데이터양이 많을 때, 송신자 쪽 운영체제가 알아서 초당 송신량을 줄인다.
UDP를 속도 제한 없이 마구 송신하면 해당 데이터를 처리하느라 이외의 곳에서 들어오는 데이터를 처리하지 못할 수 있다. 다른 네트워크들은 네트워크 경쟁에서 밀리며, 이 때문에 주변의 네트워킹이 두절되기도 한다. 이는 혼잡현상이라 한다.
블로킹 소켓이란 지금까지 해 온 UDP, TCP를 의미한다.
네트워킹 대상 개수만큼 스레드를 만들어 통신하는 방법이 있다.
네트워킹 대상이 많지 않으면 큰 문제는 없지만, 스레드가 1000개면, 적어도 1기가바이트가 필요하다. 또한, 스레드 간의 컨텍스트 스위치가 빈번히 일어나므로 자원 낭비가 발생한다.
소켓 송신 함수에서 계속해서 송신을 하는데 수신하는 쪽에서 송신 속도를 따라가지 못한다면 송신 쪽 소켓 버퍼가 가득 찰 것이다.송신 버퍼에 빈 공간이 없으면 TCP통신은 블로킹이 발생한다.
하지만 대부분의 운영체제에서는 소켓 함수가 블로킹되지 않게 하는 API를 제공한다. 이를 논블록 소켓이라 한다.
사용하는 방법은
would block은 "블로킹 걸렸어야 할 상황인데 말이지...하지만 자네는 운이 좋아. 블로킹에 걸리지 않았잖아?"라는 의미이다.
void NonBlockSocketOperation()
{
s=socket(TCP);
...;
s.connect(...);
s.SetNonBlocking(true);//논블록 방식으로 변경
while(true)
{
r=s.send(dest, data); //->1
if(r==EWUOLDBLOCK)
{
continue;
}
if(r==OK)
{
//보내기 성공에 대한 처리
}
else
{
//보내기 실패에 대한 처리
}
}
}
논블록 소켓을 이용하면 한 스레드에서 여러 소켓을 한꺼번에 다룰 수 있다.
루프를 돌면서 소켓 100개에 대한 수신 함수를 한다고 가정할 때, 모든 소켓이 수신할 데이터를 받아놓은 상태는 아니다. 따라서 논블록 소켓으로 처리하면 블로킹이 난무하는 문제가 사라진다.
논블록 소켓이 수신할 데이터가 있으면 데이터를 꺼내서 처리한다. 하지만 만약 수신할 데이터가 없다면 would block오류를 리턴할 뿐, 즉시 리턴한다. 따라서 많은 수의 소켓을 지연 시간 없이 처리할 수 있다.
하지만 connect()함수를 논블로킹으로 실행되었을때 would block이 리턴된다면 0바이트 송신으로 현재 상태를 확인해볼 수 있다.
하지만 while(true)문으로 이를 계속 테스트한다면, 블로킹 소켓에서는 블로킹이 걸려 CPU사용량을 높이지 않지만, 논블록 소켓은 CPU사용량을 100%로 만든다.
게임 클라이언트에게는 큰 문제가 없지만 서버에서는 그렇지 않다.
select(sockers, 100ms);
select는 socket에 I/O처리가 가능한 소켓이 하나라도 있을 경우 즉시 리턴한다.
그렇지 않으면 100ms동안 블로킹 한다.
I/O처리가 가능하다는 것은 해당 소켓에 소켓 함수를 호출하면 would block이 아닌 다른 결과가 나온다는 것을 의미한다.
블로킹 모드의 경우 리스닝 소켓에 대해 accept()를 호출하면 블로킹이 걸린다.
TCP연결이 들어오면 리턴을 하는데, accept()의 리턴 값은 TCP연결에 대한 소켓 핸들이다.
논블록 소켓의 경우 TCP연결이 아직 들어오지 않았으면 accept()는 블로킹 대신 would block오류를 준다.
따라서 select()를 이용하여 I/O가 감지되면 accept()함수를 호출하면 된다.
TCP소켓에서 송신 버퍼에 1바이트라도 비어 있으면 I/O가능이 된다. 이러한 상태에서 send()함수를 호출하면 1바이트만 송신 버퍼에 채워지고, 성공적으로 리턴한다.
receive() 역시 1바이트만 들어 있어도 수신 버퍼에 있는 것을 꺼내 오고 성공적으로 리턴한다.
하지만 UDP에서는 UDP소켓에 1바이트라도 비어 있으면 I/O가능이지만, 보내려는 데이터가 5바이트라면, UDP는 일부분만 보낼 수 없으므로 would block이 발생한다. 여전히 UDP송신 버퍼에는 1바이트의 공간이 남아 있어 I/O가능이지만 UDP send()를 하지 못한 채 헛발질만 계속한다.
소켓 송수신 함수에 들어가는 데이터 블록 인자를 성공적으로 실행하면 프로세스 내 데이터 블록을 운영체제 커널 내 소켓 버퍼에 복사한다. 따라서 고성능의 서버를 개발할 때는 이러한 복사 연산도 중요하다.
이를 해결하는 방법은 Overlapped 또는 Asynchronous I/O를 이용하는 방법이다.
Overlapped I/O함수는 즉시 리턴되지만, 운영체제로 해당 I/O실행이 별도로 동시간대에 진행되는 상태다.
따라서 호출한 Overlapped I/O함수가 비동기로 하는 일이 완료될때 까지는 소켓 API에 인자로 넘긴 데이터 블록을 제거하거나 내용을 변경해서는 안된다.
Overlapped I/O는 윈도우 전용으로 작동하는 함수로, 리눅스나 다른 운영체제에서는 찾기 힘들다.
소켓이 I/O가능 상태가 되면 이를 감지해서 사용자에게 알림을 해 주는 역할.
논블록 소켓을 대량으로 가지고 있을 때 효율적으로 처리해 주는 API
소켓의 Overlapped I/O가 완료되면 이를 감지해서 사용자에게 알려 주는 역할
사용자는 IOCP에서 I/O가 완료되었음을 알려 주는 신호를 꺼낼 수 있음.
소켓 수가 많더라도 이 중에서 I/O가 완료된 것들만 IOCP를 이용해서 바로 얻을 수 있다.