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

Wireshark + C#으로 확인하는 TCP 통신의 Receive Window

검색해 보면, 이에 관해 자세한 설명이 나온 글을 쉽게 찾을 수 있습니다.

TCP series #4: TCP receive window and everything you need to know about it
; https://accedian.com/blog/tcp-receive-window-everything-need-know/

그래도, 이쪽 분야가 워낙 빠른지라 무턱대고 저 내용을 그대로 믿을 수는 없으니 정말 그런지 한 번 테스트를 해보겠습니다. ^^

이를 위해 지난 글의 예제 코드를 그대로 재사용해 서버는 Azure VM에 올려 두고, 클라이언트도 제가 가진 물리 서버의 VM에 올려 둔 후, 연결해 패킷 캡처를 해봤습니다.

2016 48.517893 ..client_ip... ..server_ip.. TCP	66 2865 → 15000 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM=1
2018 48.522650 ..server_ip.. ..client_ip... TCP	66 15000 → 2865 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1440 WS=256 SACK_PERM=1
2019 48.522716 ..client_ip... ..server_ip.. TCP	54 2865 → 15000 [ACK] Seq=1 Ack=1 Win=263424 Len=0

예전에는 Window 크기를 TCP Header에 2바이트로 보관해 총 65535 바이트까지 지정할 수 있었다고 합니다. 하지만 네트워크의 발전으로 64K는 너무 작아 TCP Header에 추가 Option 필드로 "Window scale" 값을 포함해 이를 곱하는 방식으로 보완했습니다. 현재는 Scale 값으로 1바이트를 점유하는데, 재미있는 것은 이 값이 2의 제곱을 나타내기 때문에 Wireshark에서 출력한 WS=256에 대해 실제 패킷에는 log2(256) = 8이 담겨 있습니다. 그러니까 이론 상 Win = 65535, WS=14(Scale 허용 최댓값 14: 2^14 = 16384)라면 최대 1,073,725,440(1GB)까지 Window Size를 지정할 수 있습니다.

이를 감안해 위의 2016번과, 2018번 패킷에 있는 Window 크기는 다음과 같이 "정상적이지 않게" 계산할 수 있습니다.

[클라이언트 측의 Receive Window 크기]
Win=64240
WS=256, (Scale = 8, 2^8 == 256)
윈도우 크기 = 64240 * 256 = 16,445,440 (약 16MB)

[서버 측의 Receive Window 크기]
Win=8192
WS=256, (Scale = 8, 2^8 == 256)
윈도우 크기 = 8192 * 256 = 2,097,152 (2MB)

왜 저것이 "정상적이지 않은" 크기인지 다음의 문서에서 이를 설명합니다.

Windows scaling
; https://docs.microsoft.com/en-us/troubleshoot/windows-server/networking/description-tcp-features#windows-scaling

It's important to note that the window size used in the actual three-way handshake is NOT the window size that is scaled. This is per RFC 1323 section 2.2, "The Window field in a SYN (for example, a [SYN] or [SYN,ACK]) segment itself is never scaled."
This means that the first data packet sent after the three-way handshake is the actual window size. If there is a scaling factor, the initial window size of 65,535 bytes is always used. The window size is then multiplied by the scaling factor identified in the three-way handshake.


따라서 (3-way handshake의) SYN 패킷에 있는 Window Size는 (양측 모두 scale 값을 설정한 경우) 버리는 값이고, 오직 Scale 값만이 유효해 이후 통신에서 해당 Scale 값을 재사용한다는 점입니다. 예를 들어, 저렇게 연결을 맺은 통신에서 5,000 바이트를 클라이언트에서 서버로 보내면 이런 패킷이 나옵니다.

252 5.875126 ..client_ip... ..server_ip.. TCP 5054 1093 → 15000 [PSH, ACK] Seq=1 Ack=1 Win=263424 Len=5000
253 5.880317 ..server_ip.. ..client_ip... TCP 60  15000 → 1093 [ACK] Seq=1 Ack=4321 Win=262656 Len=0
254 5.880317 ..server_ip.. ..client_ip... TCP 60  15000 → 1093 [ACK] Seq=1 Ack=5001 Win=261888 Len=0

위에서 wireshark가 보여주는 Win 값은 편의상 계산된 값이며 실제로는 해당 패킷에 window size만 각각 다음과 같이 2바이트로 표현해 전송합니다.

252번 패킷 Window size value = 0x0405(1029)
253번 패킷 Window size value = 0x0402(1026)
254번 패킷 Window size value = 0x03ff(1023)

그리고, 각각 연결 시에 알렸던 scale 값을 곱하면 wireshark가 보여준 Win=... 값을 확인할 수 있습니다.

