Winsock发送呼叫很慢
我正在制作一个可能需要处理3000多个连接的游戏服务器(TCP)。 目前它有50个连接,我已经遇到滞后。Winsock发送呼叫很慢
我发现它的winsock send()调用每次需要100〜300ms才能返回,因为它是单线程服务器,所以整个服务器的速度非常慢。
到目前为止,我已经想到了两种解决方案。
- 重新设计我的服务器,为每个客户端创建一个线程(为3000个客户端创建3000多个线程是否真的很稳定?)。
- 找到一种方法使send()调用立即返回。
这是我的套接字初始化代码:
int ret = WSAStartup(MAKEWORD(2,2), &wsaData);
if(ret != 0)
{
printf("Winsock failed to start.\n");
system("pause");
return;
}
server.sin_family = AF_INET;
server.sin_addr.s_addr = INADDR_ANY;
server.sin_port = htons(52000);
sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
printf("Invalid Socket.\n");
system("pause");
return;
}
if(bind(sock, (sockaddr*)&server, sizeof(server)) != 0)
{
printf("error");
return;
}
if(listen(sock, 5) != 0)
{
printf("error");
return;
}
在一个单独的线程接受代码
sockaddr_in from;
int fromlen = sizeof(from);
SOCKET sTmpSocket = accept(ServerSock, (struct sockaddr*)&from, &fromlen);
我的发送功能
void CClient::SendPacket(BYTE* pPacket, DWORD Len)
{
DWORD ToSendLen = Len;
while (ToSendLen)
{
int iResult = send(this->ClientSocket, (char*)(pPacket + Len - ToSendLen), ToSendLen, 0);
if (iResult == SOCKET_ERROR)
return;
ToSendLen -= iResult;
}
}
这是我的服务器线程(不完整,只有相关部分)
while (1)
{
for (int i = 0; i < MAX_CLIENT; i++)
{
if (Clients[i].bConnected == false)
continue;
timeval timeout;
timeout.tv_sec = 0;
timeout.tv_usec = 1;
fd_set socketset;
socketset.fd_count = 1;
socketset.fd_array[0] = Clients[i].ClientSocket;
if (select(0, &socketset, 0, 0, &timeout))
{
int RecvLen = recv(Clients[i].ClientSocket, (char*)pBuffer, 10000, MSG_PEEK);
if (RecvLen == SOCKET_ERROR)
{
Clients[i].bConnected = false;
iNumClients--;
}
else if (RecvLen == 0)
{
Clients[i].bConnected = false;
iNumClients--;
}
else if (RecvLen > 0)
{
// Packet handling here
recv(Clients[i].ClientSocket, (char*)pBuffer, dwDataLen, MSG_WAITALL);
//...
}
}
}
Sleep(1);
}
任何帮助将不胜感激。 谢谢。
我会去IOCP(IO completion ports),它可以带来理想的表现,并且可以很好地扩展。看看Boost.Asio,它使用IOCP(在窗口中)。
拥有3000个以上线程的想法非常糟糕,实际上并没有扩展(就内存消耗和上下文切换而言)!
我看着IOCP,所有我发现它是帮助我检测套接字事件而不是做for循环。 如何帮助send()通话延迟? – user1537941 2012-07-19 14:04:01
IOCP有助于使发送操作异步。请参阅[boost :: asio :: async_write(..)](http://www.boost.org/doc/libs/1_50_0/doc/html/boost_asio/reference/async_write.html) – Simon 2012-07-19 17:12:17
立即想到一件事,那就是使用非阻塞套接字并检查套接字是否可用select
写入。
作为一个侧面说明,您使用的错误来自select
的返回值。它在超时时返回0
,如果在任何集合中都有套接字,则返回一个正数,如果有错误则返回-1
。你不检查错误。此外,虽然在winsock版本中不需要,但select
的第一个参数实际上应该是最高套接字号加1。
还有一点,你只使用select
一个客户端的时间,加上所有客户端的设置,然后做一个select
:
fd_set readset;
FD_ZERO(&readset);
SOCKET max_socket = 0;
for (int i = 0; i < MAX_CLIENT; i++)
{
if (Clients[i].bConnected)
{
FD_SET(Clients[i].ClientSocket, &readset);
max_socket = std::max(max_socket, Clients[i].ClientSocket);
}
}
int res = select(max_socket + 1, &readset, 0, 0, &timeout);
if (res == SOCKET_ERROR)
std::cout << "Error #" << WSAGetLastError() << '\n';
else if (res > 0)
{
for (int i = 0; i < MAX_CLIENT; i++)
{
if (Clients[i].bConnected && FD_ISSET(Clients[i].ClientSocket, &readset))
{
// Read from client
}
}
}
您的想法是为每个客户端创建一个发送缓冲区,追加每次在该缓冲区发送的数据,然后在一个单独的线程中检查套接字是否可写,然后为每个客户端调用winsock send()。 如果套接字是可写的,winsock send()调用将不会花费100ms以上的时间? – user1537941 2012-07-19 14:15:05
哦,这个想法是错误的。 fdset最多可以处理64个套接字。 – user1537941 2012-07-19 16:11:30
@ user1537941默认情况下,'fd_set'应该能够包含1024个套接字。在包含'
另一种方法要考虑的是有多个工作线程处理一部分活动连接并使其可配置。例如,您可以为每个内核创建n个线程,然后使用n进行试验。
这应该会给你很多多线程的好处,而不会为每个客户端的一个线程的极端。
是的,它会提供一个提升,但由于300ms +每次发送呼叫仍会阻止。 – user1537941 2012-07-19 15:10:17
如果您只发送小包,请使用setsockopt()设置TCP_NODELAY选项以开始。
这就是为什么游戏使用UDP - 虽然UDP不能保证通过数据包接收。另外,您发送的数据有多大? – BugFinder 2012-07-19 13:15:12
您应该立即找到使发送呼叫立即返回的方法。对于服务器来说,3000个这样的服务器将非常昂贵。我不知道关于winsock的具体内容,所以不会回答这个问题,但在C#中可以通过使用异步调用来完成。 – AlexDev 2012-07-19 13:16:01
游戏服务器正在使用TCP作为房间列表和重要数据包......这是我遇到瓶颈的地方。 我使用UDP作为玩家动作的游戏玩法等。 – user1537941 2012-07-19 13:57:05