Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
 
(연관된 글이 1개 있습니다.)
(시리즈 글이 8개 있습니다.)
개발 환경 구성: 533. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 리눅스 환경
; https://www.sysnet.pe.kr/2/0/12527

개발 환경 구성: 534. Wireshark + C#으로 확인하는 TCP 통신의 MSS(Maximum Segment Size) - 윈도우 환경
; https://www.sysnet.pe.kr/2/0/12528

개발 환경 구성: 535. Wireshark + C#으로 확인하는 TCP 통신의 MIN RTO
; https://www.sysnet.pe.kr/2/0/12529

개발 환경 구성: 536. Wireshark + C#으로 확인하는 TCP 통신의 Receive Window
; https://www.sysnet.pe.kr/2/0/12530

개발 환경 구성: 538. Wireshark + C#으로 확인하는 ReceiveBufferSize(SO_RCVBUF), SendBufferSize(SO_SNDBUF)
; https://www.sysnet.pe.kr/2/0/12532

개발 환경 구성: 539. Wireshark + C/C++로 확인하는 TCP 연결에서의 shutdown 동작
; https://www.sysnet.pe.kr/2/0/12533

개발 환경 구성: 540. Wireshark + C/C++로 확인하는 TCP 연결에서의 closesocket 동작
; https://www.sysnet.pe.kr/2/0/12534

개발 환경 구성: 541.  Wireshark로 확인하는 LSO(Large Send Offload), RSC(Receive Segment Coalescing) 옵션
; https://www.sysnet.pe.kr/2/0/12535




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 스택입니다. ^^;




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 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)
13423정성태10/6/20233063스크립트: 58. 파이썬 - async/await 기본 사용법
13422정성태10/5/20233204닷넷: 2148. C# - async 유무에 따른 awaitable 메서드의 병렬 및 예외 처리
13421정성태10/4/20233243닷넷: 2147. C# - 비동기 메서드의 async 예약어 유무에 따른 차이
13420정성태9/26/20235327스크립트: 57. 파이썬 - UnboundLocalError: cannot access local variable '...' where it is not associated with a value
13419정성태9/25/20233092스크립트: 56. 파이썬 - RuntimeError: dictionary changed size during iteration
13418정성태9/25/20233759닷넷: 2146. C# - ConcurrentDictionary 자료 구조의 동기화 방식
13417정성태9/19/20233336닷넷: 2145. C# - 제네릭의 형식 매개변수에 속한 (매개변수를 가진) 생성자를 호출하는 방법
13416정성태9/19/20233152오류 유형: 877. redis-py - MISCONF Redis is configured to save RDB snapshots, ...
13415정성태9/18/20233639닷넷: 2144. C# 12 - 컬렉션 식(Collection Expressions)
13414정성태9/16/20233390디버깅 기술: 193. Windbg - ThreadStatic 필드 값을 조사하는 방법
13413정성태9/14/20233582닷넷: 2143. C# - 시스템 Time Zone 변경 시 이벤트 알림을 받는 방법
13412정성태9/14/20236853닷넷: 2142. C# 12 - 인라인 배열(Inline Arrays) [1]
13411정성태9/12/20233364Windows: 252. 권한 상승 전/후 따로 관리되는 공유 네트워크 드라이브 정보
13410정성태9/11/20234863닷넷: 2141. C# 12 - Interceptor (컴파일 시에 메서드 호출 재작성) [1]
13409정성태9/8/20233721닷넷: 2140. C# - Win32 API를 이용한 모니터 전원 끄기
13408정성태9/5/20233714Windows: 251. 임의로 만든 EXE 파일을 포함한 ZIP 파일의 압축을 해제할 때 Windows Defender에 의해 삭제되는 경우
13407정성태9/4/20233473닷넷: 2139. C# - ParallelEnumerable을 이용한 IEnumerable에 대한 병렬 처리
13406정성태9/4/20233426VS.NET IDE: 186. Visual Studio Community 버전의 라이선스
13405정성태9/3/20233841닷넷: 2138. C# - async 메서드 호출 원칙
13404정성태8/29/20233364오류 유형: 876. Windows - 키보드의 등호(=, Equals sign) 키가 눌리지 않는 경우
13403정성태8/21/20233189오류 유형: 875. The following signatures couldn't be verified because the public key is not available: NO_PUBKEY EB3E94ADBE1229CF
13402정성태8/20/20233250닷넷: 2137. ILSpy의 nuget 라이브러리 버전 - ICSharpCode.Decompiler
13401정성태8/19/20233525닷넷: 2136. .NET 5+ 환경에서 P/Invoke의 성능을 높이기 위한 SuppressGCTransition 특성 [1]
13400정성태8/10/20233351오류 유형: 874. 파이썬 - pymssql을 윈도우 환경에서 설치 불가
13399정성태8/9/20233373닷넷: 2135. C# - 지역 변수로 이해하는 메서드 매개변수의 값/참조 전달
13398정성태8/3/20234135스크립트: 55. 파이썬 - pyodbc를 이용한 SQL Server 연결 사용법
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...