分析
参考windows消息机制的模型。
消息机制:所有的用户操作均依次按顺序(有序处理)被记录,装进一个队列,消息队列由操作系统维护,系统通过我们设置的回调函数处理不同类型的消息。
操作系统为每个窗口创建一个消息队列并且维护,所以我们想要使用消息队列,那就要创建一个窗口,该模型只能用于windows,不过我们可以学到这种处理思想。
服务器
直到开始监听都是一模一样的。
创建窗口也不是重点。
异步选择
int WSAAPI WSAAsyncSelect(
SOCKET s,
HWND hWnd,
u_int wMsg,
long lEvent
);
功能:将socket与消息绑定在一起,并投递给操作系统
参数:
- s — socket
- hWnd — 窗口句柄
- wMsg — 消息编号
- lEvent — 消息类型(跟WSASelectEvent一模一样)
返回值:
- 如果成功,返回0。
- 如果失败,返回SOCKET_ERROR。
绑定服务器事件
代码:
// 自定义消息
#define UM_ASYNCSELECTMSG WM_USER + 1
// 异步选择
if (SOCKET_ERROR == WSAAsyncSelect(socketServer, hWnd, UM_ASYNCSELECTMSG, FD_ACCEPT))
{
printf("WSAAsyncSelect 失败 error:%d\n", WSAGetLastError());
closesocket(socketServer);
WSACleanup();
return -1;
}
绑定客户端事件
// 绑定客户端事件
if (SOCKET_ERROR == WSAAsyncSelect(socketClient, hWnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE))
{
printf("WSAAsyncSelect 失败 error:%d\n", WSAGetLastError());
closesocket(socketClient);
break;
}
事件分类处理
参数 | 信息 |
---|---|
客户端socket | (SOCKET)wParam |
产生的错误码 | HIWORD(lParam) |
具体的消息种类 | LOWORD(lParam) |
处理错误
因为有序,所以删除需要整体移位置。
SOCKET sock = (SOCKET)wparam; // 获取socket
// 获取错误
if (0 != HIWORD(lparam))
{
if (WSAECONNABORTED == HIWORD(lparam))
{
TextOut(hdc, 0, x, _T("客户端正常下线"), strlen("客户端正常下线"));
x += 15;
WSAAsyncSelect(sock, hWnd, 0, 0); // 关闭该socket上的消息
closesocket(sock);
// 记录数组中有序删除该socket
for (int i = 0; i < g_count; ++i)
{
if (sock == g_sockALL[i])
{
g_sockALL[i] = g_sockALL[g_count - 1];
--g_count;
break;
}
}
}
break;
}
处理其他消息
和事件选择模型类似
// 具体消息
switch (LOWORD(lparam))
{
case FD_ACCEPT:
{
TextOut(hdc, 0, x, _T("accept"), strlen("accept"));
x += 15;
SOCKET socketClient = accept(sock, NULL, NULL);
if (INVALID_SOCKET == socketClient)
{
printf("accept 失败 error:%d\n", WSAGetLastError());
break;
}
// 绑定客户端事件
if (SOCKET_ERROR == WSAAsyncSelect(socketClient, hWnd, UM_ASYNCSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE))
{
printf("WSAAsyncSelect 失败 error:%d\n", WSAGetLastError());
closesocket(socketClient);
break;
}
// 记录
g_sockALL[g_count] = socketClient;
++g_count;
}
break;
case FD_READ:
{
TextOut(hdc, 0, x, _T("read"), strlen("read"));
char str[1024] = { 0 };
if (SOCKET_ERROR == recv(sock, str, 1023, 0))
{
printf("recv 失败 error:%d\n", WSAGetLastError());
break;
}
else
{
// 给客户回信
if (SOCKET_ERROR == send(sock, "ok", strlen("ok") + 1, 0))
{
printf("send 失败 error:%d\n", WSAGetLastError());
}
}
TextOut(hdc, 30, x, str, strlen(str));
x += 15;
}
break;
case FD_WRITE:
TextOut(hdc, 0, x, _T("wrtie"), strlen("wrtie"));
x += 15;
break;
case FD_CLOSE:
TextOut(hdc, 0, x, _T("客户端正常下线"), strlen("客户端正常下线"));
x += 15;
WSAAsyncSelect(sock, hWnd, 0, 0); // 关闭该socket上的消息
closesocket(sock);
// 记录数组中有序删除该socket
for (int i = 0; i < g_count; ++i)
{
if (sock == g_sockALL[i])
{
g_sockALL[i] = g_sockALL[g_count - 1];
g_count--;
break;
}
}
}
问题
在一次处理过程中,客户端产生多次send,服务器会产生多次接收消息,第一次接收消息会收完所有信息。
运行结果
模型流程图
和事件选择模型一样把select模型的同步阻塞变成了异步阻塞。
源码链接
百度云链接:https://pan.baidu.com/s/1xBOiSADlAG2gO1TC6BBO_A
提取码:sxbd