Microsoft MVP성태의 닷넷 이야기
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)

서버용 Socket에서 사용하는 포트가 충돌한다면?

일반적으로 소켓 서버의 경우 지정된 포트를 가지고 Listen을 하게 됩니다.

using System;
using System.Net;
using System.Net.Sockets;

class Program
{
    static void Main(string[] args)
    {
        int port = 7999;
        Socket listenSocket = new Socket(AddressFamily.InterNetwork,
                                     SocketType.Stream,
                                     ProtocolType.Tcp);

        IPEndPoint ep = new IPEndPoint(IPAddress.Any, port);
        listenSocket.Bind(ep);
        listenSocket.Listen(5);

        IPEndPoint bindingEndpoint = (listenSocket.LocalEndPoint as IPEndPoint);
        Console.WriteLine("[LISTEN] " + bindingEndpoint.Address + ":" + bindingEndpoint.Port); // [LISTEN] 0.0.0.0:7999
        Console.ReadLine();
    }
}

그런데, 하필 그 포트를 로컬에서 생성된 TCP 소켓이 사용하고 있다면 어떻게 될까요?

예를 들어, 다음과 같이 현재 시스템에 사용 중인 소켓을 나열해보면,

E:\>netstat -ano | findstr "EST"
  TCP    121.163.96.206:43611   74.125.203.125:5222    ESTABLISHED     6224
  ...[생략]...
  TCP    127.0.0.1:65001        127.0.0.1:41177        ESTABLISHED     3380

보시는 바와 같이 43611 포트가 사용중인데, 이때 이 포트로 LISTEN을 해보면,

int port = 43611;
Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

IPEndPoint ep = new IPEndPoint(IPAddress.Any, port);
listenSocket.Bind(ep); // 예외 발생

Bind 메서드 호출 단계에서 다음과 같은 예외가 발생합니다.

E:\...\bin\Debug>ConsoleApplication1.exe

Unhandled Exception: System.Net.Sockets.SocketException: Only one usage of each socket address (protocol/network address/port) is normally permitted
   at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddresssocketAddress)
   at System.Net.Sockets.Socket.Bind(EndPoint localEP)
   at Program.Main(String[] args) in e:\...\ConsoleApplication1\Program.cs:line 23

이건 ... 그야말로 확률 싸움입니다. 소켓 서버를 구현한다면 해당 포트가 클라이언트용 소켓 접속에 사용되고 있지 않음을 운좋게 바랄 수밖에 없습니다.




물론 ^^ 이런 문제를 해결할 수 있는 대안이 있습니다.

우선, 소켓 서버의 포트가 고정일 필요가 없다면 Bind 시에 포트 번호를 0으로 지정해 줄 수 있습니다.

Socket listenSocket = new Socket(AddressFamily.InterNetwork,
                                SocketType.Stream,
                                ProtocolType.Tcp);

IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
listenSocket.Bind(ep);
listenSocket.Listen(5);

IPEndPoint bindingEndpoint = (listenSocket.LocalEndPoint as IPEndPoint);
Console.WriteLine("[LISTEN] " + bindingEndpoint.Address + ":" + bindingEndpoint.Port); // 출력: [LISTEN] 0.0.0.0:46083

그럼, 시스템 측에서는 현재 비어 있는 소켓 포트를 하나 선정해서 LISTEN 포트로 사용합니다. 따라서 여유분의 포트만 있다면 적어도 포트 충돌이 발생할 위험은 없는 것입니다.

그런데, 이 방법이 그다지 현실적이진 않습니다. 왜냐하면 클라이언트 측에서 서버에 접속하려면 반드시 포트 번호를 알아야 하고, 그것이 가변적이라면 어떤 포트를 사용하게 될지 사전에 클라이언트 측에 알려줄 수 있는 또 다른 방법을 고안해야 하는 불편함이 있기 때문입니다.