252번 패킷 1029 * 256 = 263424
253번 패킷 1026 * 256 = 262656
254번 패킷 1023 * 256 = 261888

이런 이유로 네트워크 성능 도구들은 처음 연결 단계의 패킷을 잡지 못한 경우 이후 TCP 패킷들의 Window 크기를 정상적으로 보여줄 수 없습니다. 일례로, wireshark도 TCP 연결이 맺어진 다음 패킷 모니터링을 시작하면 다음과 같이 Win 크기를 scale 계산이 안 된 값으로 보여줘 매우 작게 나옵니다.

1 0.000000 ..client_ip... ..server_ip... TCP 1054 34069 → 15000 [PSH, ACK] Seq=1 Ack=1 Win=1026 Len=1000
2 0.059503 ..server_ip... ..client_ip... TCP 60 15000 → 34069 [ACK] Seq=1 Ack=1001 Win=509 Len=0

그리고 이때의 TCP 상세 설명을 보면 "[Window size scaling factor: -1 (unknown)]"라고 출력합니다.

참고로, 어느 한쪽이라도 (3-way handshake 단계에서) scale을 사용한다고 명시하지 않으면 양 측 모두 이 기능을 사용하지 않습니다. (아마도 이를 대비해 Window Size에도 기본적으로는 값을 설정해서 보내는 듯합니다.)




이쯤에서 다시 3-way handshake 패킷을 볼까요?

2016 48.517893 ..client_ip... ..server_ip.. TCP 66 2865 → 15000 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM=1
2018 48.522650 ..server_ip... .client_ip... TCP 66 15000 → 2865 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1440 WS=256 SACK_PERM=1
2019 48.522716 ..client_ip... ..server_ip.. TCP 54 2865 → 15000 [ACK] Seq=1 Ack=1 Win=263424 Len=0

마지막에 (기준 scale 값을 제공하는) SYN 패킷이 아닌 ACK 하나가 클라이언트에서 서버로 전달되는 과정이 있는데요, 바로 저기에 포함된 263,424는 정확히 클라이언트의 TCP Receive Window 크기를 의미합니다. 이렇게 따지고 보면, TCP 통신에서 3-way handshake 단계 중 상대방의 TCP Receive Window 크기를 알 수 있는 것은 서버 측만 가능합니다. 달리 말하면, 클라이언트의 경우에는 직접 통신하면서 받게되는 이후의 패킷에서만 서버의 Receive Window 크기를 알 수 있다는 건데요, 실제로 위에서 5,000 바이트를 전송했을 때 서버로부터의 ACK 패킷을 보면,

252 5.875126 ..client_ip... ..server_ip..  TCP 5054    1093 → 15000 [PSH, ACK] Seq=1 Ack=1 Win=263424 Len=5000
253 5.880317 ..server_ip..  ..client_ip... TCP 60  15000 → 1093 [ACK] Seq=1 Ack=4321 Win=262656 Len=0
254 5.880317 ..server_ip..  ..client_ip... TCP 60  15000 → 1093 [ACK] Seq=1 Ack=5001 Win=261888 Len=0

그제서야 클라이언트는 서버의 Receive Window 크기를 인지하게 됩니다. 그렇다면, 클라이언트가 먼저 서버로 데이터를 보내는 경우 어떻게 서버의 TCP Receive Window 크기를 알고 흐름 제어를 할 수 있을까요? 이에 대한 답도 문서에 잘 보면 나옵니다. ^^

This means that the first data packet sent after the three-way handshake is the actual window size. If there is a scaling factor, the initial window size of 65,535 bytes is always used.


즉, TCP 협상에서 scale 사용을 지정했으면 기본 윈도우 크기를 65,535로 사용한다는 것입니다. 따라서 (서버로부터 scale을 사용하겠다고 했으므로) 클라이언트는 서버의 최초 TCP Receive Window의 크기를 65,535로 가정하고 흐름 제어를 하다가 서버로부터 수신한 패킷에 Window Size 필드가 있으면 그것에 scale 값을 곱해 새로운 윈도우 크기로 인식하게 되는 것입니다.




기왕 하는 김에, Receive Window 크기를 소진해 볼까요? 그런데 아쉽게도 아주 매끄러운 재현 방법이 없습니다.

위에서 설명했던 내용대로 서버의 Receive Window 크기는 이후에 (위에서는 262,656으로) 변경이 되겠지만 초기에는 클라이언트에서 65,535로 가정할 것입니다. 그리고 TCP Receive Window란, 상대의 ACK 없이 보낼 수 있는 (이상적인 상황에서의) 최대 데이터 크기이기 때문에 TCP 연결 후 서버 측의 VM을 paused 상태로 만들고 send를 하면 이론상 유사하게 재현이 될 걸로 보입니다. 즉, 클라이언트가 바로 데이터를 전송하는 경우 65,535 바이트까지는 전송이 되어야 하는 것입니다.

