최근에 소켓 프로그래밍 하다 재밌는걸 알았읍니다.

윈도우즈 소켓 함수인 send()는 데이터의 전송을 보장하지 않읍니다.
정확히는 유저 버퍼에 있는 데이터를 소켓의 송신 버퍼에 복사를 해줄 뿐입니다.
데이터 전송은 커널에서 알아서 해줄 뿐이지용.

그렇다면, 데이터의 전송을 보장하는 방법이 없느냐?
그렇진 않읍니다.
데이터의 전송을 보장하려면, OverlappedIO을 이용하여 콜백 함수나 IOCP 큐로 통지를 받아야 합니다. 이 경우엔 All or Nothing으로 요청한 데이터를 모두 전송하거나 1byte도 보내지 않게 됩니다.
다만 이 방법을 사용할 때도, WSASend류의 함수를 호출하고, 리턴되더라도 그것이 전송을 보장하진 않습니다. 단지, 전송 완료에 대한 통지를 콜백 함수나 IOCP 큐로 받을 수 있을 뿐이지요.

여기까지가 제가 알고 있던 내용이었읍니다.
그런데, 최근에 재미있는 아티클을 발견했읍니다.

https://support.microsoft.com/eu-es/help/214397/design-issues-sending-small-data-segments-over-tcp-with-winsock

이 아티클은 TCP 통신에서 몇 가지 경우를 나누고 무엇이 더 효과적인지를 알려주는데요.
다음과 같은 내용이 아티클에 있읍니다.

“In most cases, the send completion in the application only indicates the data buffer in an application send call is copied to the Winsock kernel buffer and does not indicate that the data has hit the network medium. The only exception is when you disable the Winsock buffering by setting SO_SNDBUF to 0.”

“Unless you want to guarantee a packet is sent on the wire when a send completion is indicated by Winsock, you should not set the SO_SNDBUF to zero.”

즉, 소켓의 송신 버퍼 사이즈를 0으로 한다면, 함수 호출의 리턴이 곧 데이터 전송 완료까지를 의미하게 됩니다. OverlappedIO을 통한 콜백이나, IOCP 큐가 필요 없지용. 그냥 send()함수를 호출하고 완료되면 그것이 바로 데이터 전송 완료인 것입니다.

그런데…매우 흥미로웠으나, 딱히 응용할 방법은 안떠오르네요 ㅡ.ㅡ…
뭔가 끝이 이상하죠? ㅎㅎ;

아무튼, 아티클은 꽤 재미있는 내용들이 많으니 소켓 프로그래밍에 관심이 있으면 추천드립니다.

4 Likes

저번에 잠깐 올라왔던 zero copy 내용인가요?

아티클 내용은 zero copy는 딱히 언급은 안하고 있읍니다.
저도 그래서 좀 헷갈렸구요.(그래서 쓰고 지웠어요) 사실 아직도 헷갈립니다 ㅡ.ㅡ…

보통 소켓 통신할 때 zero copy는 소켓 버퍼의 크기를 0으로 만들어서 진행을 하지요.
[어플리케이션 버퍼 <-> 소켓 버퍼 <-> NIC 버퍼] 의 2단계 복사를
[어플리케이션 버퍼 <-> NIC 버퍼]로 1단계로 줄이는 것 처럼요.

그런데, 이런 제로 카피를 보장해주는 것은 send(), recv()함수가 아니라
OverlappedIO를 하는 WSASend, WSARecv와 같은 함수라고 알고 있었읍니다.
리눅스 계열에서도 보통의 송수신 함수만으로는 안되고, 몇 가지를 더 조합해야 가능하다고 들었읍니다.

그런데, 위 아티클의 내용을 따라가다보면,
“어? 굳이 WSASend, Recv를 안써도 소켓 송수신 버퍼를 0으로 만들어서 통신하면, 이게 제로 카피 아닌가?”
라는 의문이 들게 됩니다…그리고 실제로 소켓 송수신 버퍼를 0으로 만들고 전송, 수신해도 잘 받아집니다 ㅡ.ㅡ;;;;

