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)
13329정성태4/24/20233706Windows: 246. Win32 C/C++ - 직접 띄운 대화창 템플릿을 위한 Modal 메시지 루프 생성파일 다운로드1
13328정성태4/19/20233379VS.NET IDE: 184. Visual Studio - Fine Code Coverage에서 동작하지 않는 Fake/Shim 테스트
13327정성태4/19/20233793VS.NET IDE: 183. C# - .NET Core/5+ 환경에서 Fakes를 이용한 단위 테스트 방법
13326정성태4/18/20235204.NET Framework: 2109. C# - 닷넷 응용 프로그램에서 SQLite 사용 (System.Data.SQLite) [1]파일 다운로드1
13325정성태4/18/20234504스크립트: 48. 파이썬 - PostgreSQL의 with 문을 사용한 경우 연결 개체 누수
13324정성태4/17/20234318.NET Framework: 2108. C# - Octave의 "save -binary ..."로 생성한 바이너리 파일 분석파일 다운로드1
13323정성태4/16/20234259개발 환경 구성: 677. Octave에서 Excel read/write를 위한 io 패키지 설치
13322정성태4/15/20235036VS.NET IDE: 182. Visual Studio - 32비트로만 빌드된 ActiveX와 작업해야 한다면?
13321정성태4/14/20233826개발 환경 구성: 676. WSL/Linux Octave - Python 스크립트 연동
13320정성태4/13/20233804개발 환경 구성: 675. Windows Octave 8.1.0 - Python 스크립트 연동
13319정성태4/12/20234290개발 환경 구성: 674. WSL 2 환경에서 GNU Octave 설치
13318정성태4/11/20234113개발 환경 구성: 673. JetBrains IDE에서 "Squash Commits..." 메뉴가 비활성화된 경우
13317정성태4/11/20234226오류 유형: 855. WSL 2 Ubuntu 20.04 - error: cannot communicate with server: Post http://localhost/v2/snaps/...
13316정성태4/10/20233552오류 유형: 854. docker-compose 시 "json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" 오류 발생
13315정성태4/10/20233750Windows: 245. Win32 - 시간 만료를 갖는 컨텍스트 메뉴와 윈도우 메시지의 영역별 정의파일 다운로드1
13314정성태4/9/20233828개발 환경 구성: 672. DosBox를 이용한 Turbo C, Windows 3.1 설치
13313정성태4/9/20233911개발 환경 구성: 671. Hyper-V VM에 Turbo C 2.0 설치 [2]
13312정성태4/8/20233932Windows: 244. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (개선된 버전)파일 다운로드1
13311정성태4/7/20234442C/C++: 163. Visual Studio 2022 - DirectShow 예제 컴파일(WAV Dest)
13310정성태4/6/20234020C/C++: 162. Visual Studio - /NODEFAULTLIB 옵션 설정 후 수동으로 추가해야 할 library
13309정성태4/5/20234206.NET Framework: 2107. .NET 6+ FileStream의 구조 변화
13308정성태4/4/20234097스크립트: 47. 파이썬의 time.time() 실숫값을 GoLang / C#에서 사용하는 방법
13307정성태4/4/20233867.NET Framework: 2106. C# - .NET Core/5+ 환경의 Windows Forms 응용 프로그램에서 HINSTANCE 구하는 방법
13306정성태4/3/20233665Windows: 243. Win32 - 윈도우(cbWndExtra) 및 윈도우 클래스(cbClsExtra) 저장소 사용 방법
13305정성태4/1/20234032Windows: 242. Win32 - 시간 만료를 갖는 MessageBox 대화창 구현 (쉬운 버전)파일 다운로드1
13304정성태3/31/20234387VS.NET IDE: 181. Visual Studio - C/C++ 프로젝트에 application manifest 적용하는 방법
1  2  3  4  5  6  7  8  9  10  11  [12]  13  14  15  ...