하지만, 테스트로 (연결 후 처음) 20,000 바이트를 보냈을 때 분할된 첫 번째 패킷에 대한 ACK 대기로 다음과 같이 Retransmission 절차로 빠집니다.

1 0.000000   ..client_ip... ..server_ip... TCP 66 33692 → 15000 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM=1
2 0.005193   ..server_ip... ..client_ip... TCP 66 15000 → 33692 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=256 SACK_PERM=1
3 0.005281   ..client_ip... ..server_ip... TCP 54 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=0
4 15.104217  ..client_ip... ..server_ip... TCP 14654 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=14600
5 15.411778  ..client_ip... ..server_ip... TCP 1514 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=1460
6 16.022453  ..client_ip... ..server_ip... TCP 1514 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=1460
7 17.223054  ..client_ip... ..server_ip... TCP 590 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=536
8 18.427186  ..client_ip... ..server_ip... TCP 590 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=536
9 19.636720  ..client_ip... ..server_ip... TCP 1514 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=1460
10 22.047373 ..client_ip... ..server_ip... TCP 1514 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=1460
11 26.853856 ..client_ip... ..server_ip... TCP 1514 [TCP Retransmission] 33692 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=1460
12 36.466235 ..client_ip... ..server_ip... TCP 54 33692 → 15000 [RST, ACK] Seq=1461 Ack=1 Win=0 Len=0

오호~~~ 당황스럽군요. ^^ 동일 환경의 연결 상태에서 20,000 바이트를 보내면 첫 번째 패킷 보낸 후 마지막 패킷의 ACK를 받는 시간까지 다 합쳐도 10ms가 안 걸렸습니다. 즉, 적어도 Receive Window라고 65,535 크기를 가정했다면 20,000 바이트 정도는 모두 보냈어야 하고 ACK를 받지 못한 300ms가 지난 시점부터 Retransmission 절차가 나와야 하는데 그렇지 않은 것입니다.

혹시, Send Window의 초기 크기가 14600 정도로 잡혀 있던 걸까요? ^^ 이를 확인하기 위해서는 InitialCongestionWindow의 값을 봐야 합니다.

C:\Windows\System32> netsh interface tcp show supplemental

The TCP global default template is internet

TCP Supplemental Parameters
----------------------------------------------
Minimum RTO (msec)                  : 300
Initial Congestion Window (MSS)     : 10
Congestion Control Provider         : cubic
Enable Congestion Window Restart    : disabled
Delayed ACK timeout (msec)          : 40
Delayed ACK frequency               : 2
Enable RACK                         : enabled
Enable Tail Loss Probe              : enabled

Please use the 'netsh int tcp show supplementalports' and
'netsh int tcp show supplementalsubnets' commands to view active filters.

그러고 보니 3-way handshake 단계에서 협의된 MSS 크기는 1460입니다. 거기다 InitialCongestionWindow의 값이 10이니까, 14600만큼 전송하고 ACK를 대기하게 된 것입니다. 정확히 설명이 되었군요. ^^

참고로 예전에는 InitialCongestionWindow 값이 보통 1이었다고 합니다. 그래서 간혹 블로그 글들을 보면 첫 번째 패킷은 무조건 송신 후 ACK를 기다린다는 식의 설명이 나오는데 바로 icwnd 값이 1로 설정된 당시의 환경에서 기인한 것입니다. 이후 2로 증가한 후 근래에는 10(RFC6928)을 기본으로 쓰는 운영체제들이 늘었다고.




다소 과격하지만 ^^ 넉넉잡아 500,000 바이트 정도 send 하고 수신 측은 recv만 호출하지 않는다면 Receive Window를 소진하는 것을 쉽게 재현할 수 있습니다.

// 연결 단계의 패킷 3개
153 2.888173 ..client_ip... ..server_ip... TCP 66 11337 → 15000 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 WS=256 SACK_PERM=1
154 2.893354 ..server_ip... ..client_ip... TCP 66 15000 → 11337 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=1460 WS=256 SACK_PERM=1
155 2.893446 ..client_ip... ..server_ip... TCP 54 11337 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=0

// 송신이 진행되면서 수신 측은 내부 버퍼인 SO_RCVBUF의 크기 정도는 처리되면서 Receive Window의 크기가 그다지 변화가 없다가,
290 7.682747 ..client_ip... ..server_ip... TCP 14654 11337 → 15000 [ACK] Seq=1 Ack=1 Win=262656 Len=14600
291 7.689219 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=2921 Win=131328 Len=0
...[생략]...                                   
316 7.702878 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=55481 Win=131328 Len=0
317 7.702897 ..client_ip... ..server_ip... TCP 11734 11337 → 15000 [ACK] Seq=113881 Ack=1 Win=262656 Len=11680
318 7.702932 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=58401 Win=131328 Len=0
319 7.702932 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=61321 Win=131328 Len=0
320 7.702932 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=64241 Win=131328 Len=0

