【发布时间】:2015-08-05 16:24:02
【问题描述】:
我有一个 C++ 游戏服务器,我正在使用一个网络库在 Windows 中使用 winsock。 我一直在对我的服务器进行压力测试,看看它一次可以接受多少个连接。当我使用我的游戏客户端连接时它工作正常,但在我进行如下所述的压力测试后我的游戏客户端无法再连接。
压力测试是,我使用一个简单的 for 循环程序连接到我的服务器大约 1000 次,它只是启动与我的游戏服务器的 tcp 连接并立即关闭它。它们都连接在一起。然后,之后,我尝试连接我的游戏。游戏根本连不上。
我检查了库 (见下文) 中的 tcpaccept() 函数,没有输出。由于某种原因,accept() 在我“攻击”了 1000 个连接后停止接受连接。 什么可能使我的服务器停止接受连接?
以下是我对侦听和接受连接并关闭它们的循环的总结:
bool serverIsOn = true;
double listen = tcplisten(12345, 30000, 1);
setnagle(listen, true);
...
while(serverIsOn){
double playerSocket = tcpaccept(listen, 1);
if(playerSocket > -1){
cout << "Got a new connection, socket ID: " << playerSocket << endl;
//add their sockID to list here!
addSockIDToList(playerSocket);
}
//Loop through list of socks and parse their messages here..
//If their message size == 0, we close their socket via closesocket(sockID);
loopThroughSocketIdsAndCloseOnLeave();
}
cout << "Finished!" << endl;
这里是 tcplisten、tcpaccept、CSocket::CSocket(SOCKET)、CSocket::tcplisten(...) 和 CSocket::tcpaccept(...) 的定义:
double tcplisten(int port, int max, int mode)
{
CSocket* sock = new CSocket();
if(sock->tcplisten(port, max, mode))
return AddSocket(sock);
delete sock;
return -1;
}
double tcpaccept(int sockid, int mode)
{
CSocket*sock = (CSocket*)sockets.item(sockid);
if(sock == NULL)return -1;
CSocket*sock2 = sock->tcpaccept(mode);
if(sock2 != NULL)return AddSocket(sock2);
return -1;
}
...
CSocket::CSocket(SOCKET sock)
{
sockid = sock;
udp = false;
format = 0;
}
bool CSocket::tcplisten(int port, int max, int mode)
{
if((sockid = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) return false;
SOCKADDR_IN addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if(mode)setsync(1);
if(bind(sockid, (LPSOCKADDR)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
{
closesocket(sockid);
return false;
}
if(listen(sockid, max) == SOCKET_ERROR)
{
closesocket(sockid);
sockid = INVALID_SOCKET;
return false;
}
return true;
}
CSocket* CSocket::tcpaccept(int mode)
{
if(sockid == INVALID_SOCKET) return NULL;
SOCKET sock2;
if((sock2 = accept(sockid, (SOCKADDR *)&SenderAddr, &SenderAddrSize)) != INVALID_SOCKET)
{
//This does NOT get output after that 1000-'attack' test.
std::cout << "Accepted new connection!" << std::endl;
CSocket*sockit = new CSocket(sock2);
if(mode >=1)sockit->setsync(1);
return sockit;
}
return NULL;
}
在我的 1000 个连接压力测试之后,我可以做些什么来弄清楚为什么 accept() 不再接受连接?这与我在完成连接后关闭连接的方式有关吗?当我这样做时,我所做的只是调用:closesocket(sockID)。
请询问需要的任何其他代码!
编辑: 我刚刚注意到我的“压力测试”java 程序 在连接大约 668 次 后出现异常。这是一个例外:
Exception in thread "main" java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:579) at java.net.Socket.connect(Socket.java:528) at java.net.Socket.<init>(Socket.java:425) at java.net.Socket.<init>(Socket.java:208) at sockettest.SocketTest.main(SocketTest.java:63) Java Result: 1
【问题讨论】:
-
我不确定这是否是问题,但我也注意到当有人连接到服务器时,sockID 每次都会增加一。这个库似乎没有回收套接字 ID(不确定 winsock 是否应该这样工作)。例如,如果一个玩家的 sockID 为 2 并且他们离开了,那么下一个加入的玩家的 sockID 将是 3 而不是 2。
-
与其他平台不同,Windows 不使用 ID 号识别套接字。套接字是内核中的实际对象,因此使用对象句柄表示。一旦套接字关闭,该句柄可能会被重用。人们在关闭套接字后忘记使套接字句柄无效(将其设置为
INVALID_SOCKET)是一个常见错误,然后他们作用于非无效套接字句柄并最终作用于恰好发生在的完全不同的套接字上使用相同的句柄值。这就是为什么您应该在关闭句柄后始终使其无效。 -
connect()可能由于多种不同的原因而失败并出现“连接被拒绝”错误,但最常见的(假设防火墙没有阻止连接)是服务器套接字没有在监听端口,或者服务器套接字的待处理连接积压已满(这意味着accept()没有被足够频繁地调用,如果有的话)。 -
您好 Remy,使套接字 ID 句柄无效的正确代码是什么?我仍在试图弄清楚如何使用这个库。另外,我每次迭代都调用accept() 来查看是否有连接通过。它肯定是被调用的。以及如何检查积压是否已满,以及关闭套接字时清空它的正确方法是什么?
-
我在上一条评论中说过如何(将套接字句柄设置为
INVALID_SOCKET),例如:closesocket(sockid); sockid = INVALID_SOCKET;当listen()失败时,您在CSocket::tcplisten()中这样做,但在@987654331 失败时不会这样做@ 失败。