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



[최초 등록일: ]
[최종 수정일: 2/6/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)
12633정성태5/7/202133개발 환경 구성: 571. UAC - 관리자 권한 없이 UIPI 제약을 없애는 방법
12632정성태5/8/202137기타: 80. (WACOM도 지원하는) Tablet 공통 디바이스 드라이버 - OpenTabletDriver
12631정성태5/7/202155사물인터넷: 60. ThingSpeak 사물인터넷 플랫폼에 ESP8266 NodeMCU v1 + 조도 센서 장비 연동파일 다운로드1
12630정성태5/7/202136사물인터넷: 59. NodeMCU v1 ESP8266 보드의 A0 핀 사용법 - Cds Cell(GL3526) 조도 센서 연동파일 다운로드1
12629정성태5/5/202190.NET Framework: 1057. C# - CoAP 서버 및 클라이언트 제작 (UDP 소켓 통신)파일 다운로드1
12628정성태5/4/202114Linux: 39. Eclipse 원격 디버깅 - Cannot run program "gdb": Launching failed
12627정성태5/4/202123Linux: 38. 라즈베리 파이 제로 용 프로그램 개발을 위한 Eclipse C/C++ 윈도우 환경 설정
12626정성태5/3/202145.NET Framework: 1056. C# - Thread.Suspend 호출 시 응용 프로그램 hang 현상 (2)파일 다운로드1
12625정성태5/3/202125오류 유형: 714. error CS5001: Program does not contain a static 'Main' method suitable for an entry point
12624정성태5/3/2021194.NET Framework: 1055. C# - struct/class가 스택/힙에 할당되는 사례 정리 [6]파일 다운로드1
12623정성태5/2/202186.NET Framework: 1054. C# 9 최상위 문에 STAThread 사용파일 다운로드1
12622정성태5/3/202119오류 유형: 713. XSD 파일을 포함한 프로젝트 - The type or namespace name 'TypedTableBase<>' does not exist in the namespace 'System.Data'
12621정성태5/1/2021117.NET Framework: 1053. C# - 특정 레지스트리 변경 시 알림을 받는 방법파일 다운로드1
12620정성태5/3/2021319.NET Framework: 1052. C# - 왜 구조체는 16 바이트의 크기가 적합한가? [1]파일 다운로드1
12619정성태4/29/2021272.NET Framework: 1051. C# - 구조체의 크기가 16바이트가 넘어가면 힙에 할당된다? [1]파일 다운로드1
12618정성태5/7/202196사물인터넷: 58. NodeMCU v1 ESP8266 CP2102 Module을 이용한 WiFi UDP 통신파일 다운로드1
12617정성태4/26/202160.NET Framework: 1050. C# - ETW EventListener의 Keywords 별 EventId에 따른 필터링 방법파일 다운로드1
12616정성태4/26/2021120.NET Framework: 1049. C# - ETW EventListener를 상속받았을 때 초기화 순서파일 다운로드1
12615정성태4/26/202142오류 유형: 712. Microsoft Live 로그인 - 계정을 선택하는(Pick an account) 화면에서 진행이 안 되는 문제
12614정성태4/24/2021114개발 환경 구성: 570. C# - Azure AD 인증을 지원하는 ASP.NET Core 5.0 웹 애플리케이션 예제 구성파일 다운로드1
12613정성태4/23/202192.NET Framework: 1048. C# - ETW 이벤트의 Keywords에 속한 EventId 구하는 방법 (2) 관리 코드파일 다운로드1
12612정성태4/23/2021125.NET Framework: 1047. C# - ETW 이벤트의 Keywords에 속한 EventId 구하는 방법 (1) PInvoke파일 다운로드1
12611정성태4/22/2021140오류 유형: 711. 닷넷 EXE 실행 오류 - Mixed mode assembly is build against version 'v2.0.50727' of the runtime
12610정성태4/22/2021175.NET Framework: 1046. C# - 컴파일 시점에 참조할 수 없는 타입을 포함한 이벤트 핸들러를 Reflection을 이용해 구독하는 방법파일 다운로드1
12609정성태4/22/2021138.NET Framework: 1045. C# - 런타임 시점에 이벤트 핸들러를 만들어 Reflection을 이용해 구독하는 방법파일 다운로드1
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...