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://docs.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://docs.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/




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



donaricano-btn



[최초 등록일: ]
[최종 수정일: 7/9/2021]

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)
12551정성태3/5/2021662오류 유형: 702. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly. (2)
12550정성태3/5/2021603오류 유형: 701. Live Share 1.0.3713.0 버전을 1.0.3884.0으로 업데이트 이후 ContactServiceModelPackage 오류 발생하는 문제
12549정성태3/4/2021633오류 유형: 700. VsixPublisher를 이용한 등록 시 다양한 오류 유형 해결책
12548정성태3/4/2021678개발 환경 구성: 546. github workflow/actions에서 nuget 패키지 등록하는 방법
12547정성태3/3/2021904오류 유형: 699. 비주얼 스튜디오 - The 'CascadePackage' package did not load correctly.
12546정성태3/3/2021765개발 환경 구성: 545. github workflow/actions에서 빌드시 snk 파일 다루는 방법 - Encrypted secrets
12545정성태3/2/20211511.NET Framework: 1026. 닷넷 5에 추가된 POH (Pinned Object Heap) [7]
12544정성태2/26/20211169.NET Framework: 1025. C# - Control의 Invalidate, Update, Refresh 차이점 [2]
12543정성태2/26/20211118VS.NET IDE: 158. C# - 디자인 타임(design-time)과 런타임(runtime)의 코드 실행 구분
12542정성태2/20/20211265개발 환경 구성: 544. github repo의 Release 활성화 및 Actions를 이용한 자동화 방법
12541정성태2/18/2021993개발 환경 구성: 543. 애저듣보잡 - Github Workflow/Actions 소개
12540정성태2/17/2021991.NET Framework: 1024. C# - Win32 API에 대한 P/Invoke를 대신하는 Microsoft.Windows.CsWin32 패키지
12539정성태2/16/20211078Windows: 189. WM_TIMER의 동작 방식 개요파일 다운로드1
12538정성태2/15/20211273.NET Framework: 1023. C# - GC 힙이 아닌 Native 힙에 인스턴스 생성 - 0SuperComicLib.LowLevel 라이브러리 소개 [2]
12537정성태2/11/20211366.NET Framework: 1022. UI 요소의 접근은 반드시 그 UI를 만든 스레드에서! - 두 번째 이야기
12536정성태2/9/20211309개발 환경 구성: 542. BDP(Bandwidth-delay product)와 TCP Receive Window
12535정성태2/9/2021907개발 환경 구성: 541. Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
12534정성태2/8/20211223개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작 [1]파일 다운로드1
12533정성태2/8/20211189개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작파일 다운로드1
12532정성태2/6/20211057개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF) [1]
12531정성태2/5/2021941개발 환경 구성: 537. Wireshark + C#으로 확인하는 PSH flag와 Nagle 알고리듬파일 다운로드1
12530정성태2/4/20211402개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
12529정성태2/4/20211012개발 환경 구성: 535. Wireshark + C#으로 확인하는 TCP 통신의 MIN RTO [1]
12528정성태2/1/20211082개발 환경 구성: 534. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 윈도우 환경
12527정성태2/1/20211067개발 환경 구성: 533. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 리눅스 환경파일 다운로드1
12526정성태2/1/2021862개발 환경 구성: 532. Azure Devops의 파이프라인 빌드 시 snk 파일 다루는 방법 - Secure file
1  2  3  4  5  6  7  8  9  10  11  12  [13]  14  15  ...