성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
MathJax 입력기
최근 덧글
[정성태] Working with Rust Libraries from C#...
[정성태] Detecting blocking calls using asyn...
[정성태] 아쉽게도, 커뮤니티는 아니고 개인 블로그입니다. ^^
[정성태] 질문이 잘 이해가 안 됩니다. 우선, 해당 소스코드에서 ILis...
[양승조
] var대신 dinamic으로 선언해서 해결은 했습니다. 맞는 해...
[양승조
] 또 막혔습니다. ㅠㅠ var list = props[i].Ge...
[양승조
] 아. 감사합니다. 어제는 안됐던것 같은데....정신을 차려야겠네...
[정성태] "props[i].GetValue(props[i])" 코드에서 ...
[정성태] 저렇게 조각 코드 말고, 실제로 재현이 되는 예제 프로젝트를 압...
[정성태] Modules 창(Ctrl+Shift+U)을 띄워서, 해당 Op...
글쓰기
제목
이름
암호
전자우편
HTML
홈페이지
유형
제니퍼 .NET
닷넷
COM 개체 관련
스크립트
VC++
VS.NET IDE
Windows
Team Foundation Server
디버깅 기술
오류 유형
개발 환경 구성
웹
기타
Linux
Java
DDK
Math
Phone
Graphics
사물인터넷
부모글 보이기/감추기
내용
<div style='display: inline'> <h1 style='font-family: Malgun Gothic, Consolas; font-size: 20pt; color: #006699; text-align: center; font-weight: bold'>C# - TIME_WAIT과 ephemeral port 재사용</h1> <p> 예전에 소켓 상태 테스트를 하면서,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 코드로 재현하는 소켓 상태(FIN_WAIT1, FIN_WAIT2, TIME_WAIT, CLOSE_WAIT, LAST_WAIT) ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/1334'>https://www.sysnet.pe.kr/2/0/1334</a> </pre> <br /> 맨 처음 closesocket을 호출한 측(만약, 둘이 동시에 closesocket을 호출하면 양측 모두)에 TIME_WAIT 상태로 2MSL(maximum segment lifetime) 시간 동안 유지된다고 했는데요, 그때도 언급했지만 2MSL 시간이라는 게 참 애매합니다. 이에 대해 직접적으로 설명한 공식 문서는 찾을 수 없고, 연관된 것들에서 그 흔적을 볼 수 있는데요,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > TcpTimedWaitDelay LPFN_CONNECTEX callback function (mswsock.h) ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex'>https://docs.microsoft.com/en-us/windows/win32/api/mswsock/nc-mswsock-lpfn_connectex</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> By default, the MSL is defined to be 120 seconds. The TcpTimedWaitDelay registry setting defaults to a value 240 seconds, which represents 2 times the maximum segment lifetime of 120 seconds or 4 minutes. <br /> </div><br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SystemConfig_Network class ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/etw/systemconfig-network'>https://docs.microsoft.com/en-us/windows/win32/etw/systemconfig-network</a> </pre> <br /> <div style='BACKGROUND-COLOR: #ccffcc; padding: 10px 10px 5px 10px; MARGIN: 0px 10px 10px 10px; FONT-FAMILY: Malgun Gothic, Consolas, Verdana; COLOR: #005555'> RFC 793 published by the IETF requires that TCP maintains a closed connection for an interval at least equal to twice the maximum segment lifetime (2MSL) of the network. When a connection is released, its socket pair and TCP control block (TCB) can be used to support another connection. By default, the MSL is defined to be 120 seconds, and the value of this entry is equal to two MSLs, or 4 minutes. For more information, see RFC 793.<br /> </div><br /> <br /> MSL의 시간이 120초, 따라서 2MSL로 TcpTimedWaitDelay가 정해졌으니 4분인데, 엉뚱하게도 biztalk 문서에 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Settings that can be Modified to Improve Network Performance ; <a target='tab' href='https://docs.microsoft.com/en-us/biztalk/technical-guides/settings-that-can-be-modified-to-improve-network-performance'>https://docs.microsoft.com/en-us/biztalk/technical-guides/settings-that-can-be-modified-to-improve-network-performance</a> </pre> <br /> 120으로 기본값이 설정되었다고 하며 실제로 테스트해보면 Windows 10에서 2분(120)이 맞습니다.<br /> <br /> 자, 그럼 TIME_WAIT 상태로 머무는 동안 점유된 포트는 이후 어떻게 소켓 통신에 영향을 미치게 될까요? 기왕에 이번 테스트를 하면서 환경도 구축했으니,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > 경로: HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters 이름: MaxUserPort 타입: DWORD 값: 7d0 (2000) <a target='tab' href='https://www.sysnet.pe.kr/2/0/12350#maxuserport'>DynamicPortRangeStartPort</a> : 1024 DynamicPortRangeNumberOfPorts : 977 <a target='tab' href='https://www.sysnet.pe.kr/2/0/12435#set_nettcpsetting'>AutoReusePortRangeStartPort</a> : 15000 AutoReusePortRangeNumberOfPorts : 1000 </pre> <br /> ^^ 마저 결과를 확인해 보겠습니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 어차피 서버 측은 5-tuple 구분을 하므로, 여기서 궁금한 것은 클라이언트 측의 TIME_WAIT 상태에 따른 포트 재사용 문제입니다. 따라서, 다음과 같이 서버와 클라이언트 코드를 구성해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 서버 예제 코드 using System; using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading; namespace ConsoleApp1 { class Program { static List<TcpClient> _cla17000 = new List<TcpClient>(); static List<TcpClient> _cla17001 = new List<TcpClient>(); static void Main(string[] args) { Thread t = new Thread(CountFunc); t.IsBackground = true; t.Start(); bool active1 = true; bool active2 = true; if (args.Length >= 1) { if (args[0] == "1") { active2 = false; } else if (args[0] == "2") { active1 = false; } } if (active1 == true) { Thread t17000 = new Thread(acceptFunc); t17000.IsBackground = true; t17000.Start(17000); } if (active2 == true) { Thread t17001 = new Thread(acceptFunc); t17001.IsBackground = true; t17001.Start(17001); } while (true) { string txt = Console.ReadLine(); if (txt == "1") { foreach (var cla in _cla17000) { try { cla.Close(); } catch { } } _cla17000.Clear(); } Console.WriteLine(DateTime.Now); } } private static void acceptFunc(object objPort) { int port = (int)objPort; TcpListener svr = new TcpListener(IPAddress.Any, port); { svr.Start(); while (true) { TcpClient client = svr.AcceptTcpClient(); if (port == 17000) { _cla17000.Add(client); } else { _cla17001.Add(client); } } } } private static void CountFunc() { while (true) { Console.WriteLine($"# of 17000: {_cla17000.Count}, 17001: {_cla17001.Count}"); Thread.Sleep(5000); } } } } </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 클라이언트 예제 코드 using System; using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Net.Sockets; namespace ConsoleApp2 { class Program { static void Main(string[] args) { string ipAddr = args[0]; int port = int.Parse(args[1]); int numberOf = int.Parse(args[2]); List<Socket> clients1 = new List<Socket>(); try { for (int i = 0; i < numberOf; i++) { try { Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); <span style='color: blue; font-weight: bold'>client.Bind(new IPEndPoint(IPAddress.Any, 0));</span> client.Connect(ipAddr, port); Console.WriteLine($"{client.LocalEndPoint}-{client.RemoteEndPoint}"); clients1.Add(client); } catch { } } } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.WriteLine($"{clients1.Count}, {Process.GetCurrentProcess().Id}"); while (true) { string txt = Console.ReadLine(); if (txt == "1") { foreach (var cla in clients1) { cla.Close(); } } Console.ReadLine(); } } } } </pre> <br /> 실행해 보면, 클라이언트 측의 Bind로 인해 DynamicPortRangeStartPort 영역의 포트를 사용하게 되고 Reuse를 하지 못해 지난번처럼 DynamicPortRangeNumberOfPorts 즈음에서 더 이상 접속이 안 됩니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\temp2> <span style='color: blue; font-weight: bold'>ConsoleApp1.exe</span> # of 17000: 0, 15001: 0 # of 17000: 966, 15001: 0 C:\temp2> <span style='color: blue; font-weight: bold'>ConsoleApp2 localhost 17000 1000</span> ...[생략]... 966 </pre> <br /> 이 상태에서, 클라이언트 측 콘솔에 1+{ENTER}를 치면 소켓 접속을 모두 끊고, 이어 서버 측에서도 1+{ENTER}를 치면 대응 소켓을 모두 끊으므로 클라이언트 측 소켓에 대해 TIME_WAIT이 남는 것을 "netstat -ano | findstr TIME_WAIT"로 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>netstat -ano | findstr TIME_WAIT</span> TCP 127.0.0.1:1024 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 TCP 127.0.0.1:1025 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 TCP 127.0.0.1:1026 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 TCP 127.0.0.1:1027 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 ...[생략]... TCP 127.0.0.1:1997 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 TCP 127.0.0.1:1998 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 TCP 127.0.0.1:1999 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 TCP 127.0.0.1:2000 127.0.0.1:17000 <span style='color: blue; font-weight: bold'>TIME_WAIT</span> 0 </pre> <br /> 이제 2분의 여유 시간이 있으니 이 사이에 포트 재사용 여부를 테스트할 수 있습니다. ^^<br /> <br /> 우선, cmd.exe 창을 띄워 같은 포트로 접속을 시도해 보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > D:\temp> <span style='color: blue; font-weight: bold'>ConsoleApp2.exe localhost 17000 1001</span> 0 </pre> <br /> TIME_WAIT으로 점유된 포트로 인해 더 이상 가용 포트가 없어 접속이 안되는 것을 확인할 수 있습니다. 반면, 다른 포트로 접속하면 어떻게 될까요?<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > D:\temp> <span style='color: blue; font-weight: bold'>ConsoleApp2.exe localhost 17001 1001</span> 127.0.0.1:1116-127.0.0.1:17001 127.0.0.1:1117-127.0.0.1:17001 127.0.0.1:1118-127.0.0.1:17001 127.0.0.1:1119-127.0.0.1:17001 ...[생략]... 127.0.0.1:1112-127.0.0.1:17001 127.0.0.1:1113-127.0.0.1:17001 127.0.0.1:1114-127.0.0.1:17001 127.0.0.1:<span style='color: blue; font-weight: bold'>1115</span>-127.0.0.1:17001 965 </pre> <br /> 이런 경우에는, 별다른 설정이 없었는데도 5-tuple 구분을 통해 정상적으로 동일한 포트 번호를 점유하는 것을 확인할 수 있습니다. 실제로 위에서 출력된 포트 하나를 기준으로 netstat 명령을 내려보면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > C:\WINDOWS\system32> <span style='color: blue; font-weight: bold'>netstat -ano | findstr 1115</span> TCP 127.0.0.1:<span style='color: blue; font-weight: bold'>1115</span> 127.0.0.1:17000 TIME_WAIT 0 TCP 127.0.0.1:<span style='color: blue; font-weight: bold'>1115</span> 127.0.0.1:17001 ESTABLISHED 17304 </pre> <br /> 1115 포트가 TIME_WAIT, ESTABLISHED에 각각 사용된 것이 보입니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1680&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 따라서, 5-tuple 구분이 되는 서버 소켓으로의 접속이라면 클라이언트 측의 TIME_WAIT으로 인한 소켓 고갈을 염려할 필요는 없습니다. 단지, 주소 및 포트까지 동일한 서버 소켓으로 접속하는 경우라면, TIME_WAIT으로 인한 소켓 포트 점유는 문제가 됩니다.<br /> <br /> 닷넷 환경이라면 어떨까요?<br /> <br /> 만약 여러분이 소켓을 사용해 직접 구현한 경우라면 TIME_WAIT으로 인한 소켓 포트 점유를 주의 깊게 살펴야 합니다. 반면, HttpWebRequest, WebClient, HttpClient 등을 이용한 경우라면 그것들 자체 내에 풀링 기능이 있으므로,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > You're using HttpClient wrong and it is destabilizing your software ; <a target='tab' href='https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/'>https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/</a> </pre> <br /> TIME_WAIT이 문제가 되는 경우는 거의 없습니다.<br /> </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1585
(왼쪽의 숫자를 입력해야 합니다.)