找出为啥 Mingw64 编译的应用程序无法在特定端口上绑定套接字?
Posted
技术标签:
【中文标题】找出为啥 Mingw64 编译的应用程序无法在特定端口上绑定套接字?【英文标题】:Finding out why Mingw64 compiled app cannot bind socket on specific port?找出为什么 Mingw64 编译的应用程序无法在特定端口上绑定套接字? 【发布时间】:2021-11-21 09:13:31 【问题描述】:所以,我在 Windows 10 上,并使用来自 MSYS2 的最新 MINGW64:
$ uname -a
MINGW64_NT-10.0-19043 DESKTOP-XXXXXXX 3.2.0-340.x86_64 2021-08-02 16:30 UTC x86_64 Msys
我在使用 Winsock 绑定时遇到了一些奇怪的事情,我现在可以在一个最小的工作示例上重建它,这是来自 Winsock Server And Client Example: "getaddrinfo" was not declared in this scope 的基本服务器代码,我保存为 test.cpp
(编辑:代码现在带有打印输出, EDIT2:并带有输入参数):
#undef UNICODE
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
// Need to link with Ws2_32.lib
#pragma comment (lib, "Ws2_32.lib")
// #pragma comment (lib, "Mswsock.lib")
#define DEFAULT_BUFLEN 512
//~ #define DEFAULT_PORT "27015"
#define DEFAULT_PORT "9010"
void print_getaddrinfo_response(struct addrinfo *result);
int __cdecl main(int argc, char **argv)
WSADATA wsaData;
int iResult;
SOCKET ListenSocket = INVALID_SOCKET;
SOCKET ClientSocket = INVALID_SOCKET;
struct addrinfo *result = NULL;
struct addrinfo hints;
char defaultport[8];
int iSendResult;
char recvbuf[DEFAULT_BUFLEN];
int recvbuflen = DEFAULT_BUFLEN;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
printf("WSAStartup failed with error: %d\n", iResult);
return 1;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// here, argc==1 for no arguments
if (argc==2)
snprintf( defaultport, 8, "%s", argv[1] );
else
snprintf( defaultport, 8, "%s", DEFAULT_PORT );
printf("Listening on port: %s ...", defaultport);
// Resolve the server address and port
iResult = getaddrinfo(NULL, defaultport, &hints, &result);
if ( iResult != 0 )
printf("getaddrinfo failed with error: %d\n", iResult);
WSACleanup();
return 1;
//print_getaddrinfo_response(result);
// Create a SOCKET for connecting to server
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET)
printf("socket failed with error: %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
freeaddrinfo(result);
iResult = listen(ListenSocket, SOMAXCONN);
if (iResult == SOCKET_ERROR)
printf("listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
if (argc==2) // exit immediately
printf(" exiting\n");
closesocket(ListenSocket);
WSACleanup();
return 0;
// Accept a client socket
ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET)
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
// No longer need server socket
closesocket(ListenSocket);
// Receive until the peer shuts down the connection
do
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
// Echo the buffer back to the sender
iSendResult = send( ClientSocket, recvbuf, iResult, 0 );
if (iSendResult == SOCKET_ERROR)
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
printf("Bytes sent: %d\n", iSendResult);
else if (iResult == 0)
printf("Connection closing...\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
while (iResult > 0);
// shutdown the connection since we're done
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
printf("shutdown failed with error: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
// cleanup
closesocket(ClientSocket);
WSACleanup();
return 0;
void print_getaddrinfo_response(struct addrinfo *result)
// from https://docs.microsoft.com/en-us/windows/win32/api/ws2def/ns-ws2def-addrinfoa
INT iRetval;
int i = 1;
struct addrinfo *ptr = NULL;
struct sockaddr_in *sockaddr_ipv4;
LPSOCKADDR sockaddr_ip;
char ipstringbuffer[46];
DWORD ipbufferlength = 46;
// Retrieve each address and print out the hex bytes
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next)
printf("getaddrinfo response %d\n", i++);
printf("\tFlags: 0x%x\n", ptr->ai_flags);
printf("\tFamily: ");
switch (ptr->ai_family)
case AF_UNSPEC:
printf("Unspecified\n");
break;
case AF_INET:
printf("AF_INET (IPv4)\n");
sockaddr_ipv4 = (struct sockaddr_in *) ptr->ai_addr;
printf("\tIPv4 address %s\n",
inet_ntoa(sockaddr_ipv4->sin_addr) );
break;
case AF_INET6:
printf("AF_INET6 (IPv6)\n");
// the InetNtop function is available on Windows Vista and later
// sockaddr_ipv6 = (struct sockaddr_in6 *) ptr->ai_addr;
// printf("\tIPv6 address %s\n",
// InetNtop(AF_INET6, &sockaddr_ipv6->sin6_addr, ipstringbuffer, 46) );
// We use WSAAddressToString since it is supported on Windows XP and later
sockaddr_ip = (LPSOCKADDR) ptr->ai_addr;
// The buffer length is changed by each call to WSAAddresstoString
// So we need to set it for each iteration through the loop for safety
ipbufferlength = 46;
iRetval = WSAAddressToString(sockaddr_ip, (DWORD) ptr->ai_addrlen, NULL,
ipstringbuffer, &ipbufferlength );
if (iRetval)
printf("WSAAddressToString failed with %u\n", WSAGetLastError() );
else
printf("\tIPv6 address %s\n", ipstringbuffer);
break;
case AF_NETBios:
printf("AF_NETBIOS (NetBIOS)\n");
break;
default:
printf("Other %ld\n", ptr->ai_family);
break;
printf("\tSocket type: ");
switch (ptr->ai_socktype)
case 0:
printf("Unspecified\n");
break;
case SOCK_STREAM:
printf("SOCK_STREAM (stream)\n");
break;
case SOCK_DGRAM:
printf("SOCK_DGRAM (datagram) \n");
break;
case SOCK_RAW:
printf("SOCK_RAW (raw) \n");
break;
case SOCK_RDM:
printf("SOCK_RDM (reliable message datagram)\n");
break;
case SOCK_SEQPACKET:
printf("SOCK_SEQPACKET (pseudo-stream packet)\n");
break;
default:
printf("Other %ld\n", ptr->ai_socktype);
break;
printf("\tProtocol: ");
switch (ptr->ai_protocol)
case 0:
printf("Unspecified\n");
break;
case IPPROTO_TCP:
printf("IPPROTO_TCP (TCP)\n");
break;
case IPPROTO_UDP:
printf("IPPROTO_UDP (UDP) \n");
break;
default:
printf("Other %ld\n", ptr->ai_protocol);
break;
printf("\tLength of this sockaddr: %d\n", ptr->ai_addrlen);
printf("\tCanonical name: %s\n", ptr->ai_canonname);
这是我在 MINGW64 中编译的:
$ g++ test.cpp -g -o test.exe -lws2_32
...就构建而言,它可以毫无问题地编译。但是运行时:
如果您拥有原始链接帖子中的代码,使用#define DEFAULT_PORT "27015"
,那么就没有问题,并且代码可以正常工作 - 我正在从 cmd.exe
运行它:
D:\>test.exe
getaddrinfo response 1
Flags: 0x0
Family: AF_INET (IPv4)
IPv4 address 0.0.0.0
Socket type: SOCK_STREAM (stream)
Protocol: IPPROTO_TCP (TCP)
Length of this sockaddr: 16
Canonical name: (null)
Bytes received: 7
Bytes sent: 7
Connection closing...
...上述情况是为了响应使用 telnet(我从 MINGW64 bash
shell 调用)触发它而发生的:
$ telnet 127.0.0.1 27015
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
hello
hello
↔
telnet> q
Connection closed.
如果你有这篇文章中的代码,也就是#define DEFAULT_PORT "9010"
,它编译得很好;但是当我尝试运行它时,它会立即退出:
D:\>test.exe
getaddrinfo response 1
Flags: 0x0
Family: AF_INET (IPv4)
IPv4 address 0.0.0.0
Socket type: SOCK_STREAM (stream)
Protocol: IPPROTO_TCP (TCP)
Length of this sockaddr: 16
Canonical name: (null)
bind failed with error: 10013
现在,Socket error 10013 is a message which implies that a port is blocked and/or unreachable - 我猜这意味着,端口 9010 以某种方式被阻止了?!
但是,到目前为止,我无法确认此端口是否以任何方式被阻止:
在test.exe
程序上查询防火墙(在管理命令提示符下)给了我:
D:\>netsh firewall show config | findstr test
Enable Inbound test / D:\test.exe
...我猜的意思是,允许传入连接?
D:\>netsh firewall show config | findstr 9010
D:\>
以上没有返回任何内容,因此防火墙中似乎没有明确提及端口 9010。
并查询开放的监听端口9010(如后台有挂起的进程,阻止test.exe启动),同样无返回:
D:\>netstat -a -n | findstr 9010
D:\>
那么,我到底该如何调试,并找出/确认,为什么此应用程序中的端口 9010 无法访问 - 但端口 27015 工作正常?!
编辑:我添加了打印输出以回应评论:
getaddrinfo
函数可以返回结果列表
...在这里,它似乎只返回一个 - 对于地址 0.0.0.0
另外,我同意:
您正在使用某人不希望您使用的端口。
...本质上,我想找出不希望我使用端口 9010 的人是什么 - 特别是因为当我尝试上述命令时,我没有收到任何消息,任何正在使用端口 9010。
EDIT2:代码现在可以通过输入参数接受端口,如果是这种情况,它会立即退出(因此,所有关于重新编译和调用的注释仍然有效)。
也就是说,现在我可以这样调用循环了:
$ for i in $(seq 8080 11000); do ./test.exe $i; done
Listening on port: 8080 ... exiting
Listening on port: 8081 ... exiting
Listening on port: 8082 ... exiting
...
Listening on port: 8818 ... exiting
Listening on port: 8819 ... exiting
Listening on port: 8820 ...bind failed with error: 10013
Listening on port: 8821 ...bind failed with error: 10013
...
到目前为止,我发现使用这种技术,该程序无法绑定到端口 8820:9519 [diff 700]、9676:9875 [diff 200] ... 可能还有其他端口。
问题是:为什么这些范围确实失败了,而其他范围则没有,我如何使用任何 Windows 应用程序(GUI 或命令行?)确认这一点?
【问题讨论】:
getaddrinfo
函数可以返回一个 list 结果,并且大多数显示它的示例将使用循环调用 socket
和 bind
直到两者都成功。你试过吗?此外,它可能不是无法访问的端口,它可能是您尝试绑定的接口。您尝试绑定的接口(IP 地址)是什么,您检查了吗?
感谢@Someprogrammerdude - 很棒的建议,会尝试并尝试回复结果。
Per Windows Sockets Error Codes: "WSAEACCES 10013 Permission denied... WSAEACCES 错误的另一个可能原因是调用绑定函数时(在带有 SP4 的 Windows NT 4.0 和稍后),另一个应用程序、服务或内核模式驱动程序以独占访问绑定到同一地址。 这种独占访问是 Windows NT 4.0 SP4 及更高版本的新功能,通过使用 SO_EXCLUSIVEADDRUSE 选项实现." IOW,你正在使用一个别人不想让你使用的端口。
感谢@RemyLebeau - 编辑后;我完全同意某些东西不希望我使用端口 9010。但我无法确认这是什么东西 - 例如,netstat
没有显示任何在端口 9010 上侦听的进程。
0.0.0.0
是“全部”或“任何”接口 (INADDR_ANY
)。所以看起来@RemyLebeau 确实是正确的。
【参考方案1】:
嗯,终于我找到了一种方法,进行独立检查,确认我在问题中观察到的行为。
经过审查,我已经得出结论,Q 中的程序不能绑定和监听至少端口范围 8820:9519 和 9676:9875 - 但它可以绑定到这些范围之外的端口进行监听。
所以首先,我找到了:Troubleshoot port exhaustion issues - Windows Client Management | Microsoft Docs:
您可以使用以下 netsh 命令查看计算机上的动态端口范围:
netsh int ipv4 show dynamicport tcp netsh int ipv4 show dynamicport udp netsh int ipv6 show dynamicport tcp netsh int ipv6 show dynamicport udp
现在,这本身并没有帮助 - “动态端口”是“临时端口”,that is used for only a short period of time for the duration of a communication session。所以,现在适用于这个问题。
然后,经过 很多 次失误,我终于打开了以下页面:Error 10013 when you bind excluded port again - Windows Server | Microsoft Docs:
假设您通过在运行 Windows Server 2012 R2、Windows Server 2012 或 Windows Server 2008 R2 的计算机上运行以下命令来排除端口:
netsh int ipv4 add excludedportrange protocol = tcp startport = Integer numberofports = 1
太棒了——除了我不想排除端口,我想显示排除的端口——所以采用上一篇文章的语法,我在管理员命令提示符下尝试了这个 (cmd.exe
) :
D:\>netsh int ipv4 show excludedportrange protocol=tcp
Protocol tcp Port Exclusion Ranges
Start Port End Port
---------- --------
1074 1173
1174 1273
1348 1447
1448 1547
1548 1647
1648 1747
1748 1847
1848 1947
1948 2047
2048 2147
2148 2247
5357 5357
8820 8919 ## 8820:9519
8920 9019 ## 8820:9519
9020 9119 ## 8820:9519
9120 9219 ## 8820:9519
9220 9319 ## 8820:9519
9320 9419 ## 8820:9519
9420 9519 ## 8820:9519
9676 9775 ## 2) 9676:9875
9776 9875 ## 2) 9676:9875
9984 10083
10084 10183
10184 10283
10284 10383
10384 10483
10584 10683
10684 10783
10784 10883
10884 10983
10984 11083
11084 11183
11184 11283
50000 50059 *
* - Administered port exclusions.
您可以看到我在哪里标记了通过在 OP 中运行程序获得的“禁止”端口范围,与 excludeportrange 输出的范围相匹配,并带有 ##
标记。
好吧,终于!现在问题比比皆是:
为什么excludedportrange 将原本单一的不间断范围显示为多个(7 或2 个)连续范围?! 为什么 excludeportrange 在 Windows 防火墙中不可见或可配置(至少到目前为止我找不到它)?好吧,至少我现在知道为什么我会观察自己的行为;真是一种解脱……
编辑:解决方案在这里Huge amount of ports are being reserved · Issue #5306 · microsoft/WSL;在这里也注意到了:Cannot bind to some ports due to permission denied
什么对我有用:
D:\src\ola_mingw64_install>net stop winnat
The Windows NAT Driver service was stopped successfully.
D:\>net start winnat
The Windows NAT Driver service was started successfully.
D:\>netsh int ipv4 show excludedportrange protocol=tcp store=active
Protocol tcp Port Exclusion Ranges
Start Port End Port
---------- --------
5357 5357
50000 50059 *
* - Administered port exclusions.
D:\>test.exe
Listening on port: 9010 ...Bytes received: 7
Bytes sent: 7
Connection closing...
嗯,那是一次糟糕的经历!想象一下,曾经有一段时间,我认为计算机和编程会让事情变得更容易,哈哈:)
【讨论】:
以上是关于找出为啥 Mingw64 编译的应用程序无法在特定端口上绑定套接字?的主要内容,如果未能解决你的问题,请参考以下文章
在 mingw-w64/msys2 中编译的应用程序,“应用程序无法正确启动(0xc00007b”