서버용 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
기본적으로는 레지스트리에 아무런 값이 없습니다.
하지만, "rpccfg /pe 5050-5070"와 같이 한번이라도 실행해 주면 이렇게 포트 범위가 설정됩니다.
참고로, 기본 예약 포트의 범위는 운영체제의 서버/클라이언트 버전에 따라서도 다릅니다. 가령, 클라이언트 운영체제인 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.
따라서, 서버에서 충돌이 발생할 수 있는 범위의 포트가 있다면 이런 식으로 예약해 두는 것이 좋습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]