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

비밀번호

댓글 작성자
 




... 16  17  18  19  20  21  22  [23]  24  25  26  27  28  29  30  ...
NoWriterDateCnt.TitleFile(s)
13045정성태5/5/20226829.NET Framework: 2001. C# XingAPI - 주식 종목에 따른 PBR, PER, ROE 구하는 방법(t3341 예제)
13044정성태5/4/20226295오류 유형: 808. error : clang++ exited with code 127
13043정성태5/3/20225930오류 유형: 807. C# - 닷넷 응용 프로그램에서 Informix DB 사용 시 오류 메시지 정리
13042정성태5/3/20226321.NET Framework: 2000. C# - 닷넷 응용 프로그램에서 Informix DB 사용 방법파일 다운로드1
13041정성태4/28/20226581개발 환경 구성: 642. Informix 데이터베이스 docker 환경 구성
13040정성태4/27/20227124VC++: 156. 비주얼 스튜디오 - Linux C/C++ 프로젝트에서 openssl 링크하는 방법
13039정성태4/27/20227885.NET Framework: 1999. C# - Playwright를 이용한 간단한 브라우저 제어 실습
13038정성태4/26/20225803오류 유형: 806. twine 실행 시 ConfigParser.ParsingError: File contains parsing errors: /root/.pypirc
13037정성태4/25/20226112.NET Framework: 1998. Azure Functions를 사용한 간단한 실습
13036정성태4/24/20226836.NET Framework: 1997. C# - nano 시간을 가져오는 방법 [2]
13035정성태4/22/20227398Windows: 204. Windows 10부터 바뀐 QueryPerformanceFrequency, QueryPerformanceCounter
13034정성태4/21/20226817.NET Framework: 1996. C# XingAPI - 주식 종목에 따른 PBR, PER, ROE, ROA 구하는 방법(t3320, t8430 예제)파일 다운로드1
13033정성태4/18/20227408.NET Framework: 1195. C# - Thread.Yield와 Thread.Sleep(0)의 차이점(?)
13032정성태4/17/20227111오류 유형: 805. Github의 50MB 파일 크기 제한 - warning: GH001: Large files detected. You may want to try Git Large File Storage
13031정성태4/15/20226658.NET Framework: 1194. C# - IdealProcessor와 ProcessorAffinity의 차이점
13030정성태4/15/20226334오류 유형: 804. 정규 표현식 오류 - Quantifier {x,y} following nothing.
13029정성태4/14/20226747Windows: 203. iisreset 후에도 이전에 설정한 전역 환경 변수가 w3wp.exe에 적용되는 문제
13028정성태4/13/20226656.NET Framework: 1193. (appsettings.json처럼) web.config의 Debug/Release에 따른 설정 적용
13027정성태4/12/20226936.NET Framework: 1192. C# - 환경 변수의 변화를 알리는 WM_SETTINGCHANGE Win32 메시지 사용법파일 다운로드1
13026정성태4/11/20228445.NET Framework: 1191. C 언어로 작성된 FFmpeg Examples의 C# 포팅 전체 소스 코드 [3]
13025정성태4/11/20227807.NET Framework: 1190. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 vaapi_encode.c, vaapi_transcode.c 예제 포팅
13024정성태4/7/20226318.NET Framework: 1189. C# - 런타임 환경에 따라 달라진 AppDomain.GetCurrentThreadId 메서드
13023정성태4/6/20226631.NET Framework: 1188. C# - ffmpeg(FFmpeg.AutoGen)를 이용한 transcoding.c 예제 포팅 [3]
13022정성태3/31/20226513Windows: 202. 윈도우 11 업그레이드 - "PC Health Check"를 통과했지만 여전히 업그레이드가 안 되는 경우 해결책
13021정성태3/31/20226670Windows: 201. Windows - INF 파일을 이용한 장치 제거 방법
13020정성태3/30/20226420.NET Framework: 1187. RDP 접속 시 WPF UserControl의 Unloaded 이벤트 발생파일 다운로드1
... 16  17  18  19  20  21  22  [23]  24  25  26  27  28  29  30  ...