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

비밀번호

댓글 작성자
 




... 106  107  108  109  110  111  112  113  [114]  115  116  117  118  119  120  ...
NoWriterDateCnt.TitleFile(s)
11075정성태10/20/201621006Windows: 127. Convert-WindowsImage.ps1 사용 방법 정리
11074정성태10/20/201629316Windows: 126. Windows Server 2016 평가판을 정식 버전으로 라이선스 변경하는 방법
11073정성태10/20/201625362.NET Framework: 613. 윈도우 데스크톱 응용 프로그램(예: Console)에서 알림 메시지(Toast notifications) 띄우기 [1]파일 다운로드1
11072정성태10/20/201621887VC++: 102. 새로 추가한 ATL COM 객체가 regsvr32.exe로 등록이 안 되는 문제
11071정성태10/20/201625417.NET Framework: 612. UWP(유니버설 윈도우 플랫폼) 앱에서 콜백 함수 내에서의 UI 요소 접근 방법 [1]
11070정성태10/20/201619626Windows: 125. 윈도우 서버 2016 마이그레이션
11069정성태10/19/201627345.NET Framework: 611. C++ 개발자들을 위한 C# Thread 동작 방식 [2]
11068정성태10/19/201630539Windows: 124. 윈도우 운영체제의 시간 함수 (5) - TSC(Time Stamp Counter)와 QueryPerformanceCounter [12]파일 다운로드1
11067정성태10/18/201626860Windows: 123. 윈도우 운영체제의 시간 함수 (4) - RTC, TSC, PM Clock, HPET Timer [2]
11066정성태10/17/201624505Windows: 122. 윈도우 운영체제의 시간 함수 (3) - QueryInterruptTimePrecise, QueryInterruptTime 함수파일 다운로드1
11065정성태10/15/201629946Windows: 121. 윈도우 운영체제의 시간 함수 (2) - Sleep 함수의 동작 방식 [1]
11064정성태10/14/201621633.NET Framework: 610. C# - WaitOnAddress Win32 API 사용파일 다운로드1
11063정성태10/14/201637754Windows: 120. 윈도우 운영체제의 시간 함수 (1) - GetTickCount와 timeGetTime의 차이점 [5]파일 다운로드1
11062정성태10/12/201618371오류 유형: 361. WCF .svc 호출 시 Could not find a base address that matches scheme net.tcp 예외
11061정성태10/12/201630482오류 유형: 360. IIS - 500.19 오류 (0x80070021)
11060정성태10/12/201622950오류 유형: 359. WCF - .svc 요청시 404 Not Found
11059정성태10/11/201627222.NET Framework: 609. WPF - 다중 스레드 환경에서 데이터 바인딩의 INotifyPropertyChanged.PropertyChanged에 대한 배려 [1]파일 다운로드1
11058정성태10/8/201622539개발 환경 구성: 303. Windows 10 Bash Shell - 한글 환경을 영문으로 바꾸고 싶다면?
11057정성태10/8/201617936오류 유형: 358. Windows 10 bash shell - sudo: unable to resolve host ...
11056정성태10/8/201620794개발 환경 구성: 302. Windows 10 bash shell 시작 시 [...] packages can be updated.
11055정성태10/8/201621850.NET Framework: 608. double 값을 구할 때는 반드시 피연산자를 double로 형변환! [6]
11054정성태10/5/201625870개발 환경 구성: 301. "Let's Encrypt" SSL 인증서를 Azure Cloud Services(classic)에 업데이트하는 방법
11053정성태10/5/201620559.NET Framework: 607. C# try/catch/finally의 IL 코드 표현
11052정성태9/27/201634712개발 환경 구성: 300. C# DLL에서 Win32 C/C++처럼 dllexport 함수를 제공하는 방법 [7]파일 다운로드1
11051정성태9/25/201621892개발 환경 구성: 299. docker - c:\programdata\docker\windowsfilter 폴더 정리하는 방법파일 다운로드1
11050정성태9/24/201626646VC++: 101. 반올림하지 않고 double 변수 값 출력하는 방법 [3]
... 106  107  108  109  110  111  112  113  [114]  115  116  117  118  119  120  ...