“그냥 zero copy라는게 송수신 버퍼를 0으로 만들면 무조건 되는거구나?” 라는 생각이 드는데요.
그런데, 아티클에선 또 이런 내용이 있읍니다.
“If necessary, Winsock can buffer significantly more than the SO_SNDBUF buffer size.”
즉, 지정해둔 버퍼 크기보다 사이즈가 크게 버퍼를 잡기도 한답니다…

그래서 아직 저도 잘 모르곘읍니다. ㅜㅜ
뭔가 더 파고들면 알 수 있을 것 같은데, 단서를 발견하기가 힘드네요

버퍼 사이즈를 크게 잡는 경우는
지연 전송을 하더라도 좀 더 모아서 처리하는 경우입니다

소켓 버퍼 크기를 작게 잡을수록
패킷 크기가 작은 경우 패킷을 굉장히 많이 보내게 되니까요

오홍 그렇군여

WINSOCK상의 구현으로는 SNDBUF가 0일 경우, TCP적으로 혼잡 상태라면 패킷이 전송 가능한 상태가 될때까지 Blocking 하다가 전송이 완료되고 나서 리턴하는 방식인가 보군요.
다만 효율성은… 잘 모르겠습니다. 제가 생각하는게 맞다면 SNDBUF를 0으로 설정하면 사실상 송신 사이드에서의 TCP Flow Control을 포기하는거니.
어차피 TCP State Control이나 SNDBUF에 저장된 패킷의 송신여부는 커널의 TCP Stack이 알아서 처리해줄텐데, SNDBUF에 정상적으로 인입된 경우 리턴하는 방식 이상의 Control Point가 필요할까요… 라는 개인적인 생각입니다.

아 그리고 또. ZC이야기가 나왔네요.
윈도우 커널은 잘 모르겠지만, 리눅스 커널에서는 ZC와 버퍼 사이즈를 0으로 하는 것과는 별개의 이야기입니다.
리눅스 ZC는 인터럽트 발생후 소켓 버퍼 (패킷 버퍼) 할당 단계부터 유저 메모리 영역에 매핑할 수 맀는 Reserved Area를 사용하게 됩니다.
SNDBUF나 RCVBUF는 단순히 해당 패킷버퍼의 포인터를 버퍼링하고 있을 뿐이고요. (물론 이런 포인터 추가/제거 단계도 정말 넓은 의미에서의 Copy라고 본다면, ZC와 연관성이 없다고 단언하기는 어렵습니다만…)
윈도우는 어떤지 모르겠지만 리눅스에서 '버퍼사이즈가 0이면 ZC가 가능하다.'는 명백하게 오개념입니다.

오홍 그렇군여

여태까지 생각한걸 정리해보면,

제로 카피의 경우 OverlappedIO를 제공하는 소켓 + WSArecv, WSASend와 같이 OverlappedIO를 제공하는 함수를 동시에 써야 합니다.
이 때, 수신 버퍼의 사이즈는 변경하지 않아도, WSARecv에 WSABUF로 제공하는 버퍼에 다이렉트로 데이터가 저장됩니다.(제로카피)
그러나, 송신 버퍼는 여전히 사이즈를 0으로 변경해야, 제로 카피가 이루어집니다.
추가 내용이 궁금하시면 아래 아티클도 참고해주세요 ㅋ.ㅋ

https://support.microsoft.com/en-us/help/181611/socket-overlapped-i-o-versus-blocking-nonblocking-mode

따라서

“어? 굳이 WSASend, Recv를 안써도 소켓 송수신 버퍼를 0으로 만들어서 통신하면, 이게 제로 카피 아닌가?”

는 아니라고 결론내렸읍니다.

안녕하세요 ㅎㅎㅎ

궁금해서 여쭙니다.
Zero copy를 써야할때가 있나요? 성능향상을 위해서인가요?

넵 성능 향상을 목적으로 합니다.
패킷 송수신을 위한 복사가 2단계에서 1단계로 줄어들게 되니까요.
2단계 : 어플리케이션 <-> 소켓 버퍼 <-> NIC 버퍼
1단계: 어플리케이션 <-> NIC 버퍼