다음 방법은? 클라이언트 소켓 연결에 사용될 포트와의 충돌을 막기 위해 윈도우 운영체제의 경우 "동적 포트"에 대한 범위를 지정할 수 있는 방법을 제공하고 있습니다.

The default dynamic port range for TCP/IP has changed in Windows Vista and in Windows Server 2008
; http://support.microsoft.com/kb/929851

서버 버전 별로 (TCP및 UDP에 대해) 다음과 같이 기본 예약되어 있습니다.

Windows Server 2003: 1024 ~ 5000
Windows Server 2008 이후: 49152 ~ 65535

동적 포트를 변경하기 위해서는 2003의 경우 Windows Server 2003 리소스 킷에 포함된,

Windows Server 2003 Resource Kit Tools 
; http://www.microsoft.com/en-us/download/details.aspx?id=17657

rpccfg.exe 프로그램을 사용해서,

Minimizing Windows Server 2003 network services 
; http://www.hsc.fr/ressources/breves/min_w2k3_net_srv.html.en

다음과 같이 원하는 범위를 지정할 수 있고,

C:\>rpccfg /pe 5050-5070
The following ports/port range will be used for Internet ports
        5050-5070

조회할 수 있습니다.

C:\>rpccfg /d 0
The following ports/port range will be used for Internet ports
        5050-5070

Windows Server 2008 이후로는 자체 내장된 netsh을 이용해 다음과 같이 조회할 수 있고,

C:\WINDOWS\system32>netsh int ipv4 show dynamicport tcp

Protocol tcp Dynamic Port Range
---------------------------------
Start Port      : 1025
Number of Ports : 64510

이렇게 쉽게 설정할 수 있습니다.

netsh int ipv4 set dynamicport tcp start=10000 num=1000
netsh int ipv4 set dynamicport udp start=10000 num=1000
netsh int ipv6 set dynamicport tcp start=10000 num=1000
netsh int ipv6 set dynamicport udp start=10000 num=1000

설정된 값은 레지스트리(2003의 경우, HKEY_LOCAL_MACHINE\Software\Microsoft\Rpc)에 기록됩니다.

How to configure RPC dynamic port allocation to work with firewalls 
; http://support.microsoft.com/kb/154596/en-us

기본적으로는 레지스트리에 아무런 값이 없습니다.

port_reserve_1.png

하지만, "rpccfg /pe 5050-5070"와 같이 한번이라도 실행해 주면 이렇게 포트 범위가 설정됩니다.

port_reserve_2.png

참고로, 기본 예약 포트의 범위는 운영체제의 서버/클라이언트 버전에 따라서도 다릅니다. 가령, 클라이언트 운영체제인 Windows 8.1의 경우 다음과 같은 예약 범위를 보여줍니다.

E:\>netsh int ipv4 show dynamicport tcp

Protocol tcp Dynamic Port Range
---------------------------------
Start Port      : 1025
Number of Ports : 64510




다행히 조금 더 공격적인 포트 제외 방법이 있습니다.

How to reserve a range of ephemeral ports on a computer that is running Windows Server 2003 or Windows 2000 Server 
; http://support.microsoft.com/kb/812873

You cannot exclude ports by using the ReservedPorts registry key in Windows Server 2008 or in Windows Server 2008 R2 
; http://support.microsoft.com/kb/2665809

가령 윈도우 8.1에서 다음과 같이 실행해 보면,

D:\>netsh int ipv4 show excludedportrange protocol=tcp

Protocol tcp Port Exclusion Ranges

Start Port    End Port
----------    --------
        80          80
      8012        8013
      8023        8025
      8090        8093

* - Administered port exclusions.

예약된 포트를 알 수 있습니다. 재미있는 것은, IIS 서버의 웹 사이트에 할당한 포트가 기본적으로 포함되어 있다는 것입니다. 따라서, IIS 웹 사이트용 포트는 절대 다른 프로그램에서 사용하는 TCP 소켓과 충돌이 발생하지 않습니다.

