一个采用完全端口IO模型的服务器,经过测试,在LAN到LAN的局域网没有问题,在校园网,或者国内的服务器运行就没有问题,但是把服务器程序放到国外服务器运行就会出问题,并且一开始的几分钟没有问题,接收始终也没有问题,主要是一遇到丢包就会阻塞,使用WSASend函数发送的数据,很长时间(甚至 达到十分钟)不能得到完成通知,GetQueuedCompletionStatus一直无法得到WSASend的缓冲区的通知。百思不得其解,向CSDN的朋友们求助。该服务器,使用的缓冲对象定义:
typedef struct _BUF_OBJ
{
WSAOVERLAPPED wol;
struct _SOCK_OBJ *conn;
char *buf;
UINT len;
UINT off;
UINT size;
OPERATE operation;
DWORD flag;
DLIST   entry;
ULONG no;
}BUF_OBJ, *PBUF_OBJ;简化的socket对象结构体
typedef struct _SOCK_OBJ
{
SOCKET s;

BOOL IsClosing;

DLIST SendList; CRITICAL_SECTION Cris; DLIST   entry;
}SOCK_OBJ, *PSOCK_OBJ;在初始化完成端口,创建线程后,主要运行的有三种类型的线程,第一种是等待事件线程(单一个):DWORD WINAPI CIOCPServer::EeventThread(LPVOID lpParam)
{
CIOCPServer *pIocpSvr = NULL;
DWORD CurTime;
DWORD NextCheckTime;
int rc;
DWORD ret = 0; if(lpParam == NULL)
{
return UINT(-1);
} pIocpSvr = (CIOCPServer *)lpParam;
NextCheckTime = timeGetTime() + DEFAULT_CHECK_GAP;
while (TRUE)
{
rc = WSAWaitForMultipleEvents(
pIocpSvr->ThreadNum,
pIocpSvr->hThread,
FALSE,
DEFAULT_WAIT_GAP,
TRUE
);
if(rc == WAIT_IO_COMPLETION)
{
}
else if (rc == WAIT_FAILED)
{
//失败退出
break;
}
else if (rc == WAIT_TIMEOUT)
{
}
else if(rc == WAIT_OBJECT_0)
{
//收到退出事件,退出
break;
}

/********/
定期检查客户端列表代码,主要回收资源,和关闭长时间不活动的客户端连接
/*********/

}
}第二种完成线程,专门接受系统的完成通知,进行IO的数据处理,服务,有CPU个数*2 + 2个线程:DWORD WINAPI CIOCPServer::CompletionThread(LPVOID lpParam)
{
PSOCK_OBJ sock = NULL;
PBUF_OBJ buf = NULL;
OVERLAPPED *ol = NULL;
HANDLE Comp;
DWORD bytes;
DWORD Flags;
int rc;
int error;
DWORD time; if(lpParam == NULL)
{
return SOCKET_ERROR;
} Comp = (HANDLE)lpParam;
while (TRUE)
{
error = NO_ERROR;
SetLastError(0);
rc = GetQueuedCompletionStatus(
Comp,
&bytes,
(PULONG_PTR)&sock,
&ol,
INFINITE
);
if(ol == NULL)
{
break;
}
buf = CONTAINING_RECORD(ol, BUF_OBJ, wol); if (!rc)
{
rc = WSAGetOverlappedResult(
sock->s,
&buf->wol,
&bytes,
FALSE,
&Flags
);
if (rc == FALSE)
{
error = WSAGetLastError();
}
}

ENTER_CRITICAL_SECTION(&sock->Cris);
if (error != NO_ERROR)
{
FreeBuf(sock, buf);
}
else
{
IoHandler(sock, buf, Comp, bytes);
}
LEAVE_CRITICAL_SECTION(&sock->Cris); } return 0;
}
void IoHandler(PSOCK_OBJ sock, PBUF_OBJ buf, HANDLE Comp, DWORD bytes)
{
if(sock == NULL || buf == NULL || Comp == NULL)
{
return ;
} _ENTER_CRITICAL_SECTION(&sock->Cris);

if (buf->operation == OP_ACCEPT)
{
//主要是Accept代码,此处没有问题,忽略相关代码
sock->pGetAcceptExSockaddrs(
buf->buf,
0,
sizeof(SOCKADDR_STORAGE) + 16,
sizeof(SOCKADDR_STORAGE) + 16,
(SOCKADDR **)&Local,
&LocalLen,
(SOCKADDR **)&Remote,
&RemoteLen
); }
else if (buf->operation == OP_READ)
{
//获得取数据并处理
error = RxHandler(sock, buf, bytes);
if (error != NO_ERROR)
{
sock->IsClosing = TRUE;
} }
else if (buf->operation == OP_WRITE)
{
//写数据完成,继续发 buf->off = 0;
sock->TxBytes += bytes;
FreeBuf(sock, buf); error = TxHandler(sock);
if (error != NO_ERROR)
{
sock->IsClosing = TRUE;
MARK;
}
} _LEAVE_CRITICAL_SECTION(&sock->Cris);}int TxHandler(PSOCK_OBJ sock)
{ PBUF_OBJ buf = NULL;
PDLIST  root;
PDLIST  tmp;
int ret; if(sock == NULL)
{
return SOCKET_ERROR;
} ret = NO_ERROR; _ENTER_CRITICAL_SECTION(&sock->Cris); root = &sock->SendList;
tmp = root->next;
while(tmp != root)
{
                  //不断从发送列表取数据块发送
if(sock->SendNo >= DEFAULT_OVERLAPPED_COUNT)
{
                           //出现WSA_IO_PENDING数目超过DEFAULT_OVERLAPPED_COUNT,不再继续递交发送请求
break;
}
buf =  CONTAINING_RECORD(tmp, BUF_OBJ, entry);
if (PostSend(sock, buf) != NO_ERROR)
{
ret = SOCKET_ERROR;
break;
}
DLIST_DELETE(root, tmp);
tmp = tmp->next;
} _LEAVE_CRITICAL_SECTION(&sock->Cris); return ret;
}//提交发送请求。缓冲区在buf结构体里
int PostSend(PSOCK_OBJ sock, PBUF_OBJ buf, UINT bytesSent = 0)
{
WSABUF wbuf;
DWORD bytes;
int rc; if(sock == NULL || buf == NULL)
{
return SOCKET_ERROR;
} if(bytesSent >= buf->len)
{
return NO_ERROR;
} buf->operation = OP_WRITE; wbuf.buf = buf->buf + bytesSent;
wbuf.len = buf->len - bytesSent; bytes = wbuf.len; memset(&buf->wol, 0, sizeof(WSAOVERLAPPED));

_ENTER_CRITICAL_SECTION(&sock->Cris); rc = WSASend(
sock->s,
&wbuf,
1,
&bytes,
0,
&buf->wol,
NULL
); if (rc == SOCKET_ERROR)
{
DWORD flags;
flags = WSAGetLastError();
if (flags != WSA_IO_PENDING)
{
_LEAVE_CRITICAL_SECTION(&sock->Cris);
return SOCKET_ERROR;
}
else
{

//记录WSA_IO_PENDING的个数
sock->SendNo ++;
}
} _LEAVE_CRITICAL_SECTION(&sock->Cris); InterlockedIncrement(&sock->ref); return NO_ERROR;
}最后一种线程,是发送线程,负责取数据发送DWORD WINAPI CIOCPServer::DataThread(LPVOID lpParam)
{
CIOCPServer *pIocpSvr = NULL;
DWORD CurTime;
DWORD NextCheckTime;
int rc;
DWORD ret = 0; if(lpParam == NULL)
{
return UINT(-1);
} pIocpSvr = (CIOCPServer *)lpParam; while (TRUE)
{
//获取数据到sendbuf缓冲区中用于发送bytes字节
sendbuf = GetBuf(bytes);//这里sendbuf的内容已经用memset清零,
//重新申请内存,赋给指针buf,size是缓冲长度,len是数据长度

...........

//查找需要发送数据的客户端sock对象,

Send(sock, sendbuf, bytes);
}
}
int Send(PSOCK_OBJ sock, PBUF_OBJ buf, DWORD bytes)
{
if(sock == NULL || buf == NULL)
{
MARK;
return -1;
} if(bytes == 0)
{
return 0;
} ENTER_CRITICAL_SECTION(&sock->Cris);
if(sock->IsClosing)
{
FreeBuf(sock, buf);
LEAVE_CRITICAL_SECTION(&sock->Cris);
return 0;
}
buf->conn = sock;
buf->len = bytes;
//为了保证数据先进先出的处理顺序,先进队列,再发送
TxPending(sock, buf);
LEAVE_CRITICAL_SECTION(&sock->Cris); return bytes;
}void TxPending(PSOCK_OBJ sock, PBUF_OBJ buf)
{
if(sock == NULL || buf == NULL)
{
return;
} if(sock->IsClosing)
{
FreeBuf(sock, buf);
return;
} _ENTER_CRITICAL_SECTION(&sock->Cris); DLIST_INSERT_TAIL(&sock->SendList, &buf->entry);//缓冲对象进队列 _LEAVE_CRITICAL_SECTION(&sock->Cris); TxHandler(sock);//调用发送函数,发送数据。
}
在这里,一直不明白为什么WSASend发出去以后,几分钟后仍然没有收到系统通知,GetQueuedCompletionStatus没有返回,而同样环境的远程桌面工具已经恢复连接。
后来查了MSDN的WSASend文档,似乎也没有什么问题WSASend
The WSASend function sends data on a connected socket.int WSASend(
  SOCKET s,
  LPWSABUF lpBuffers,
  DWORD dwBufferCount,
  LPDWORD lpNumberOfBytesSent,
  DWORD dwFlags,
  LPWSAOVERLAPPED lpOverlapped,
  LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
Parameters

[in] Descriptor identifying a connected socket. 
lpBuffers 
[in] Pointer to an array of WSABUF structures. Each WSABUF structure contains a pointer to a buffer and the length of the buffer. This array must remain valid for the duration of the send operation. 
dwBufferCount 
[in] Number of WSABUF structures in the lpBuffers array. 
lpNumberOfBytesSent 
[out] Pointer to the number of bytes sent by this call if the I/O operation completes immediately. 
dwFlags 
[in] Flags used to modify the behavior of the WSASend function call. See Using dwFlags in the Res section for more information. 
lpOverlapped 
[in] Pointer to a WSAOVERLAPPED structure. This parameter is ignored for nonoverlapped sockets. 
lpCompletionRoutine 
[in] Pointer to the completion routine called when the send operation has been completed. This parameter is ignored for nonoverlapped sockets. 

解决方案 »

  1.   

    GetQueuedCompletionStatus设置为非无限等待试试看呢?
      

  2.   

    在你的代码
    ======================================
    else if (buf->operation == OP_WRITE) 

    //写数据完成,继续发 
    ======================================
    我没看到你处理写数据未完成时的处理,是你省略了,还是根本就没处理这个过程?
      

  3.   

    发送代码就在这行注释之后啊
    buf->off = 0; 
    sock->TxBytes += bytes; 
    FreeBuf(sock, buf); error = TxHandler(sock); 
    if (error != NO_ERROR) 

    sock->IsClosing = TRUE; 
    MARK; 
      

  4.   

    假如WSASend一次发送不完就会在本次未发完的位置再发一次,比较简单,不常见的状况,省略了其实就几行代码if(buf->len > bytes)
    {
    PostSend(sock, buf, bytes);}