// SO_RCVBUF가 마침내 꽉 차게 되면, 이후 Receive Window가 점차로 줄어들기 시작
...[생략]...
362 7.714849 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=175201 Win=21760 Len=0
363 7.714849 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=178121 Win=18944 Len=0
364 7.714849 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=181041 Win=15872 Len=0
365 7.714849 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=183961 Win=13056 Len=0
366 7.714910 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=186881 Win=9984 Len=0
367 7.714910 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=189801 Win=7168 Len=0
368 7.716539 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=192721 Win=4352 Len=0
369 7.716539 ..server_ip... ..client_ip... TCP 60 15000 → 11337 [ACK] Seq=1 Ack=195641 Win=1280 Len=0

// 송신 측이 수신자의 Receive Window의 크기를 마지막으로 1280 바이트를 채워 보내는 것으로,
500 12.718148 ..client_ip... ..server_ip... TCP 1334 [TCP Window Full] 11337 → 15000 [ACK] Seq=195641 Ack=1 Win=262656 Len=1280

// Receive Window 소진 
501 12.778449 ..server_ip... ..client_ip... TCP 60 [TCP ZeroWindow] 15000 → 11337 [ACK] Seq=1 Ack=196921 Win=0 Len=0

// 이후, 수신 측의 Receive Window 크기 조회를 일정 시간 동안 반복
507 13.079354 ..client_ip... ..server_ip... TCP 55 [TCP ZeroWindowProbe] 11337 → 15000 [ACK] Seq=196921 Ack=1 Win=262656 Len=1
510 13.137834 ..server_ip... ..client_ip... TCP 60 [TCP ZeroWindow] [TCP ACKed unseen segment] 15000 → 11337 [ACK] Seq=1 Ack=196922 Win=0 Len=0
522 13.749622 ..client_ip... ..server_ip... TCP 55 [TCP Previous segment not captured] 11337 → 15000 [ACK] Seq=196922 Ack=1 Win=262656 Len=1
526 13.809922 ..server_ip... ..client_ip... TCP 60 [TCP ZeroWindow] [TCP ACKed unseen segment] 15000 → 11337 [ACK] Seq=1 Ack=196923 Win=0 Len=0
553 15.015558 ..client_ip... ..server_ip... TCP 55 [TCP ZeroWindowProbe] 11337 → 15000 [ACK] Seq=196923 Ack=1 Win=262656 Len=1
554 15.076075 ..server_ip... ..client_ip... TCP 60 [TCP ZeroWindow] [TCP ACKed unseen segment] 15000 → 11337 [ACK] Seq=1 Ack=196924 Win=0 Len=0
606 17.481915 ..client_ip... ..server_ip... TCP 55 [TCP Previous segment not captured] 11337 → 15000 [ACK] Seq=196924 Ack=1 Win=262656 Len=1
609 17.529619 ..server_ip... ..client_ip... TCP 60 [TCP ZeroWindow] [TCP ACKed unseen segment] 15000 → 11337 [ACK] Seq=1 Ack=196925 Win=0 Len=0

보다시피 수신 측은 "Ack=196921 Win=0"을 마지막 ACK로 응답하면서 Receive Window가 모두 소진되었습니다. 이후, 송신 측은 주기적으로 (wireshark에서 "TCP ZeroWindowProbe"라고 표시된) ACK 패킷을 수신 측으로 날리며 receive window의 변화가 있는지 묻습니다. 그리고 이에 대해 수신 측은 (wireshark에서 "ZeroWindow"라고 표시된) ACK 패킷으로 여전히 공간이 없음을 확인해 줍니다. ("ZeroWindow", "ZeroWindowProbe"라는 것은 wirehshark가 서비스로 붙여주는 문구이고 실제 이런 이름의 필드는 없습니다.)

물론, 저 상태에서 수신 측에 recv 호출을 해주면 다시 Receive Window 공간이 생겼음을 송신 측으로 보내고 이후 데이터 전송이 재개됩니다.




참고로, Vista/Windows Server 2008부터는 자동으로 Receive Window에 대한 크기를 조절한다고 합니다.

The Cable Guy TCP Receive Window Auto-Tuning
; https://docs.microsoft.com/en-us/previous-versions/technet-magazine/cc162519(v=msdn.10)

암튼... 너무나 복잡해진 TCP 스택입니다. ^^;




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

[연관 글]


donaricano-btn



[최초 등록일: ]
[최종 수정일: 2/5/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/202126오류 유형: 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  ...