물론, 우리가 원하는 포트를 다음과 같은 명령어로 추가/삭제할 수 있습니다.

C:\Windows\system32>netsh int ipv4 Add excludedportrange protocol=tcp startport=1000 numberofports=5 store=persistent
Ok.


C:\Windows\system32>netsh int ipv4 delete excludedportrange protocol=tcp startport=1000 numberofports=5 store=persistent
Ok.

따라서, 서버에서 충돌이 발생할 수 있는 범위의 포트가 있다면 이런 식으로 예약해 두는 것이 좋습니다.




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

[연관 글]






[최초 등록일: ]
[최종 수정일: 11/10/2023]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 166  167  168  169  170  171  172  173  [174]  175  176  177  178  179  180  ...
NoWriterDateCnt.TitleFile(s)
653정성태1/29/200922009.NET Framework: 122. XML Serializer를 이용한 값 복사: 성능은 어떨까!파일 다운로드1
652정성태1/22/200922789.NET Framework: 121. WPF - PrintTicket provider failed to bind to printer.
651정성태1/20/200920090.NET Framework: 120. 타입이 다른 배열끼리의 변환
650정성태1/19/200931896COM 개체 관련: 21. C/C++ 프로젝트에 /clr 옵션 적용으로 인한 COM 개체 사용 오류
649정성태1/18/200929430Windows: 38. Q1U UMPC에 Windows 7 베타 설치하기
648정성태1/18/200928118Windows: 37. Windows PE를 USB 메모리에 적용
647정성태1/18/200938263Windows: 36. Windows PE ISO 이미지 만들기 [1]
646정성태1/18/200931246디버깅 기술: 23. COMPLUS_ZapDisable - JIT 최적화 코드 생성 제어 [1]
645정성태1/11/200930044Windows: 35. 서명되지 않은 드라이버 로딩 방법
644정성태1/11/200921129Windows: 34. VPC 설치 후기 [2]
643정성태1/10/200926519Windows: 33. Windows 7 베타와 VMA 충돌 [1]
642정성태1/8/200925253개발 환경 구성: 34. Sysinternals의 모든 툴을 한번에 업데이트 하는 방법 [1]
641정성태1/7/200922392기타: 27. D820 - A09 바이오스 업데이트 프로그램 패치 [2]
640정성태1/4/200924096Team Foundation Server: 29. ClickOnce 응용 프로그램 배포를 Team Build에 추가.
639정성태1/4/200922062Team Foundation Server: 28. PFX 코드 서명을 포함한 프로젝트의 팀 빌드 실패 - MSB4018
638정성태1/3/200925099.NET Framework: 119. WPF - 의존 속성 정의에서 XamlParseException 발생하는 예 [2]
637정성태1/1/200927320기타: 26. 2008년 인기 순위 정리
636정성태12/31/200822427.NET Framework: 118. 2진 검색을 이용한 리스트 정렬 삽입파일 다운로드1
635정성태12/29/200825082오류 유형: 66. 파일 암호화 오류 - Recovery policy configured for this system contains invalid recovery certificate
634정성태12/29/200839405기타: 25. 가상 키보드 관련 정리 [4]
633정성태12/20/200824871기타: 24. RMClock for x64 [2]
632정성태12/19/200833487기타: 23. D820 - 배터리 없이 바이오스 업데이트 방법 [2]파일 다운로드1
631정성태12/10/200842170VC++: 36. Detours 라이브러리를 이용한 Win32 API - Sleep 호출 가로채기 [3]
630정성태12/9/200822995.NET Framework: 117. WPF - TreeView에서 항목이 펼쳐질 때 Cursors.Wait 사용파일 다운로드1
629정성태12/7/200832160.NET Framework: 116. 소켓 연결 시간 제한
628정성태12/6/200821063.NET Framework: 115. Marshal 타입 관련 2가지 자원 해제 메서드파일 다운로드1
... 166  167  168  169  170  171  172  173  [174]  175  176  177  178  179  180  ...