在解决了select模型本身的同步阻塞问题后,我们还要处理send、recv、accept的执行阻塞。
我们socket的操作本质上都是字符串的拷贝复制,重叠IO是windows提供的一种异步读写文件的机制,将读的指令以及我们的buffer投给操作系统,然后函数直接返回,操作系统独立开个线程,将数据复制进咱们的buffer,数据复制期间,我们就可以去做其他事,即读写过程变成了异步,可以同时投递多个读写操作。
typedef struct _WSAOVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
WSAEVENT hEvent;
} WSAOVERLAPPED, *LPWSAOVERLAPPED;
前四个成员系统使用,我们不需要直接使用。
操作完成hEvent就会被置成有信号。
SOCKET WSAAPI WSASocketW(
int af,
int type,
int protocol,
LPWSAPROTOCOL_INFOW lpProtocolInfo,
GROUP g,
DWORD dwFlags
);
功能:创建一个SOCKET
参数:
值 | 含义 |
---|---|
0 | 不执行组操作 |
SG_UNCONSTRAINED_GROUP | 创建一个不受限制的套接字组,并使新的套接字成为第一个成员。 |
SG_CONSTRAINED_GROUP | 创建一个受约束的套接字组,并使新的套接字成为第一个成员。 |
值 | 含义 |
---|---|
WSA_FLAG_OVERLAPPED | 创建一个支持重叠I / O操作的套接字。 |
WSA_FLAG_MULTIPOINT_C_ROOT | 创建一个在多点会话中将为c_root的套接字。 |
WSA_FLAG_MULTIPOINT_C_LEAF | 创建一个在多点会话中将为c_leaf的套接字。 |
WSA_FLAG_MULTIPOINT_D_ROOT | 创建一个在多点会话中为d_root的套接字。 |
WSA_FLAG_MULTIPOINT_D_LEAF | 创建一个在多点会话中为d_leaf的套接字。 |
WSA_FLAG_ACCESS_SYSTEM_SECURITY | 创建一个套接字,使它能够在包含安全访问控制列表(SACL)而不是仅随意访问控制列表(DACL)的套接字上设置安全描述符。 |
WSA_FLAG_NO_HANDLE_INHERIT | 创建一个不可继承的套接字。 |
返回值:
代码:
// 创建socket
SOCKET socketServer = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (INVALID_SOCKET == socketServer)
{
printf("创建WSASocket失败 error:%d\n", WSAGetLastError());
WSACleanup();
return -1;
}
BOOL AcceptEx(
SOCKET sListenSocket,
SOCKET sAcceptSocket,
PVOID lpOutputBuffer,
DWORD dwReceiveDataLength,
DWORD dwLocalAddressLength,
DWORD dwRemoteAddressLength,
LPDWORD lpdwBytesReceived,
LPOVERLAPPED lpOverlapped
);
功能:接受一个新的连接,返回本地和远程地址,并且接收由所述客户端应用程序发送的数据的第一个块。
参数:
返回值:
WSAGetLastError返回错误:
代码:
int PostAccept()
{
g_sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
char str[1024] = {
0 };
DWORD dwRecvcount;
if (TRUE == AcceptEx(g_allSock[0], g_sock, str, 0, sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16, &dwRecvcount, &g_allOlp[0])) // 立即完成
{
printf("accept\n");
g_allSock[g_count] = g_sock;
g_allOlp[g_count].hEvent = WSACreateEvent();
PostRecv(g_count); // 投递recv
++g_count; // 客户端数量++
PostAccept(); // 投递accept
return 0;
}
else
{
int result = WSAGetLastError();
if (ERROR_IO_PENDING == result) // 延迟处理
{
return 0;
}
else
{
return result;
}
}
}
int WSAAPI WSARecv(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesRecvd,
LPDWORD lpFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
功能:投递异步接收信息
参数:
typedef struct _WSABUF {
ULONG len; /* the length of the buffer */
_Field_size_bytes_(len) CHAR FAR *buf; /* the pointer to the buffer */
} WSABUF, FAR * LPWSABUF;
值 | 含义 |
---|---|
MSG_PEEK | 窥视传入的数据。数据被复制到缓冲区中,但不会从输入队列中删除。 |
MSG_OOB | 处理OOB数据 |
MSG_PARTIAL | 此次接收到的数据是客户端发来的一部分,接下来接收下一部分 |
MSG_PUSH_IMMEDIATE | 通知传送尽快完成 |
MSG_WAITALL | 呼叫者提供的缓冲区已满或连接已关闭或请求已取消或发生错误才把数据发送出去 |
返回值:
WSAGetLastError返回错误:
代码:
int PostRecv(int index)
{
WSABUF wsabuf;
wsabuf.buf = g_strRecv[index];
wsabuf.len = MAX_RECV_COUNT;
DWORD dwRecvCount;
DWORD dwFlag = 0;
if (0 == WSARecv(g_allSock[index], &wsabuf, 1, &dwRecvCount, &dwFlag, &g_allOlp[index], NULL)) // 立即完成
{
// 接收到客户端消息
printf("Client Data : %s \n", wsabuf.buf);
memset(g_strRecv[index], 0, MAX_RECV_COUNT);
PostSend(index); // 给客户回信
PostRecv(index); // 对自己投递接收
return 0;
}
else
{
int result = WSAGetLastError();
if (ERROR_IO_PENDING == result) // 延迟处理
{
return 0;
}
else
{
return result;
}
}
}
int WSAAPI WSASend(
SOCKET s,
LPWSABUF lpBuffers,
DWORD dwBufferCount,
LPDWORD lpNumberOfBytesSent,
DWORD dwFlags,
LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
);
功能:投递异步发送信息
参数:
值 | 含义 |
---|---|
MSG_DONTROUTE | 指定不应对数据进行路由。Windows套接字服务提供者可以选择忽略此标志。 |
MSG_OOB | 处理OOB数据 |
MSG_PARTIAL | 此次接收到的数据是客户端发来的一部分,接下来接收下一部分 |
返回值:
WSAGetLastError返回错误:
代码:
int PostSend(int index)
{
WSABUF wsabuf;
wsabuf.buf = "ok";
wsabuf.len = MAX_RECV_COUNT;
DWORD dwSendCount;
DWORD dwFlag = 0;
if (0 == WSASend(g_allSock[index], &wsabuf, 1, &dwSendCount, dwFlag, &g_allOlp[index], NULL)) // 立即完成
{
return 0;
}
else
{
int result = WSAGetLastError();
if (ERROR_IO_PENDING == result) // 延迟处理
{
return 0;
}
else
{
return result;
}
}
}
采用一个一个询问
for (int i = 0; i < g_count; ++i)
{
// 询问事件
int result = WSAWaitForMultipleEvents(1, &(g_allOlp[i].hEvent), FALSE, 0, FALSE);
if (WSA_WAIT_FAILED == result || WSA_WAIT_TIMEOUT == result)
{
continue;
}
。。。
}
BOOL WSAAPI WSAGetOverlappedResult(
SOCKET s,
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer,
BOOL fWait,
LPDWORD lpdwFlags
);
功能:获取对应socket上的具体重叠io情况
参数:
返回值:
代码:
// 有信号
DWORD dwState;
DWORD dwFlag;
BOOL bFlag = WSAGetOverlappedResult(g_allSock[i], &g_allOlp[i], &dwState, TRUE, &dwFlag);
WSAResetEvent(g_allOlp[i].hEvent); // 信号置空
客户端异常下线
代码:
if (FALSE == bFlag)
{
int result = WSAGetLastError();
if (10054 == result)
{
printf("客户端异常下线\n");
// 关闭
closesocket(g_allSock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
// 从数组中删掉
g_allSock[i] = g_allSock[g_count - 1];
g_allOlp[i] = g_allOlp[g_count - 1];
// 循环控制变量-1
--i;
// 个数减-1
--g_count;
}
continue;
}
接收链接成功
代码:
if (0 == i) // accept
{
printf("accept\n");
g_allSock[g_count] = g_sock;
g_allOlp[g_count].hEvent = WSACreateEvent();
PostRecv(g_count); // 投递recv
++g_count; // 客户端数量++
PostAccept(); // 投递accept
continue;
}
接收数据为0客户端下线
代码:
if (0 == dwState)
{
printf("客户端正常下线\n");
// 关闭
closesocket(g_allSock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
// 从数组中删掉
g_allSock[i] = g_allSock[g_count - 1];
g_allOlp[i] = g_allOlp[g_count - 1];
// 循环控制变量-1
--i;
// 个数减-1
--g_count;
continue;
}
send或recv成功
代码:
if (0 != dwState) // 发送 或者 接收成功了
{
if (g_strRecv[i][0] != 0) // recv
{
// 接收到客户端消息
printf("Client Data : %s \n", g_strRecv[i]);
memset(g_strRecv[i], 0, MAX_RECV_COUNT);
PostSend(i); // 给客户回信
PostRecv(i); // 对自己投递接收
}
}
代码:
int PostAccept()
{
while (1)
{
g_sock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
char str[1024] = {
0 };
DWORD dwRecvcount;
if (TRUE == AcceptEx(g_allSock[0], g_sock, str, 0, sizeof(struct sockaddr_in) + 16,
sizeof(struct sockaddr_in) + 16, &dwRecvcount, &g_allOlp[0])) // 立即完成
{
printf("accept\n");
g_allSock[g_count] = g_sock;
g_allOlp[g_count].hEvent = WSACreateEvent();
PostRecv(g_count); // 投递recv
++g_count; // 客户端数量++
continue;
}
else
{
break;
}
}
return 0;
}
和上面使用不同的是用回调函数来处理相关逻辑。
回调函数参数类型:
LPWSAOVERLAPPED_COMPLETION_ROUTINE LpwsaoverlappedCompletionRoutine;
void LpwsaoverlappedCompletionRoutine(
DWORD dwError, // 错误码
DWORD cbTransferred, // 发送或者接收到的字节数
LPWSAOVERLAPPED lpOverlapped, // 重叠结构
DWORD dwFlags // 函数执行的方式(WSARecv参数5)
)
{
...}
可以看到和上面WSAGetOverlappedResult是对应的,所以处理逻辑也对应。
代码:
void CALLBACK RecvCall(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
if (10054 == dwError)
{
printf("客户端异常下线\n");
}
else if (0 == cbTransferred)
{
printf("客户端正常下线\n");
}
int i = lpOverlapped - &g_allOlp[0];
if (10054 == dwError || 0 == cbTransferred)
{
// 关闭
closesocket(g_allSock[i]);
WSACloseEvent(g_allOlp[i].hEvent);
// 从数组中删掉
g_allSock[i] = g_allSock[g_count - 1];
g_allOlp[i] = g_allOlp[g_count - 1];
// 个数减-1
--g_count;
}
else
{
// 接收到客户端消息
printf("Client Data : %s \n", g_strRecv[i]);
memset(g_strRecv[i], 0, MAX_RECV_COUNT);
PostSend(i); // 给客户回信
PostRecv(i); // 对自己投递接收
}
}
int PostRecv(int index)
{
WSABUF wsabuf;
wsabuf.buf = g_strRecv[index];
wsabuf.len = MAX_RECV_COUNT;
DWORD dwRecvCount;
DWORD dwFlag = 0;
if (0 == WSARecv(g_allSock[index], &wsabuf, 1, &dwRecvCount, &dwFlag, &g_allOlp[index], RecvCall)) // 立即完成
{
// 接收到客户端消息
printf("Client Data : %s \n", wsabuf.buf);
memset(g_strRecv, 0, MAX_RECV_COUNT);
PostSend(index); // 给客户回信
PostRecv(index); // 对自己投递接收
return 0;
}
else
{
int result = WSAGetLastError();
if (ERROR_IO_PENDING == result) // 延迟处理
{
return 0;
}
else
{
return result;
}
}
}
和上面使用不同的是用回调函数来处理相关逻辑。
代码:
void CALLBACK SendCall(DWORD dwError, DWORD cbTransferred, LPWSAOVERLAPPED lpOverlapped, DWORD dwFlags)
{
}
int PostSend(int index)
{
WSABUF wsabuf;
wsabuf.buf = "ok";
wsabuf.len = MAX_RECV_COUNT;
DWORD dwSendCount;
DWORD dwFlag = 0;
if (0 == WSASend(g_allSock[index], &wsabuf, 1, &dwSendCount, dwFlag, &g_allOlp[index], SendCall)) // 立即完成
{
return 0;
}
else
{
int result = WSAGetLastError();
if (ERROR_IO_PENDING == result) // 延迟处理
{
return 0;
}
else
{
return result;
}
}
}
因为recv和send相关消息都在回调函数中处理完成了,所以分类处理其实只有accept相关内容。
代码:
while (1)
{
int nRes = WSAWaitForMultipleEvents(1, &(g_allOlp[0].hEvent), FALSE, WSA_INFINITE, TRUE);
if (WSA_WAIT_FAILED == nRes || WSA_WAIT_IO_COMPLETION == nRes)
{
continue;
}
WSAResetEvent(g_allOlp[0].hEvent); // 信号置空
printf("accept\n");
g_allSock[g_count] = g_sock;
g_allOlp[g_count].hEvent = WSACreateEvent();
PostRecv(g_count); // 投递recv
++g_count; // 客户端数量++
PostAccept(); // 投递accept
}
可以看到我们的send、recv、accept的执行阻塞问题也解决了。
百度云链接:https://pan.baidu.com/s/1xBOiSADlAG2gO1TC6BBO_A
提取码:sxbd