Bug:两个客户端趋近于同一时刻关闭的情况,recv返回的是同一个socketID,应该是返回两个socketID才对
现状:启动一个服务器tcpip程序,三台客户端程序,每个20ms连接断开一次,一直循环。
tcp服务器端程序accept阻塞等待客户端连接
tcp服务器端程序recv阻塞等待客户端关闭
[201416][201604]11:44:36 ERROR: accept连接: 端口号54470连接,SOCKET号4852
[201416][201604]11:44:36 ERROR: accept连接: 端口号54726连接,SOCKET号4812
[201416][201604]11:44:36 ERROR: accept连接: 端口号54982连接,SOCKET号4868
[201416][204384]11:44:36 ERROR: recv断开: SOCKET号4852
[201416][204344]11:44:36 ERROR: recv断开: SOCKET号4812
[201416][204432]11:44:36 ERROR: recv断开: SOCKET号4868
[201416][201604]11:44:36 ERROR: accept连接: 端口号55238连接,SOCKET号4880
[201416][201604]11:44:36 ERROR: accept连接: 端口号55494连接,SOCKET号4836
[201416][201604]11:44:36 ERROR: accept连接: 端口号55750连接,SOCKET号4892
[201416][204496]11:44:36 ERROR: recv断开: SOCKET号4880
[201416][204544]11:44:36 ERROR: recv断开: SOCKET号4892
[201416][201604]11:44:36 ERROR: accept连接: 端口号56006连接,SOCKET号4900
[201416][201604]11:44:36 ERROR: accept连接: 端口号56262连接,SOCKET号4860
[201416][203984]11:44:36 ERROR: recv断开: SOCKET号4836
[201416][201604]11:44:36 ERROR: accept连接: 端口号56518连接,SOCKET号4916
[201416][204624]11:44:36 ERROR: recv断开: SOCKET号4916
[201416][204688]11:44:36 ERROR: recv断开: SOCKET号4916
[201416][201604]11:44:36 ERROR: accept连接: 端口号56774连接,SOCKET号4924
[201416][201604]11:44:36 ERROR: accept连接: 端口号57030连接,SOCKET号4884
[201416][204592]11:44:36 ERROR: recv断开: SOCKET号4860
[201416][201604]11:44:36 ERROR: accept连接: 端口号57286连接,SOCKET号4908
[201416][204648]11:44:36 ERROR: recv断开: SOCKET号4884
[201416][201604]11:44:36 ERROR: accept连接: 端口号57542连接,SOCKET号4956
[201416][204664]11:44:36 ERROR: recv断开: SOCKET号4908
[201416][204680]11:44:36 ERROR: recv断开: SOCKET号4884
[201416][201604]11:44:36 ERROR: accept连接: 端口号57798连接,SOCKET号4932
[201416][201604]11:44:36 ERROR: accept连接: 端口号58054连接,SOCKET号4964
[201416][204720]11:44:36 ERROR: recv断开: SOCKET号4956
[201416][204736]11:44:36 ERROR: recv断开: SOCKET号4932
[201416][204752]11:44:36 ERROR: recv断开: SOCKET号4964
[201416][201604]11:44:36 ERROR: accept连接: 端口号58310连接,SOCKET号4972
[201416][201604]11:44:36 ERROR: accept连接: 端口号58566连接,SOCKET号4940
[201416][201604]11:44:36 ERROR: accept连接: 端口号58822连接,SOCKET号4988
[201416][204784]11:44:36 ERROR:
现在的报错,应该和链表没有关系,因为没有用到链表,只打印了recv的值。问题出在了recv中,如果有两个客户端同一时刻断开的时候,recv返回时偶尔会出现同一个socketID.
两个节客户端是两个进程还是一个进程
趋于同一时刻,你可以当作断网处理了。
没有源代码是很难判断错误在哪里的。一般的情况,我们不能怀疑Java有问题,因为Java毕竟经历了这么多年,不可能会出现错误。所以,首先要检查自己写的代码是否有问题。
//客户端数据处理
DWORD WINAPI CTcpSocketServer::ClientDataThread(LPVOID pParam)
{
char tmpbuf[1024] = { 0 };//接收到的数据放在该临时空间中
CTcpSocketServer* m_pTCP = (CTcpSocketServer*)pParam;
CTcpEventInf tcpEvent(m_pTCP->GetPerrIp(), m_pTCP->GetPerrPort(), 0, m_pTCP);
CString cmd;
CString params;
int n;
SOCKET tmpclient = m_pTCP->client;//把客服端的连接保存一份,一个线程一个连接
int tmpclientID = tmpclient;
char *buf;//记录信息,并交给数据处理函数处理
CTCriticalSection g_csRecv;
while (1)
{
g_csRecv.Lock();
//int nNetTimeout = 1000;//1秒,
//setsockopt(tmpclient, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout, sizeof(int));
n = recv(tmpclient, tmpbuf, 1024, 0);//接收客服端的数据 阻塞
//客服端异常退出也应该结束该循环
if (!m_pTCP->server_status)//服务器关了,该循环结束
{
break;//直接返回在外面关闭socket
}
//收到数据异常,当接收数据为0的时候说明客户端断开了,客户端突然断电n为SOCKET_ERROR
if (n == 0 )
{
//客户端断开了,该释放socket、
//TRACE("客服端IP%s,端口号%d断开,数量%d,SOCKET号%d\n", m_pTCP->client_list.FindNode(tmpclient).IP, m_pTCP->client_list.FindNode(tmpclient).port, m_pTCP->client_list.getCount(), m_pTCP->client_list.FindNode(tmpclient).socket);//调试信息显示
//char *data = (char*)malloc(16);
//memset(data, 0, 16);
//memcpy(data, m_pTCP->client_list.FindNode(tmpclient).IP, 16);
TRACE("recv断开:SOCKET号%d\n", tmpclient);
#pragma region[日志]
char * cPrintChar = NULL;
CString strTmpClient;
strTmpClient.Format(_T("SOCKET号%d\n"), tmpclient); // 错误描述
cPrintChar = CDataConvert::CStringToCharArray(strTmpClient);
NMR_LOG(logERROR) << " recv断开: " << cPrintChar;
#pragma endregion[日志]
//shutdown(tmpclient, SD_BOTH);
///closesocket(tmpclient);//接收数据异常关闭客服端
m_pTCP->m_lpStatusProc(NULL, NULL, NULL, NULL, tmpclient);//把客户端状态反馈
return 0;
}
if (n == SOCKET_ERROR)
{
//m_pTCP->client_list.FindNode(tmpclient).status = false;//改变链接上来的客服端的状态
//m_pTCP->client_list.FindNode(tmpclient).m_hDataThread = 0;
//char *data = (char*)malloc(16);
//memset(data, 0, 16);
//memcpy(data, m_pTCP->client_list.FindNode(tmpclient).IP, 16);
//m_pTCP->client_list.DelNode(tmpclient);//显示完毕删除
TRACE("SOCKET_ERROR断开:SOCKET号%d\n",tmpclient);//调试信息显示
#pragma region[日志]
char * cPrintChar = NULL;
CString strTmpClient;
strTmpClient.Format(_T("SOCKET号%d\n"), tmpclient); // 错误描述
cPrintChar = CDataConvert::CStringToCharArray(strTmpClient);
NMR_LOG(logERROR) << " SOCKET_ERROR:recv断开: " << cPrintChar;
#pragma endregion[日志]
shutdown(tmpclient, SD_BOTH);
//closesocket(tmpclient);//接收数据异常关闭客服端
//m_pTCP->m_lpStatusProc(data, NULL, NULL, NULL, tmpclient);//把客户端状态反馈
return 0;
}
if (n>0)
{
buf = (char*)malloc(n + 1);//注意用完后记得释放内存,防止数据脏
memset(buf, 0, n);
memcpy(buf, tmpbuf, n);
buf[n] = '\0';
memset(tmpbuf, 0, 1024);
m_pTCP->m_lpDataArriveProc(buf, n, m_pTCP->m_dwUserData, &tcpEvent, tmpclientID);//数据传送回调用类,回调函数
}
g_csRecv.Unlock();
}
closesocket(tmpclient);//接收数据异常关闭客服端
g_csRecv.Unlock();
return 0;
}
//创建socket并且监听
DWORD WINAPI CTcpSocketServer::ListenAndAcceptThread(LPVOID pParam)
{
WSADATA wsaData;
sockaddr_in local;
CTcpSocketServer* m_pTCP = (CTcpSocketServer*)pParam;
//加载WinSocket库的函数,第一个参数用来指定想要加载的WinSokcet库的版本
int wsaret = WSAStartup(0x101, &wsaData);
if (wsaret != 0)//测试该版本是否能用
{
TRACE(" 设置的版本在该系统下无法使用\n");
return 0;
}
local.sin_family = AF_INET;
//使用设置IP和端口号
local.sin_addr.s_addr = inet_addr(m_pTCP->serverIP);
local.sin_port = htons((u_short)m_pTCP->serverProt);//服务器使用的端口号
//local.sin_addr.s_addr = INADDR_ANY;//使用当前主机配置的所有IP地址
//local.sin_port = htons( (u_short)20242 );//服务器使用的端口号
m_pTCP->m_sSocket = socket(AF_INET, SOCK_STREAM, 0);//把本地地址绑定到服务器、
if (INVALID_SOCKET == m_pTCP->m_sSocket)
{
return 0;
}
if (bind(m_pTCP->m_sSocket, (sockaddr*)&local, sizeof(local)) != 0)//绑定
{
return 0;
}
//if (listen(m_pTCP->m_sSocket, 10/*监听客户端上限*/) != 0)//监听
if (listen(m_pTCP->m_sSocket, SOMAXCONN/*监听客户端上限*/) != 0)//监听
{
return 0;
}
//
TRACE("服务器启动,开始监听\n");
while (true)
{
sockaddr_in from;//接收客服端IP
int fromlen = sizeof(from);
//此处考虑使用链表保存客服端的IP这样可以发方便的和客服端通信
m_pTCP->client = accept(m_pTCP->m_sSocket, (struct sockaddr*)&from, &fromlen);
if (!m_pTCP->server_status || m_pTCP->client == SOCKET_ERROR)//服务器关了,该循环结束
{
m_pTCP->m_hDataThread = 0;
break;
}
DWORD dwThreadId;
HANDLE h = CreateThread(NULL, 0, ClientDataThread, m_pTCP, 0, &dwThreadId);//创建客服端数据处理线程
//把链接上的客户端信息保存到链表中方便查询
CClientInf tmpInf(inet_ntoa(from.sin_addr), from.sin_port, m_pTCP->client, h);
tmpInf.status = true;
//m_pTCP->client_list.DelNode();//先删除已经断开的结点
m_pTCP->client_list.tailInsert(tmpInf);
#pragma region[日志]
char * cPrintChar = NULL;
CString strSQLError;
strSQLError.Format(_T("端口号%d连接,SOCKET号%d\n"), from.sin_port, m_pTCP->client); // 错误描述
cPrintChar = CDataConvert::CStringToCharArray(strSQLError);
NMR_LOG(logERROR) << " accept连接: " << cPrintChar;
#pragma endregion[日志]
TRACE("accept端口号%d连接,SOCKET号%d\n", from.sin_port, m_pTCP->client);//调试信息显示
m_pTCP->m_lpStatusProc(NULL, NULL, NULL, NULL,NULL);//把客户端状态反馈
}
return 0;
}
找到问题得原因了,以后客户端accept之后,启动客户端线程,一般情况下正常,但有的时候启动线程的时候耗时100毫秒,就造成了先来的客户端还在启动线程,后来的客户端将之前的客户端socket覆盖了。