Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일

Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬

근래의 환경에서, Nagle 알고리즘을 명시적으로 끄는 작업이 필요할까요? (정말 몰라서 묻는 겁니다. ^^)

예를 들어, TCP 소켓에서 다음과 같이 1 바이트를 보내면,

byte[] buf = new byte[1];
client.Send(buf, 0, buf.Length, SocketFlags.None);

Socket.NoDelay 속성의 기본값은 false이므로 Nagle 알고리듬을 사용하는 상태입니다. 따라서 우리가 알고 있는 지식이라면 1 바이트를 곧바로 보내지 않고 약간의 시간 차를 둬야 하는데, 실제로는 곧바로 전송이 됩니다.

그리고 이때의 패킷을 wireshark로 보면,

..client_ip... ..server_ip... TCP 55 1318 → 15000 [PSH, ACK] Seq=1 Ack=1 Win=262656 Len=1

그러한 역할을 하도록 PSH 플래그가 설정돼 있습니다. PSH가 어떤 것인지에 대해서는 아래의 글에서 잘 설명하고 있습니다.

TCP Flags: PSH 그리고 URG
; https://techlog.gurucat.net/314

즉, PSH 플래그가 설정된 패킷은 버퍼가 어느 정도 채워졌는지 고려할 필요 없이 곧바로 송신하는 역할을 담당합니다. 게다가 수신 측에서도 PSH가 설정된 패킷을 받으면 수신 버퍼의 상태를 고려하지 않고 곧바로 ACK 신호를 처리해 응용 프로그램의 recv 호출에 반응할 수 있게 합니다.

(딱히 이력을 찾을 수 없는데 원래부터 그랬던 것인지는 알 수 없으나) 어쨌든 적어도 Windows 10 환경에서 현재 TCP 소켓을 테스트해 보면, Socket.send 시 무조건 마지막 패킷에 PSH 플래그를 붙여 전송합니다. 즉, 1 바이트를 send하면 1바이트를 보내는 TCP 패킷에 PSH가 설정돼 위에서 테스트했던 것처럼 바로 전송이 되고, 20,000 바이트를 send하는 경우에는 다음과 같이 패킷 분할이 발생할 텐데,

4  3.304470 ..client_ip... ..server_ip... TCP 14654 2820 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=14600
5  3.308772 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=2921 Win=131328 Len=0
6  3.308772 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=5841 Win=131328 Len=0
7  3.308820 ..client_ip... ..server_ip... TCP 5454 2820 → 15000 [PSH, ACK] Seq=14601 Ack=1 Win=262656 Len=5400
8  3.310578 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=8761 Win=131328 Len=0
9  3.310578 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=11681 Win=131328 Len=0
10 3.310578 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=14601 Win=131328 Len=0
11 3.314316 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=17521 Win=131328 Len=0
12 3.314316 ..server_ip... ..client_ip... TCP 60 15000 → 2820 [ACK] Seq=1 Ack=20001 Win=131328 Len=0

(send로 넘겨준 앞 부분의) 첫 패킷은 PSH 플래그가 없지만, 두 번째 (send에 넘겨줬던 마지막) 패킷에는 PSH를 붙여 전송합니다.




그런데, 위의 상황을 가만히 보면 기존의 Nagle 알고리듬을 꺼야 했던 상황과 똑같습니다. 단지 다른 점이 있다면, Nagle은 송신 측에서 작은 데이터를 일정 시간 동안 대기해 버퍼가 찰 기회를 줬다가 전송하는 기능이므로, 이것을 끄게 되면 곧바로 패킷에 담아 전송하는 역할만 합니다. 즉, 이것은 전송에서만 유효하며 수신 측 입장에서 보면 1바이트가 왔다고 해서 곧바로 ACK를 하고 응용 프로그램에 올려 보낼 것인지는 사실 Nagle 알고리듬 자체와는 무관한 문제입니다.

