성태의 닷넷 이야기
홈 주인
모아 놓은 자료
프로그래밍
질문/답변
사용자 관리
사용자
메뉴
아티클
외부 아티클
유용한 코드
온라인 기능
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'>윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (3) - SO_PORT_SCALABILITY</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;' > 윈도우 서버 환경에서, 최대 생성 가능한 소켓(socket) 연결 수는 얼마일까? ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/964'>https://www.sysnet.pe.kr/2/0/964</a> 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12350'>https://www.sysnet.pe.kr/2/0/12350</a> 윈도우 환경에서 클라이언트 소켓의 최대 접속 수 (2) - SO_REUSEADDR ; <a target='tab' href='https://www.sysnet.pe.kr/2/0/12432'>https://www.sysnet.pe.kr/2/0/12432</a> </pre> <br /> 클라이언트 소켓에 대한 5-tuple 구분 값 확장을 SO_REUSEADDR로 가능하지만 현실성이 없다고 했는데요. 기왕 살펴보는 김에 또 다른 확장으로 SO_PORT_SCALABILITY 옵션을 보겠습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > SO_PORT_SCALABILITY ; <a target='tab' href='https://docs.microsoft.com/en-us/windows/win32/winsock/so-port-scalability'>https://docs.microsoft.com/en-us/windows/win32/winsock/so-port-scalability</a> </pre> <br /> 문서에도 잘 나오지만, <br /> <br /> <ol> <li>The socket function is called by a process to create a socket.</li> <li>The <span style='color: blue; font-weight: bold'>setsockopt</span> function is called to enable the <span style='color: blue; font-weight: bold'>SO_PORT_SCALABILITY</span> socket option on the newly created socket.</li> <li>The <span style='color: blue; font-weight: bold'>bind</span> function is called to do a bind <span style='color: blue; font-weight: bold'>on one of the local computer's IP addresses and port 0.</span></li> <li><span style='color: blue; font-weight: bold'>The connect function is then called to connect to a remote IP address</span>. The socket is used by the application as needed.</li> <li><span style='color: blue; font-weight: bold'>A socket function is called by the same process</span> (possibly a different thread) to create a second socket.</li> <li>The <span style='color: blue; font-weight: bold'>setsockopt</span> function is called to enable the <span style='color: blue; font-weight: bold'>SO_PORT_SCALABILITY</span> socket option on the newly created second socket.</li> <li>The <span style='color: blue; font-weight: bold'>bind</span> function is called with <span style='color: blue; font-weight: bold'>the local computer's second IP address and port 0</span>. <span style='color: blue; font-weight: bold'>Even when all ports have been previously allocated, this call succeeds because there are multiple IP addresses available on the local computer and the SO_PORT_SCALABILITY socket option was set on both sockets in the same process.</span></li> <li>The connect function is then called to connect to a remote IP address. The second socket is used by the application as needed.</li> </ol> <br /> 이 옵션을 적용해 바인딩하면, 시스템에 할당된 IP 주소만큼 배수로 중복 포트를 활용할 수 있습니다. 예를 들어, "169.254.44.76", "192.168.100.80" IP가 부여된 컴퓨터가 있다고 가정했을 때 지난번 예제 코드의 클라이언트 측을 다음과 같이 수정하면,<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.Runtime.InteropServices; namespace ConsoleApp2 { class Program { [DllImport("Ws2_32.dll")] public static extern int setsockopt(IntPtr s, SocketOptionLevel level, int optname, ref int optval, int optlen); const int SO_PORT_SCALABILITY = 0x3006; static void Main(string[] args) { string ipAddr = args[0]; int port = int.Parse(args[1]); int numberOf = int.Parse(args[2]); string targetIp = ipAddr; if (args.Length >= 4) { targetIp = args[3]; } List<Socket> clients1 = new List<Socket>(); int trueValue = 1; 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'>setsockopt(client.Handle, SocketOptionLevel.Socket, SO_PORT_SCALABILITY, ref trueValue, sizeof(int)); // 또는, client.SetSocketOption(SocketOptionLevel.Socket, (SocketOptionName)0x3006, true); client.Bind(new IPEndPoint(IPAddress.Parse(ipAddr), 0)); client.Connect(targetIp, port);</span> Console.WriteLine($"{client.LocalEndPoint}-{client.RemoteEndPoint}"); clients1.Add(client); } catch { } } } catch (Exception e) { Console.WriteLine(e.ToString()); } Console.WriteLine(clients1.Count); Console.ReadLine(); } } } </pre> <br /> 서버와 클라이언트의 실행 결과를 각각 다음과 같이 확인할 수 있습니다.<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // 서버 측 포트 15000, 15001 Listen D:\temp> <span style='color: blue; font-weight: bold'>ConsoleApp1.exe</span> # of 15000: 0, 15001: 0 # of 15000: 0, 15001: 0 # of 15000: 0, 15001: 0 # of 15000: <span style='color: blue; font-weight: bold'>970</span>, 15001: 0 # of 15000: 970, 15001: 0 # of 15000: 970, 15001: 0 # of 15000: 970, 15001: 0 # of 15000: 970, 15001: <span style='color: blue; font-weight: bold'>970</span> # of 15000: 970, 15001: 970 # of 15000: 970, 15001: 970 </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // #1 클라이언트 측 - 로컬 IP 중의 하나인 169.254.44.76을 사용해 15000 포트로 1001개 접속 시도 d:\temp> <span style='color: blue; font-weight: bold'>ConsoleApp2 169.254.44.76 15000 1001</span> 169.254.44.76:<span style='color: blue; font-weight: bold'>1580</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1581</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1582</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1583</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1584</span>-169.254.44.76:15000 ...[생략]... 169.254.44.76:<span style='color: blue; font-weight: bold'>1574</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1575</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1576</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1577</span>-169.254.44.76:15000 169.254.44.76:<span style='color: blue; font-weight: bold'>1578</span>-169.254.44.76:15000 970 </pre> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > // #2 클라이언트 측 - 로컬 IP 중의 하나인 192.168.100.80을 사용해 15001 포트로 1001개 접속 시도 d:\temp> <span style='color: blue; font-weight: bold'>ConsoleApp2.exe 192.168.100.80 15001 1001</span> 192.168.100.80:<span style='color: blue; font-weight: bold'>1580</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1581</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1582</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1583</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1584</span>-192.168.100.80:15001 ...[생략]... 192.168.100.80:<span style='color: blue; font-weight: bold'>1575</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1576</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1577</span>-192.168.100.80:15001 192.168.100.80:<span style='color: blue; font-weight: bold'>1578</span>-192.168.100.80:15001 970 </pre> <br /> 보는 바와 같이 동일한 포트를 사용하고 있지만 로컬 IP의 주소가 다르기 때문에 정상적으로 연결을 하고 있습니다.<br /> <br /> (<a target='tab' href='https://www.sysnet.pe.kr/bbs/DownloadAttachment.aspx?fid=1672&boardid=331301885'>첨부 파일은 이 글의 예제 코드를 포함</a>합니다.)<br /> <br /> <hr style='width: 50%' /><br /> <br /> 그래도 이번엔 SO_REUSEADDR보다는 다소 현실성이 있습니다. 왜냐하면 포트를 직접 지정하지 않고 0으로 선택해,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > setsockopt(client.Handle, SocketOptionLevel.Socket, SO_PORT_SCALABILITY, ref trueValue, sizeof(int)); client.<span style='color: blue; font-weight: bold'>Bind</span>(new IPEndPoint(IPAddress.Parse(ipAddr), <span style='color: blue; font-weight: bold'>0</span>)); </pre> <br /> 시스템에 의해 자동으로 부여받을 수 있기 때문입니다.<br /> <br /> 그런데, 한가지 좀 불편한 것이 있습니다. 예제의 경우 localhost에 대해 테스트를 하고 있기 때문에 바인딩하는 IP가 어떤 거냐에 상관없이 localhost로는 연결이 되어야 하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); setsockopt(client.Handle, SocketOptionLevel.Socket, SO_PORT_SCALABILITY, ref trueValue, sizeof(int)); client.Bind(new IPEndPoint(IPAddress.Parse("<span style='color: blue; font-weight: bold'>169.254.44.76</span>"), 0)); client.Connect("<span style='color: blue; font-weight: bold'>127.0.0.1</span>", port); </pre> <br /> 실제로 해보면 Connect에서 이런 오류가 발생합니다.<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 169.254.44.76 15000 1001 127.0.0.1</span> System.Net.Sockets.SocketException (0x80004005): The requested address is not valid in its context 127.0.0.1:15000 at System.Net.Sockets.Socket.Connect(IPAddress[] addresses, Int32 port) at System.Net.Sockets.Socket.Connect(String host, Int32 port) at ConsoleApp2.Program.Main(String[] args) in C:\temp\ctest_tcp_server_client\ConsoleApp2\Program.cs:line 39 </pre> <br /> 또한, 외부로 접속한다면 바인딩하는 IP에서 외부 IP로 라우팅이 되어야 합니다. 즉, "169.254.44.76"으로 바인딩한 후 (라우팅되지 않는<b><sup>*</sup></b>) "192.168.100.90"으로 접속을 하면,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); setsockopt(client.Handle, SocketOptionLevel.Socket, SO_PORT_SCALABILITY, ref trueValue, sizeof(int)); client.Bind(new IPEndPoint(IPAddress.Parse("<span style='color: blue; font-weight: bold'>169.254.44.76</span>"), 0)); client.Connect("<span style='color: blue; font-weight: bold'>192.168.100.90</span>", port); </pre> <br /> 동일한 예외가 발생합니다.<br /> <br /> <hr style='width: 50%' /><br /> <br /> 이처럼 SO_PORT_SCALABILITY를 사용하면 (라우팅이 가능한 경우<b><sup>*</sup></b>) IP의 수만큼 dynamicport 영역의 배수로 접속을 할 수 있지만, 이것 역시 잘 제어된 환경에서 사용할 수 있기에 아쉬움이 남습니다.<br /> <br /> 사실 가장 좋은 것은, SO_REUSEADDR을 다음과 같은 식으로 사용할 수 있어야 하는데,<br /> <br /> <pre style='margin: 10px 0px 10px 10px; padding: 10px 0px 10px 10px; background-color: #fbedbb; overflow: auto; font-family: Consolas, Verdana;' > client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); client.Bind(new IPEndPoint(<span style='color: blue; font-weight: bold'>IPAddress.Any, 0</span>)); </pre> <br /> 이것이 지원되지 않아 ^^; 안타까울 뿐입니다.<br /> <br /> (<b><sup>*</sup></b> 라우팅이 기준인지, 같은 네트워크여야 하는지에 대해서는 테스트가 필요합니다.) </p><br /> <br /><hr /><span style='color: Maroon'>[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]</span> </div>
첨부파일
스팸 방지용 인증 번호
1184
(왼쪽의 숫자를 입력해야 합니다.)