我有一台服务器使用双线程系统来管理100到200个并发连接。它使用TCP套接字,因为数据包传送保证很重要(它是一个通信系统,错过的远程API调用可以FUBAR一个客户端。)
我已经实现了一个自定义协议层,将传入的字节分成数据包并正确发送(下面包含了这个库)。我意识到使用MSG_PEEK的问题,但据我所知,它是唯一能满足库实现需求的系统。我愿意接受建议,特别是如果它可能是问题的一部分。
基本上,问题在于,随着客户端每4次成功发送一个keepalive数据包,服务器会因为缺少传入数据包而丢弃客户端的套接字超过20秒。我可以验证服务器本身没有离线,并且遇到问题的用户(包括我自己)的连接是稳定的。
发送/接收库在这里:
short ncsocket::send(wstring command, wstring data) {
wstringstream ss;
int datalen = ((int)command.length() * 2) + ((int)data.length() * 2) + 12;
ss << zero_pad_int(datalen) << L"|" << command << L"|" << data;
int tosend = datalen;
short __rc = 0;
do{
int res = ::send(this->sock, (const char*)ss.str().c_str(), datalen, NULL);
if (res != SOCKET_ERROR)
tosend -= res;
else
return FALSE;
__rc++;
Sleep(10);
} while (tosend != 0 && __rc < 10);
if (tosend == 0)
return TRUE;
return FALSE;
}
short ncsocket::recv(netcommand& nc) {
vector<wchar_t> buffer(BUFFER_SIZE);
int recvd = ::recv(this->sock, (char*)buffer.data(), BUFFER_SIZE, MSG_PEEK);
if (recvd > 0) {
if (recvd > 8) {
wchar_t* lenstr = new wchar_t[4];
memcpy(lenstr, buffer.data(), 8);
int fulllen = _wtoi(lenstr);
delete lenstr;
if (fulllen > 0) {
if (recvd >= fulllen) {
buffer.resize(fulllen / 2);
recvd = ::recv(this->sock, (char*)buffer.data(), fulllen, NULL);
if (recvd >= fulllen) {
buffer.resize(buffer.size() + 2);
buffer.push_back((char)L'\0');
vector<wstring> data = parsewstring(L"|", buffer.data(), 2);
if (data.size() == 3) {
nc.command = data[1];
nc.payload = data[2];
return TRUE;
}
else
return FALSE;
}
else
return FALSE;
}
else
return FALSE;
}
else {
::recv(this->sock, (char*)buffer.data(), BUFFER_SIZE, NULL);
return FALSE;
}
}
else
return FALSE;
}
else
return FALSE;
}
这是用于确定是否已经过了太多时间的代码:
if ((int)difftime(time(0), regusrs[i].last_recvd) > SERVER_TIMEOUT) {
regusrs[i].sock.end();
regusrs[i].is_valid = FALSE;
send_to_all(L"removeuser", regusrs[i].server_user_id);
wstringstream log_entry;
log_entry << regusrs[i].firstname << L" " << regusrs[i].lastname << L" (suid:" << regusrs[i].server_user_id << L",p:" << regusrs[i].parent << L",pid:" << regusrs[i].parentid << L") was disconnected due to idle";
write_to_log_file(server_log, log_entry.str());
}
“regusrs [i]”正在使用我用于故事套接字描述符和用户信息的向量的当前迭代成员。 'is_valid'检查用于判断关联用户是否是实际用户 - 这样做是为了防止系统必须释放向量成员 - 它只是将其返回到可用插槽池。没有线程访问/超出范围的问题。
无论如何,我开始怀疑服务器本身是不是问题。我目前正在另一台服务器上进行测试,但是我想知道另一组眼睛是否可以阻止某些不合适的事情,或者让我知道插槽和扩展的Keepalive这个我不知道的概念。
提前致谢!
答案 0 :(得分:2)
我想我看到你正在使用MSG_PEEK
做什么,在那里你等到看起来你有足够的数据来读取完整的数据包。但是,我会怀疑这一点。 (仅仅通过查看源代码的这一小部分而不是整个事物,很难确定系统的动态行为。)
为避免使用MSG_PEEK
,请遵循以下两个原则:
当您收到数据已准备好的通知时(我假设您正在使用select
),然后从recv()
读取所有等待数据。您可以使用多个recv()
来电,这样您就可以处理传入的数据。
如果您只读取部分数据包(长度或有效负载),则将其保存到下次获取读取通知时。将数据包和有效负载重新放在一起,不要将它们留在套接字缓冲区中。
另外,使用new
/ memcpy
/ wtoi
/ delete
的效率非常低。您根本不需要分配内存,可以使用局部变量。然后你根本不需要memcpy
,只需要演员。
我认为你已经假定你的数据包的长度不能超过999字节。