물론, Nagle 알고리듬이 꺼지면 PSH 플래그를 붙여 처리할 수도 있을 것입니다. 실제로 텔넷 같은 프로그램이 Nagle을 껐을 것이고, 패킷 캡처를 했을 때 PSH 플래그가 설정되었다는 것에서 그 사실을 유추할 수 있습니다.

그러니까, 현재의 기능을 비교해 보면 굳이 Nagle 알고리듬을 끄든지/켜든지 달라지는 것이 없습니다.

Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.NoDelay = false; // 이 코드에 상관없이 send는 곧바로 해당 데이터를 서버로 바로 전송

분명히 예전 자료를 보면, telnet 등의 서비스를 구현할 때 키보드 입력에 대한 반응성을 향상시키기 위해 Nagle을 꺼야 하는 옵션이 사용되었다는 것을 감안할 때 아마도 이전에는 send 호출 시 마지막에 자동으로 붙였던 것은 아닌 듯합니다. (혹시 이에 대한 이력을 아시는 분은 덧글 부탁드립니다. ^^)




제가 찾아본 공식 문서로는, 원래 RFC793 문서에 PSH 플래그의 역할을 찾아볼 수 있습니다.

TRANSMISSION CONTROL PROTOCOL
DARPA INTERNET PROGRAM
PROTOCOL SPECIFICATION
; https://tools.ietf.org/html/rfc793

2.8.  Data Communication

  The data that flows on a connection may be thought of as a stream of
  octets.  The sending user indicates in each SEND call whether the data
  in that call (and any preceeding calls) should be immediately pushed
  through to the receiving user by the setting of the PUSH flag.

  A sending TCP is allowed to collect data from the sending user and to
  send that data in segments at its own convenience, until the push
  function is signaled, then it must send all unsent data.  When a
  receiving TCP sees the PUSH flag, it must not wait for more data from
  the sending TCP before passing the data to the receiving process.

  There is no necessary relationship between push functions and segment
  boundaries.  The data in any particular segment may be the result of a
  single SEND call, in whole or part, or of multiple SEND calls.

  The purpose of push function and the PUSH flag is to push data through
  from the sending user to the receiving user.  It does not provide a
  record service.

  There is a coupling between the push function and the use of buffers
  of data that cross the TCP/user interface.  Each time a PUSH flag is
  associated with data placed into the receiving user's buffer, the
  buffer is returned to the user for processing even if the buffer is
  not filled.  If data arrives that fills the user's buffer before a
  PUSH is seen, the data is passed to the user in buffer size units.

  TCP also provides a means to communicate to the receiver of data that
  at some point further along in the data stream than the receiver is
  currently reading there is urgent data.  TCP does not attempt to
  define what the user specifically does upon being notified of pending
  urgent data, but the general notion is that the receiving process will
  take action to process the urgent data quickly.

그리고 RFC1122 문서에는 이를 좀 더 부연 설명하는데, 여기에 보면 SEND 호출이 반드시 PUSH 플래그를 설정하도록 강제하는 것은 아님을 알 수 있습니다.

Requirements for Internet Hosts -- Communication Layers
; https://tools.ietf.org/html/rfc1122

