디자인 문제 - Winsock을 사용하여 TCP를 통해 작은 데이터 세그먼트 보내기
- 아티클
- 2023. 07. 17.
이 문서의 내용
TCP를 통해 작은 데이터 패킷을 보내야 하는 경우 Winsock 애플리케이션의 디자인이 특히 중요합니다. 지연된 승인, Nagle 알고리즘 및 Winsock 버퍼링의 상호 작용을 고려하지 않는 디자인은 성능에 큰 영향을 줄 수 있습니다. 이 문서에서는 몇 가지 사례 연구를 사용하여 이러한 문제를 설명합니다. 또한 Winsock 애플리케이션에서 작은 데이터 패킷을 효율적으로 보내기 위한 일련의 권장 사항도 파생합니다.
원래 제품 버전: Winsock
원래 KB 번호: 214397
배경
Microsoft TCP 스택이 데이터 패킷을 받으면 200ms 지연 타이머가 꺼집니다. ACK가 전송되면 지연 타이머가 다시 설정되고 다음 데이터 패킷이 수신될 때 또 다른 200ms 지연이 시작됩니다. 인터넷 및 인트라넷 애플리케이션 모두에서 효율성을 높이기 위해 TCP 스택은 다음 조건을 사용하여 수신된 데이터 패킷에 대해 하나의 ACK를 보낼 시기를 결정합니다.
- 지연 타이머가 만료되기 전에 두 번째 데이터 패킷이 수신되면 ACK가 전송됩니다.
- 두 번째 데이터 패킷이 수신되고 지연 타이머가 만료되기 전에 ACK와 동일한 방향으로 전송할 데이터가 있는 경우 ACK는 데이터 세그먼트로 피기백되고 즉시 전송됩니다.
- 지연 타이머가 만료되면 ACK가 전송됩니다.
작은 데이터 패킷이 네트워크를 혼동하지 않도록 TCP 스택은 기본적으로 Nagle 알고리즘을 사용하도록 설정합니다. 이 알고리즘은 여러 송신 호출에서 작은 데이터 버퍼를 병합하고 전송된 이전 데이터 패킷에 대한 ACK가 원격 호스트에서 수신될 때까지 전송을 지연합니다. 다음은 Nagle 알고리즘에 대한 두 가지 예외입니다.
- 스택이 MTU(최대 전송 단위)보다 큰 데이터 버퍼를 병합한 경우 원격 호스트에서 ACK를 기다리지 않고 전체 크기의 패킷이 즉시 전송됩니다. 이더넷 네트워크에서 TCP/IP용 MTU는 1460바이트입니다.
- TCP_NODELAY 소켓 옵션은 작은 데이터 패킷이 지연 없이 원격 호스트에 전달되도록 Nagle 알고리즘을 사용하지 않도록 설정하기 위해 적용됩니다.
애플리케이션 계층에서 성능을 최적화하기 위해 Winsock은 애플리케이션에서 데이터 버퍼를 복사하여 Winsock 커널 버퍼에 대한 호출을 보냅니다. 그런 다음 스택은 자체 추론(예: Nagle 알고리즘)을 사용하여 패킷을 실제로 와이어에 넣을 시기를 결정합니다. 옵션을 사용하여 SO_SNDBUF 소켓에 할당된 Winsock 커널 버퍼의 양을 변경할 수 있습니다(기본적으로 8K임). 필요한 경우 Winsock은 버퍼 크기보다 더 많은 버퍼를 버퍼링할 SO_SNDBUF 수 있습니다. 대부분의 경우 애플리케이션에서 보내기 완료는 애플리케이션 보내기 호출의 데이터 버퍼가 Winsock 커널 버퍼에 복사되고 데이터가 네트워크 매체에 도달했음을 나타내지 않음을 나타냅니다. 유일한 예외는 Winsock 버퍼링을 0으로 설정 SO_SNDBUF 하여 사용하지 않도록 설정하는 경우입니다.
Winsock은 다음 규칙을 사용하여 애플리케이션에 대한 보내기 완료를 나타냅니다(송신이 호출되는 방법에 따라 완료 알림은 차단 호출에서 반환하거나, 이벤트를 알리거나, 알림 함수를 호출하는 함수일 수 있음)
- 소켓이 여전히 SO_SNDBUF 할당량 내에 있는 경우 Winsock은 애플리케이션 보내기에서 데이터를 복사하고 애플리케이션으로 보내기 완료를 나타냅니다.
- 소켓이 할당량을 초과 SO_SNDBUF 하고 이전에 버퍼링된 송신이 스택 커널 버퍼에 하나만 있는 경우 Winsock은 애플리케이션 보내기에서 데이터를 복사하고 애플리케이션에 보내기 완료를 나타냅니다.
- 소켓이 할당량을 초과 SO_SNDBUF 하고 스택 커널 버퍼에 이전에 버퍼링된 송신이 두 개 이상 있는 경우 Winsock은 애플리케이션 보내기에서 데이터를 복사합니다. Winsock은 스택이 충분한 보내기를 완료하여 할당량 내에 소켓 SO_SNDBUF 을 다시 넣거나 하나의 미해결 송신 조건만 완료할 때까지 애플리케이션에 대한 보내기 완료를 나타내지 않습니다.
사례 연구 1
Winsock TCP 클라이언트는 데이터베이스에 저장하기 위해 Winsock TCP 서버에 10000 레코드를 보내야 합니다. 레코드의 크기는 20바이트에서 100바이트까지 다양합니다. 애플리케이션 논리를 간소화하기 위해 디자인은 다음과 같습니다.
- 클라이언트는 보내기만 차단합니다. 서버는 차단 recv 만 수행합니다.
- 클라이언트 소켓은 각 레코드가 SO_SNDBUF 단일 데이터 세그먼트로 나가도록 0으로 설정합니다.
- 서버가 루프에서 호출 recv 합니다. 각 레코드를 한 번의 recv 호출로 recv 받을 수 있도록 게시되는 버퍼는 200바이트입니다.
성능
테스트하는 동안 개발자는 클라이언트가 서버에 초당 5개의 레코드만 보낼 수 있다는 것을 발견했습니다. 총 10000개 레코드는 최대 976kb의 데이터(10000 * 100/1024)로 서버로 보내는 데 반 시간 이상이 걸립니다.
분석
클라이언트가 옵션을 설정 TCP_NODELAY 하지 않기 때문에 Nagle 알고리즘은 TCP 스택이 ACK를 기다리도록 강제하여 유선에서 다른 패킷을 보낼 수 있습니다. 그러나 클라이언트는 옵션을 0으로 설정하여 Winsock 버퍼링을 사용하지 않도록 설정 SO_SNDBUF 했습니다. 따라서 10000 송신 통화를 보내고 ACK를 개별적으로 보내야 합니다. 서버의 TCP 스택에서 다음이 발생하므로 각 ACK는 200ms 지연됩니다.
- 서버가 패킷을 가져오면 200ms 지연 타이머가 꺼집니다.
- 서버는 아무것도 다시 보낼 필요가 없으므로 ACK를 피기백할 수 없습니다.
- 이전 패킷이 승인되지 않는 한 클라이언트는 다른 패킷을 보내지 않습니다.
- 서버의 지연 타이머가 만료되고 ACK가 다시 전송됩니다.
개선 방법
이 디자인에는 두 가지 문제가 있습니다. 먼저 지연 타이머 문제가 있습니다. 클라이언트는 200ms 이내에 두 개의 패킷을 서버에 보낼 수 있어야 합니다. 클라이언트는 기본적으로 Nagle 알고리즘을 사용하므로 기본 Winsock 버퍼링을 사용하고 0으로 설정 SO_SNDBUF 하지 않아야 합니다. TCP 스택이 MTU(최대 전송 단위)보다 큰 버퍼를 병합하면 원격 호스트에서 ACK를 기다리지 않고 전체 크기의 패킷이 즉시 전송됩니다.
둘째, 이 디자인은 이러한 작은 크기의 각 레코드에 대해 하나의 송신을 호출합니다. 이 작은 크기를 보내는 것은 효율적이지 않습니다. 이 경우 개발자는 각 레코드를 100바이트로 채우고 한 클라이언트 송신 호출에서 한 번에 80개의 레코드를 보낼 수 있습니다. 서버에서 총 전송되는 레코드 수를 알리기 위해 클라이언트는 따라야 할 레코드 수가 포함된 수정 크기 헤더와의 통신을 시작할 수 있습니다.
사례 연구 2
Winsock TCP 클라이언트 애플리케이션은 주식 견적 서비스를 제공하는 Winsock TCP 서버 애플리케이션과 두 개의 연결을 엽니다. 첫 번째 연결은 서버에 주식 기호를 보내는 명령 채널로 사용됩니다. 두 번째 연결은 주식 견적을 받기 위한 데이터 채널로 사용됩니다. 두 연결이 설정되면 클라이언트는 명령 채널을 통해 서버에 주식 기호를 보내고 주식 견적이 데이터 채널을 통해 돌아올 때까지 기다립니다. 첫 번째 주식 견적을 받은 후에만 다음 주식 기호 요청을 서버로 보냅니다. 클라이언트와 서버가 및 TCP_NODELAY 옵션을 설정 SO_SNDBUF 하지 않습니다.
- 성능
- 테스트하는 동안 개발자는 클라이언트가 초당 5개의 따옴표만 가져올 수 있다는 것을 알게 됩니다.
- 분석
- 이 디자인은 한 번에 하나의 미결제 주식 견적 요청만 허용합니다. 첫 번째 주식 기호는 명령 채널(연결)을 통해 서버로 전송되고 응답은 데이터 채널(연결)을 통해 서버에서 클라이언트로 즉시 다시 전송됩니다. 그런 다음 클라이언트는 두 번째 주식 기호 요청을 즉시 보내고 보내기 호출의 요청 버퍼가 Winsock 커널 버퍼에 복사되면 즉시 송신이 반환됩니다. 그러나 클라이언트 TCP 스택은 명령 채널을 통해 첫 번째 보내기가 아직 승인되지 않았기 때문에 커널 버퍼에서 요청을 즉시 보낼 수 없습니다. 서버 명령 채널의 200ms 지연 타이머가 만료되면 첫 번째 기호 요청에 대한 ACK가 클라이언트로 돌아갑니다. 그런 다음 두 번째 견적 요청이 200ms 지연된 후 서버로 성공적으로 전송됩니다. 이때 클라이언트 데이터 채널의 지연 타이머가 만료되었으므로 두 번째 주식 기호의 견적이 데이터 채널을 통해 즉시 다시 제공됩니다. 이전 견적 응답에 대한 ACK는 서버에서 수신됩니다. (클라이언트가 200ms에 대한 두 번째 주식 견적 요청을 보낼 수 없으므로 클라이언트의 지연 타이머가 만료되어 ACK를 서버로 보낼 수 있습니다.) 따라서 클라이언트는 두 번째 따옴표 응답을 가져오고 동일한 주기의 다른 따옴표 요청을 실행할 수 있습니다.
- 개선 방법
- 여기서는 두 연결(채널) 디자인이 필요하지 않습니다. 주식 견적 요청 및 응답에 단 하나의 연결만 사용하는 경우 견적 요청에 대한 ACK를 견적 응답에서 피기백하고 즉시 돌아올 수 있습니다. 성능을 더욱 향상시키기 위해 클라이언트는 여러 주식 견적 요청을 서버에 대한 하나의 송신 호출로 멀티플렉싱 할 수 있으며 서버는 여러 견적 응답을 클라이언트에 대한 하나의 송신 호출로 멀티플렉싱 할 수도 있습니다. 어떤 이유로 두 개의 단방향 채널 디자인이 필요한 경우 양측은 이전 패킷에 대한 ACK를 기다리지 않고도 작은 패킷을 즉시 보낼 수 있도록 옵션을 설정 TCP_NODELAY 해야 합니다.
권장 사항
이 2개의 케이스 연구 결과는 조작되는 동안, 몇몇 최악의 시나리오를 설명하는 것을 돕습니다. 광범위한 소규모 데이터 세그먼트가 전송하는 애플리케이션을 디자인하는 recvs경우 다음 지침을 고려해야 합니다.
- 데이터 세그먼트가 시간이 중요하지 않은 경우 애플리케이션은 더 큰 데이터 블록으로 병합하여 송신 호출에 전달해야 합니다. 송신 버퍼가 Winsock 커널 버퍼에 복사될 가능성이 높으므로 버퍼가 너무 크지 않아야 합니다. 8K보다 약간 작음이 효과적입니다. Winsock 커널이 MTU보다 큰 블록을 가져오는 한, 남아 있는 모든 패킷과 함께 여러 개의 전체 크기 패킷과 마지막 패킷을 보냅니다. 마지막 패킷을 제외한 전송 쪽은 200ms 지연 타이머에 의해 적중되지 않습니다. 마지막 패킷이 홀수 패킷인 경우 지연된 승인 알고리즘이 계속 적용됩니다. 송신 엔드 스택이 MTU보다 큰 다른 블록을 가져오는 경우에도 Nagle 알고리즘을 무시할 수 있습니다.
- 가능하면 단방향 데이터 흐름과 소켓 연결을 사용하지 마세요. 단방향 소켓을 통한 통신은 Nagle 및 지연된 승인 알고리즘의 영향을 더 쉽게 받습니다. 통신이 요청 및 응답 흐름을 따르는 경우 단일 소켓을 사용하여 두 송신 recvs 을 모두 수행하고 응답에서 ACK를 피기백할 수 있도록 해야 합니다.
- 모든 작은 데이터 세그먼트를 즉시 보내야 하는 경우 전송 끝에 옵션을 설정합니다 TCP_NODELAY .
- Winsock에서 송신 완료가 표시될 때 패킷이 유선으로 전송되도록 보장하지 않으려면 0으로 설정 SO_SNDBUF 하면 안 됩니다. 실제로 기본 8K 버퍼는 대부분의 상황에서 잘 작동하도록 추론적으로 결정되었으며, 새 Winsock 버퍼 설정이 기본값보다 더 나은 성능을 제공하는지 테스트하지 않는 한 이를 변경해서는 안 됩니다. 또한 0으로 설정 SO_SNDBUF 하면 대량 데이터 전송을 수행하는 애플리케이션에 주로 유용합니다. 그럼에도 불구하고 효율성을 극대화하려면 이중 버퍼링(지정된 시간에 둘 이상의 미해결 송신)과 겹치는 I/O와 함께 사용해야 합니다.
- 데이터 배달을 보장할 필요가 없는 경우 UDP를 사용합니다.
참조
지연된 승인 및 Nagle 알고리즘에 대한 자세한 내용은 다음을 참조하세요.
Braden, R.[1989], RFC 1122, 인터넷 호스트 요구 사항--통신 계층, 인터넷 엔지니어링 태스크 포스.
'[Microsoft]' 카테고리의 다른 글
Win32_NetworkAdapterConfiguration 클래스는 PPPoE(이더넷을 통해 지점 간 프로토콜) 및 VPN(가상 사설망)에 대한 정보를 검색할 수 없습니다. (0) | 2023.11.03 |
---|---|
DnsQuery 함수를 사용하여 Visual C++ .NET으로 호스트 이름 및 호스트 주소 확인 (0) | 2023.11.03 |
느린 사용으로 비차단 소켓을 닫으면 누출이 발생할 수 있습니다. (0) | 2023.11.03 |
IPPROTO_IP 수준에서 소켓 옵션을 설정/가져올 때 헤더 및 라이브러리 요구 사항 (0) | 2023.11.03 |
BeginRead 메서드로 인해 InvalidOperationException이 발생합니다. (0) | 2023.11.03 |