
소켓은 Blocking모드와 NonBlocking모드 2가지 모드를 지원합니다. 기본은 Blocking모드 입니다.
WSAEWOULDBLOCK을 반환하게 만들어졌습니다. 여기서 동기와 비동기에 대한 이야기가 나올 수 있는데, I/O은 태생적으로 비동기적입니다. 왜냐하면, I/O작업은 코드에서 직접하는게 아니라, Kernel Code와 Ethernet장치가 하게됩니다.
ULONG uMode;
ioctlsocket(_ListenSocket, FIONBIO, &uMode);
연결이 성립된(handShake가 끝난) 대상에 소켓을 매핑시키는 작업을 합니다. 시그니처는 아래와 같습니다.
SOCKET WSAAPI accept(
[in] SOCKET s,
[out] sockaddr *addr,
[in, out] int *addrlen
);
example
SOCKAADR_IN connectInfo;
ZeroMemory(&connectInfo);
int connectLen = sizeof(connectionInfo);
SOCKET connectSocket = ::accpet(_ListenSocket, reinterpret_cast<SOCKADDR*>(&connectInfo), &connectLen);
BackLogQueue에는 연결이 다 되어서 들어오는데, 어떻게 실패할 수 있을까는 생각이 직관적으로 들 수 있다.
Accpet실패를 상대방이 끊어서 실패하는 경우지 않을까라는 어떻게보면 직관적으로 무책임하게(?) (최적화를 하고싶은데, 캐시를 제일 먼저 의심하는 것처럼)생각할 수 있는데 절대 아니다.
Accpet는 BackLogQueue에 있는 연결정보를 그대로 가져올 뿐이다. 설령 상대방이 connect을 한 뒤, 바로 끊는다고해도 TCP입장에서는 그 순간에는 그걸 모른다. Send(),Recv()와 같이 동작을 취해야 해당 소켓이 닫혔는지 어떤 상태인지 알 수 있는 것이다.
실제로 MS공식문서에서 에러코드도 연결이 실패했다거나, 적극적으로 거부했다거나 이런게 없다.
Accpet MSDN
따라서, Blocking일 때, Accept에 실패했다는건 SOCKET을 만들 메모리가 없거나, 인자가 잘못되었거나, Listen을 그전에 안해줬거나 등 프로그래머가 실수한 케이스 밖에 없다는 이야기이다.
연결된 소켓(Established된 소켓)에 데이터를 보내는 함수입니다. TCP는 Send, UDP는 sendto 함수를 이용합니다.
int WSAAPI send(
[in] SOCKET s,
[in] const char *buf,
[in] int len,
[in] int flags
);
SOCKET_ERROR,요청한 길이 2가지 케이스밖에 없습니다.따라서, NonBlocking으로 Send했을 때 WOULDBLOCK 혹은 Len가 작다면 연결을 끊을 생각도 해봐야합니다.(애초에 데이터를 읽지 못하는 상황)
데이터를 수신하는 함수입니다.
int recv(
[in] SOCKET s,
[out] char *buf,
[in] int len,
[in] int flags
);
1~Len의 길이를 받습니다. TCP는 Stream형태이기 때문에, 패킷(세그먼트)의 경계가 명확하지 않습니다.
따라서, 패킷이 짤려올 수도 있고 뭉쳐져서 보내질수도 있습니다.
예를들어, 수신윈도우의 공간이 조금밖에 없어서 상대방이 원하는 데이터를 모두 못보낼 수도 있습니다.
혼잡제어 때문에 송신하는 과정에서 일부의 데이터만 전송될 수도 있습니다.
네이글 과정에서 MSS에 딱 도달해서, 데이터가 짤려서 보내질 수 있습니다.
네이글 과정에서 여러 PayLoad가 하나의 TCP Header에 뭉쳐져서 올 수도 있습니다.
따라서, Recv는 위같은 TCP의 구현을 대응할 수 있도록 만들어졌기 때문에 Recv는 1~Len까지 받는게 기본동작이 되었다고 알고 있습니다.
소켓에는 소켓 옵션을 설정할 수 있습니다.
int WSAAPI setsockopt(
[in] SOCKET s,
[in] int level,
[in] int optname,
[in] const char *optval,
[in] int optlen
);
다양한 옵션이 있지만, 자주 쓰게되는 옵션만 따로 정리하겠습니다.
Linger Option은 closesocket을 언제 반환할지에 대한 내용입니다.
closesocket은 1.FIN 송신(종료절차수립),2.Socket Handle반환 2가지 역할을 하는 함수입니다.
이때, FIN 송신을 주고 4wayHandShake과정으로 들어갔을 때 아래와 같은 선택지가 있습니다.
1. 즉시 closeSocket리턴, 커널이 자신의 송신버퍼에 남아있는 데이터를 전부 보내고 소켓을 정리하기
2. 즉시 CloseSocket리턴 ,송신버퍼에 있는 데이터를 전부 버리고 소켓을 강제종료시키기.
3. 일정 시간 동안 closeSocket리턴하지않고, 일정 시간(TimeOut)시간을 두고
리턴, 그 동안 커널이 자신의 송신버퍼에 있는 데이터를 전부 보내고, 남은건 버리기(혹은 전부 다 보내기)
기본동작은 1번 입니다. 그리고, 2번은 꽤 활용도가 있을 수 있습니다. 왜냐하면, 4wayHandshake를 타지않는 상황을 바랄 때 RST를 보내도록 할 수 있습니다.
LINGER option
/*default*/
option.l_onOff = 0;
/* RST */
option.l_onOff = 1;
option.l_linger = 0;
/* ??? */
option.l_onOff = 1;
option.l_linger = 10;
흔히하는 오해가, l_linger값을 10으로 셋팅한다면, 10초뒤에 RST를 쏴주지 않을까?라는 의미로 해석될 수 있습니다. 하지만, RST를 쏴주지 않습니다.
LINGER는 closeSocket()의 함수리턴시점을 정해주는 옵션입니다.
송/수신버퍼의 크기를 설정할 수 있다고 나와있지만, RCVBUFF는 애초에 정해진 크기보다 크게 잡기 때문에, 잘 동작을 하지 않을 수도 있고 송신버퍼는 0을 줬을 때는 다른 의미를 가지기 때문에 꽤 흥미로울 수 있습니다.