4.2.2.2  Use of Push: RFC-793 Section 2.8

            When an application issues a series of SEND calls without
            setting the PUSH flag, the TCP MAY aggregate the data
            internally without sending it.  Similarly, when a series of
            segments is received without the PSH bit, a TCP MAY queue
            the data internally without passing it to the receiving
            application.

            The PSH bit is not a record marker and is independent of
            segment boundaries.  The transmitter SHOULD collapse
            successive PSH bits when it packetizes data, to send the
            largest possible segment.

            A TCP MAY implement PUSH flags on SEND calls.  If PUSH flags
            are not implemented, then the sending TCP: (1) must not
            buffer data indefinitely, and (2) MUST set the PSH bit in
            the last buffered segment (i.e., when there is no more
            queued data to be sent).

            The discussion in RFC-793 on pages 48, 50, and 74
            erroneously implies that a received PSH flag must be passed
            to the application layer.  Passing a received PSH flag to
            the application layer is now OPTIONAL.

            An application program is logically required to set the PUSH
            flag in a SEND call whenever it needs to force delivery of
            the data to avoid a communication deadlock.  However, a TCP
            SHOULD send a maximum-sized segment whenever possible, to
            improve performance (see Section 4.2.3.4).

            DISCUSSION:
                 When the PUSH flag is not implemented on SEND calls,
                 i.e., when the application/TCP interface uses a pure
                 streaming model, responsibility for aggregating any
                 tiny data fragments to form reasonable sized segments
                 is partially borne by the application layer.

                 Generally, an interactive application protocol must set
                 the PUSH flag at least in the last SEND call in each
                 command or response sequence.  A bulk transfer protocol
                 like FTP should set the PUSH flag on the last segment
                 of a file or when necessary to prevent buffer deadlock.

                 At the receiver, the PSH bit forces buffered data to be
                 delivered to the application (even if less than a full
                 buffer has been received). Conversely, the lack of a
                 PSH bit can be used to avoid unnecessary wakeup calls
                 to the application process; this can be an important
                 performance optimization for large timesharing hosts.
                 Passing the PSH bit to the receiving application allows
                 an analogous optimization within the application.

그러니까, send 호출에 대한 push flag를 설정하는 것은 TCP 구현 스택의 임의 재량에 속하는 것입니다. 실제로 과거의 윈도우 KB 자료를 보면,

Design issues - Sending small data segments over TCP with Winsock
; https://learn.microsoft.com/en-us/troubleshoot/windows/win32/data-segment-tcp-winsock

send 호출마다 PSH가 설정된다는 식의 언급은 전혀 찾아볼 수 없고 오직 Nagle 이야기만 하고 있습니다. 아마도 저 시절에는 send에 전달한 데이터의 마지막 패킷에 PSH를 붙이는 것은 구현하지 않았을 수 있습니다.




참고로, "TCP Flags: PSH 그리고 URG" 글에서 약간 혼란의 여지가 있는 내용이 있다면 PSH 플래그를 응용 프로그램(Application Layer)에서 제어할 수 있는 것처럼 설명하고 있습니다. 그리고 위에서 인용했던 RFC 문서에도 "When an application issues a series of SEND calls without setting the PUSH flag,"라고 언급하면서 응용 프로그램 레벨에서 PUSH 플래그를 다룰 수 있는 것처럼 설명하지만, 실제로는 제어할 수 없습니다. 제가 코드상으로 방법을 찾아본 바도 그렇고, (공식 문서는 아니지만) 아래의 글에서도 이에 대한 설명을 하고 있습니다.

Improve latency for TCP by not waiting for Push flag
; http://smallvoid.com/article/winnt-tcp-push-flag.html

The setting of the Push Flag is usually not controlled by the sending application, but by the sending TCP layer.


마지막으로, 혹시 시간 되시면 다음과 같은 글들도 읽어 보시길. ^^

Improve latency for TCP by not waiting for Push flag
; http://smallvoid.com/article/winnt-tcp-push-flag.html

recv - IgnorePushBitOnReceives
; https://learn.microsoft.com/en-us/previous-versions/windows/embedded/ms911740(v=msdn.10)

첨부 파일 문서 - TCPIP_2003.doc (Windows Server 2003 TCPIP 문서)
; https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1727&boardid=331301885

Delayed ACK and Nagle’s Algorithm
; https://noisy.network/2017/02/06/delayed-ack-and-nagles-algorithm/




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]







