c++ - 找出为什么 Mingw64 编译的应用程序无法在特定端口上绑定套接字?
问题描述
所以,我在 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 服务器和客户端示例的基本服务器代码:“getaddrinfo”没有在这个范围内声明,我保存为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
现在,套接字错误 10013 是一条消息,暗示端口被阻止和/或无法访问- 我猜这意味着端口 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 或命令行?)
解决方案
好吧,最后我找到了一种方法,进行独立检查,以确认我在问题中观察到的行为。
回顾一下,我已经得出结论,Q 中的程序至少不能绑定和监听端口范围 8820:9519 和 9676:9875 - 但它可以绑定到这些范围之外的端口进行监听。
所以首先,我发现:解决端口耗尽问题 - Windows 客户端管理 | 微软文档:
您可以使用以下 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
现在,这本身并没有帮助 - “动态端口”是“临时端口”,在通信会话期间仅用于很短的一段时间。所以,现在适用于这个问题。
然后,在错过了很多之后,我终于打开了以下页面:再次绑定排除端口时出现错误10013 - Windows Server | 微软文档:
假设您通过在运行 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 个)连续范围?!
- 为什么在 Windows 防火墙中 excludeportrange 不可见或可配置(至少到目前为止我找不到它)?
好吧,至少我现在知道为什么要观察自己的行为;终于解脱了...
编辑:解决方案在这里大量端口被保留 · 问题 #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...
嗯,那是一次糟糕的经历!想象一下,曾经有一段时间,我认为计算机和编程会让事情变得更容易,哈哈:)