[최초 등록일: ]
[최종 수정일: 1/17/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13370정성태6/14/20233288개발 환경 구성: 680. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 - TLS 1.2 지원
13369정성태6/13/20233085개발 환경 구성: 679. PyCharm(을 비롯해 JetBrains에 속한 여타) IDE에서 내부 Window들의 탭이 없어진 경우
13368정성태6/13/20233222개발 환경 구성: 678. openssl로 생성한 인증서를 SQL Server의 암호화 인증서로 설정하는 방법
13367정성태6/10/20233321오류 유형: 864. openssl로 만든 pfx 인증서를 Windows Server 2016 이하에서 등록 시 "The password you entered is incorrect" 오류 발생
13366정성태6/10/20233111.NET Framework: 2128. C# - 윈도우 시스템에서 지원하는 암호화 목록(Cipher Suites) 나열파일 다운로드1
13365정성태6/8/20232883오류 유형: 863. MODIFY FILE encountered operating system error 112(failed to retrieve text for this error. Reason: 15105)
13364정성태6/8/20233665.NET Framework: 2127. C# - Ubuntu + Microsoft.Data.SqlClient + SQL Server 2008 R2 연결 방법 [1]
13363정성태6/7/20233233스크립트: 49. 파이썬 - "Transformers (신경망 언어모델 라이브러리) 강좌" - 1장 2절 코드 실행 결과
13362정성태6/1/20233153.NET Framework: 2126. C# - 서버 측의 요청 제어 (Microsoft.AspNetCore.RateLimiting)파일 다운로드1
13361정성태5/31/20233611오류 유형: 862. Facebook - ASP.NET/WebClient 사용 시 graph.facebook.com/me 호출에 대해 403 Forbidden 오류
13360정성태5/31/20233022오류 유형: 861. WSL/docker - failed to start shim: start failed: io.containerd.runc.v2: create new shim socket
13359정성태5/19/20233343오류 유형: 860. Docker Desktop - k8s 초기화 무한 반복한다면?
13358정성태5/17/20233635.NET Framework: 2125. C# - Semantic Kernel의 Semantic Memory 사용 예제 [1]파일 다운로드1
13357정성태5/16/20233457.NET Framework: 2124. C# - Semantic Kernel의 Planner 사용 예제파일 다운로드1
13356정성태5/15/20233752DDK: 10. Device Driver 테스트 설치 관련 오류 (Code 37, Code 31) 및 인증서 관련 정리
13355정성태5/12/20233664.NET Framework: 2123. C# - Semantic Kernel의 ChatGPT 대화 구현 [1]파일 다운로드1
13354정성태5/12/20233928.NET Framework: 2122. C# - "Use Unicode UTF-8 for worldwide language support" 설정을 한 경우, 한글 입력이 '\0' 문자로 처리
13352정성태5/12/20233546.NET Framework: 2121. C# - Semantic Kernel의 대화 문맥 유지파일 다운로드1
13351정성태5/11/20234055VS.NET IDE: 185. Visual Studio - 원격 Docker container 내에 실행 중인 응용 프로그램에 대한 디버깅 [1]
13350정성태5/11/20233318오류 유형: 859. Windows Date and Time - Unable to continue. You do not have permission to perform this task
13349정성태5/11/20233643.NET Framework: 2120. C# - Semantic Kernel의 Skill과 Function 사용 예제파일 다운로드1
13348정성태5/10/20233566.NET Framework: 2119. C# - Semantic Kernel의 "Basic Loading of the Kernel" 예제
13347정성태5/10/20233924.NET Framework: 2118. C# - Semantic Kernel의 Prompt chaining 예제파일 다운로드1
13346정성태5/10/20233758오류 유형: 858. RDP 원격 환경과 로컬 PC 간의 Ctrl+C, Ctrl+V 복사가 안 되는 문제
13345정성태5/9/20235026.NET Framework: 2117. C# - (OpenAI 기반의) Microsoft Semantic Kernel을 이용한 자연어 처리 [1]파일 다운로드1
13344정성태5/9/20236306.NET Framework: 2116. C# - OpenAI API 사용 - 지원 모델 목록 [1]파일 다운로드1
1  2  3  4  5  6  7  8  9  [10]  11  12  13  14  15  ...