异步选择模型是基于窗口实现
窗口的创建如下:
1.创建窗口结构体
typedef struct tagWNDCLASSEXW {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCWSTR lpszMenuName;
LPCWSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEXW, *PWNDCLASSEXW, *NPWNDCLASSEXW, *LPWNDCLASSEXW;
2.注册窗口结构体
ATOM RegisterClassExA(
const WNDCLASSEXA *unnamedParam1
);
3.创建窗口
HWND CreateWindowExA(
DWORD dwExStyle,
LPCSTR lpClassName,
LPCSTR lpWindowName,
DWORD dwStyle,
int X,
int Y,
int nWidth,
int nHeight,
HWND hWndParent,
HMENU hMenu,
HINSTANCE hInstance,
LPVOID lpParam
);
4.显示窗口
BOOL ShowWindow(
HWND hWnd,
int nCmdShow
);
5.更新窗口
BOOL UpdateWindow(
HWND hWnd
);
6.消息循环
// 获取消息
BOOL GetMessage(
LPMSG lpMsg,
HWND hWnd,
UINT wMsgFilterMin,
UINT wMsgFilterMax
);
// 翻译消息
BOOL TranslateMessage(
const MSG *lpMsg
);
// 分发消息
LRESULT DispatchMessage(
const MSG *lpMsg
);
7.回调函数
LRESULT LRESULT DefWindowProcW(
HWND hWnd,
UINT Msg,
WPARAM wParam,
LPARAM lParam
);
服务端代码:(桌面应用程序)
/*
* 注意事项:
1.创建的工程是窗口
2.要将当前项目改成多字符集,要不然接收客户端发来的信息时,会出现乱码
TCP/IP网络模型之异步选择模型是基于窗口实现的
异步选择模型核心:
消息队列:
操作系统为每个窗口创建一个消息队列并且维护
所以想要使用消息队列,就必须创建一个窗口
异步选择模型步骤:
1.将socket绑定到一个消息上,并投递给系统,由系统帮我们进行监视
WSAAsyncSelect
2.创建socket数组,将所有的socket装进数组中,并定义一个变量进行记录装进socket数组的个数
3.通过回调函数中的参数2处理分类处理消息
4.通过参数3获取socket
5.通过参数4中的高位字节获取对应的错误码 HOWORD(lParam)
6.通过参数4中的低位字节获取对应的消息,进行分类处理事件 FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
网络模型中的 异步选择模型与事件选择模型的机制比较:
异步选择模型是处理 消息队列 基于消息机制
事件选择模型是处理 事件集合 基于事件机制
*/
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#pragma comment(lib, "ws2_32.lib")
// 自定义 函数 WSAAsyncSelect() 函数中参数3所需的宏 消息
#define UM_EVENTSELECTMSG WM_USER + 1 // WM_USER + 1 表示防止自定义的消息ID与系统的消息ID冲突
// 定义客户区HDC 中的y坐标
int y = 0; // 每一次使用之后要 y += 15 防止数据覆盖在一起
// 创建socket数组用于装所有的socket 定义成全局的,方便使用
SOCKET g_allsocks[1024];
// 定义记录将socket往socket数组装的个数变量
int g_count;
/*LRESULT CALLBACK WindowProc(
_In_ HWND hwnd, hwnd获取可操作的客户区 HDC 用于TextOut所需的参数1
_In_ UINT uMsg, 处理uMsg参数传入的消息 进行分类处理消息
_In_ WPARAM wParam, 处理通过wParam传入的socket
_In_ LPARAM lParam 处理lParam,通过HIWORD(lParam)处理错误
通过LOWORD(lParam)进行分类处理事件FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
);*/
// 声明窗口结构体中成员所需的回调函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 将打开网络库、检验版本、创建socket、绑定地址与端口号、开始监听 封装成函数
SOCKET createSocket(HWND hwnd);
// 窗口的主函数是WinMain
// 参数1:当前窗口的实例句柄(实例句柄是应用程序的名字或者叫ID编号),
// 参数2:上一个的实例句柄,已经无效,在这里只起到占位作用
// 参数3:命令行参数,
// 参数4:显示方式(窗口是最小化显示还是其他方式显示)
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE PrehInstance, LPSTR lpCmdLine, int nShowCmd)
{
// 1.创建窗口的结构体
WNDCLASSEX ws;
// 给窗口结构体成员赋值
ws.cbClsExtra = 0; // 窗口类的额外空间
ws.cbSize = sizeof(ws); // 窗口结构体大小
ws.lpfnWndProc = WindowProc; // 回调函数
ws.hInstance = hInstance; // 当前窗口的实例句柄 填WinMain中的参数1
ws.hCursor = NULL; // 光标(鼠标的样式)
ws.lpszClassName = "基于窗口的异步选择模型"; // 窗口类名 显示窗口左上角的名字
ws.cbWndExtra = 0; // 当前窗口的额外空间
ws.hbrBackground = CreateSolidBrush(RGB(227, 231, 249)); // 背景颜色
ws.hIcon = NULL; // 左上角的图标
ws.hIconSm = NULL; // 窗口图标,程序的图标(显示在桌面下的图标)
ws.lpszMenuName = NULL; // 窗口菜单名
ws.style = CS_HREDRAW | CS_VREDRAW; // 窗口移动之后的样式:水平刷新和垂直刷新
// 2.注册窗口结构体
RegisterClassEx(&ws);
// 3.创建窗口 CreateWindowEx();
// 参数1:窗口额外的风格, 参数2:窗口类的名字,注意参数2必须和上面的lpszClassName窗口类名一致,如果不一致会出Bug 点击运行打不开窗口
// 参数3:窗口名字, 参数4:窗口基本的风格
// 参数5,6:窗口的起始位置, 参数7,8:窗口宽高,参数9:父窗口,参数10:菜单栏,参数11:当前窗口实例句柄,参数12:回调函数的参数
HWND hwnd = CreateWindowEx(WS_EX_OVERLAPPEDWINDOW, "基于窗口的异步选择模型", "TCP/IP-服务端", WS_OVERLAPPEDWINDOW,
100, 0, 500, 1000, NULL, NULL, hInstance, NULL);
// 判断是否成功创建窗口
if (NULL == hwnd)
{
return 0;
}
// 4.显示窗口
ShowWindow(hwnd, nShowCmd);
// 5.更新窗口
UpdateWindow(hwnd);
// 创建客户区可操作的HDC 用完之后记得要释放
HDC hdc = GetDC(hwnd);
// 在创建窗口进行消息循环之前调用封装好的创建socket等网络步骤
SOCKET socketServer = createSocket(hwnd);
// 7.将socket绑定到一个消息上,并投递给系统 WSAAsyncSelect();
/* int WSAAsyncSelect(
SOCKET s, 参数1:要绑定的socket
HWND hWnd, 参数2:创建窗口类型,标识在网络事件发生时要接收的窗口句柄
u_int wMsg, 参数3:自定义信号,用于在回调函数中参数 (UINT uMsg) 要进行处理的自定义消息
long lEvent 参数4:要处理的网络事件,FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
这里因为绑定的是服务端的socket,所以事件只填FD_ACCEPT
);*/
int nAsyncSel = WSAAsyncSelect(socketServer, hwnd, UM_EVENTSELECTMSG, FD_ACCEPT);
// 检验是否成功绑定事件并投递给系统
if (SOCKET_ERROR == nAsyncSel)
{
int a = WSAGetLastError();
// 将int类型转成char 类型
char buf[10] = { 0 };
_itoa(a, buf, 10);
TextOut(hdc, 0, y, buf, strlen(buf));
//释放socket
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}
// 将socket装进到socket集合中
g_allsocks[g_count] = socketServer;
g_count++; // 注意个数要++
// 声明GetMessage函数中参数1所需的消息
MSG msg;
// 6.消息循环 注意GetMessage函数如果参数2填的是创建窗口的类型 hwnd 的话关闭当前窗口当前应用程序没有被关闭
// 进程没有结束,还在运行,所以要参数2要填NULL
while (GetMessage(&msg, NULL, 0, 0))
{
// 翻译消息 将消息进行处理一下
TranslateMessage(&msg);
// 分发消息 再将消息变量msg传给windows,让windows来调用消息处理函数
DispatchMessage(&msg);
}
// 释放客户区HDC
ReleaseDC(hwnd, hdc);
return 0;
}
// 实现窗口结构体中成员所需的回调函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
// hwnd获取可操作的客户区 HDC 用于TextOut所需的参数1
HDC hdc = GetDC(hwnd); // 记得用完之后要释放
// 分类处理通过uMsg参数传入的事件
switch (uMsg)
{
case UM_EVENTSELECTMSG: // 处理自定义事件
{
// 获取通过参数wParam 传入的socket
SOCKET sock = (SOCKET)wParam;
// 获取消息 通过参数lParam传递进来的消息
// LOWORD(lParam); 低位存具体的消息
// HIWORD(lParam); 高位存对应的错误码
// 1.通过HIWORD(lParam)判断是否出错
if (0 != HIWORD(lParam))
{
if (WSAECONNABORTED == HIWORD(lParam))
{
// 处理客户端下线操作
TextOut(hdc, 0, y, "client close!!!", strlen("client close!!!"));
y += 15;
// 关闭对应的socket上的消息,直接将函数WSAAsyncSelect();中的后两个参数置0就可以
WSAAsyncSelect(sock, hwnd, 0, 0);
// 从socket数组中删除对应的socket
for (int i = 0; i < g_count; i++)
{
// 从socket数组中找sock
if (sock == g_allsocks[i])
{
// 删除对应的sock在数组中的对应的元素 可以直接将最后第一个数组元素赋给当前sock在数组所在的位置,也就是覆盖
g_allsocks[i] = g_allsocks[g_count - 1];
// 注意记录的数组个数要--
g_count--;
break;
}
}
}
break;
}
// 2.通过LOWORD(lParam)获取具体的消息
// 分类处理事件 FD_ACCEPT FD_READ FD_WRITE FD_CLOSE
switch (LOWORD(lParam))
{
case FD_ACCEPT: // 处理FD_ACCEPT
{
// 调用accept函数接收连接 本质创建客户端socket 注意这里的sock 是服务端的socket
SOCKET socketClient = accept(sock, NULL, NULL);
// 检验是否成功创将客户端socket
if (INVALID_SOCKET == socketClient)
{
int a = WSAGetLastError();
break;
}
// 将成功创建出来的客户端socket绑定相应的消息,并投递给系统
int retAsyncSel = WSAAsyncSelect(socketClient, hwnd, UM_EVENTSELECTMSG, FD_READ | FD_WRITE | FD_CLOSE);
// 检验是否成功绑定并投递
if (SOCKET_ERROR == retAsyncSel)
{
// 绑定出错
int a = WSAGetLastError();
// 关闭成功创建出来的socket
closesocket(socketClient);
break;
}
// 打印成功调用accept创建客户端socket的提示
TextOut(hdc, 0, y, "accept ok", strlen("accept ok"));
y += 15;
// 将成功绑定并投递的客户端socket装进到socket数组中,方便之后释放使用
g_allsocks[g_count] = socketClient;
g_count++; // 注意个数要++
}
break;
case FD_READ: // 处理FD_READ
{
// 调用recv函数读取客户端发来的信息
// 定义接收数据的缓存区字符数组
char recvBuf[1024] = { 0 };
int retRecv = recv(sock, recvBuf, sizeof(recvBuf), 0); // 注意这里的sock表示的是客户端的socket
// 判断是否成功接收消息
if (SOCKET_ERROR == retRecv)
{
// 接收消息出错
int a = WSAGetLastError();
TextOut(hdc, 0, y, "处理FD_READ 调用recv出错", strlen("处理FD_READ 调用recv出错"));
y += 15;
break;
}
// 打印接收到客户端发来的信息
TextOut(hdc, 0, y, recvBuf, strlen(recvBuf));
y += 15;
}
break;
case FD_WRITE: // 处理FD_WRITE
// 可以直接send消息个客户端
// 这里直接打印write ok 表示可以进行send
TextOut(hdc, 0, y, "write ok", strlen("write ok"));
y += 15;
break;
case FD_CLOSE: // 处理FD_CLOSE
// 处理客户端下线操作
TextOut(hdc, 0, y, "client close!!!", strlen("client close!!!"));
y += 15;
// 关闭对应的socket上的消息,直接将函数WSAAsyncSelect();中的后两个参数置0就可以
WSAAsyncSelect(sock, hwnd, 0, 0);
// 从socket数组中删除对应的socket
for (int i = 0; i < g_count; i++)
{
// 从socket数组中找sock
if (sock == g_allsocks[i])
{
// 删除对应的sock在数组中的对应的元素 可以直接将最后第一个数组元素赋给当前sock在数组所在的位置,也就是覆盖
g_allsocks[i] = g_allsocks[g_count - 1];
// 注意记录的数组个数要--
g_count--;
break;
}
}
break;
}
}
break;
case WM_CREATE: // 初始化,只执行一次
break;
case WM_DESTROY: // 销毁窗口
PostQuitMessage(0); // 终止线程请求,并清理内存
break;
}
// 释放hdc
ReleaseDC(hwnd, hdc);
// 注意DefWindowProc函数参数要和上面WindowProc函数中的参数一一对应
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
// 将打开网络库、检验版本、创建socket、绑定地址与端口号、开始监听 封装成函数
// 传入的参数用于创建客户区可操作的 HDC
SOCKET createSocket(HWND hwnd)
{
// 创建客户区可操作的HDC 用完之后记得要释放
HDC hdc = GetDC(hwnd);
//1.打开网络库
WORD wVersion = MAKEWORD(2, 2);//打开2.2版本
WSADATA WSAData;
int nRes = WSAStartup(wVersion, &WSAData);
//判断WSAStartup函数的返回值
if (0 != nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
// 注意窗口用printf 打印不了 要使用 TextOut
//printf("请重新启动电脑试试,或者检查网络库\n");
TextOut(hdc, 0, y, "请重新启动电脑试试,或者检查网络库", strlen("请重新启动电脑试试,或者检查网络库"));
y += 15;
break;
case WSAVERNOTSUPPORTED:
//printf("请更新网络库\n");
TextOut(hdc, 0, y, "请更新网络库", strlen("请更新网络库"));
y += 15;
break;
case WSAEINPROGRESS:
//printf("请重新启动\n");
TextOut(hdc, 0, y, "请重新启动", strlen("请重新启动"));
y += 15;
break;
case WSAEPROCLIM:
//printf("请关闭不用的软件,给网络运行提供充足的资源\n");
TextOut(hdc, 0, y, "请关闭不用的软件,给网络运行提供充足的资源", strlen("请关闭不用的软件,给网络运行提供充足的资源"));
y += 15;
break;
}
return 0;
}
//2、检验版本
if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
{
int a = WSAGetLastError();
// 将int类型转成char 类型
char buf[10] = { 0 };
// 将int类型转成char类型的字符串
_itoa(a, buf, 10);
TextOut(hdc, 0, y, buf, strlen(buf));
//清理网络库
WSACleanup();
return 0;
}
//3.创建socket
SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//判读socket的返回值
if (INVALID_SOCKET == socketServer)
{
int a = WSAGetLastError();
// 将int类型转成char 类型
char buf[10] = { 0 };
_itoa(a, buf, 10);
TextOut(hdc, 0, y, buf, strlen(buf));
y += 15;
//清理网络库
WSACleanup();
return 0;
}
//4.绑定地址与端口
struct sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(12345);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if (SOCKET_ERROR == bind(socketServer, (const struct sockaddr*)&si, sizeof(si)))
{
int a = WSAGetLastError();
// 将int类型转成char 类型
char buf[10] = { 0 };
_itoa(a, buf, 10);
TextOut(hdc, 0, y, buf, strlen(buf));
y += 15;
//释放socket
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}
//5.开始监听
if (SOCKET_ERROR == listen(socketServer, SOMAXCONN))
{
int a = WSAGetLastError();
// 将int类型转成char 类型
char buf[10] = { 0 };
_itoa(a, buf, 10);
TextOut(hdc, 0, y, buf, strlen(buf));
//释放socket
closesocket(socketServer);
//清理网络库
WSACleanup();
return 0;
}
// 释放客户区HDC
ReleaseDC(hwnd, hdc);
return socketServer;
}
客户端代码:(控制台应用程序)
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Winsock.h>
#pragma comment(lib,"ws2_32.lib")
WSADATA WSAData;
// 1,打开网络库
void OpenWSAStarup()
{
// 1,打开网络库
WORD version = MAKEWORD(2, 2); //设置要使用的库的版本 MAKEWORD(主版本,副版本);
int n = WSAStartup(version, &WSAData);
// 检验是否成功打开网络库
if (0 != n)
{
// 打开网络库失败,通过n 返回的错误码,给出提示
switch (n)
{
case WSASYSNOTREADY:
printf("重启电脑试试,或者检查ws_2_32库是否存在\n");
break;
case WSAVERNOTSUPPORTED:
printf("当前库版本号不支持,尝试更换版本试试\n");
break;
case WSAEPROCLIM:
printf("已达到对Windows套接字实现支持的任务数量的限制\n");
break;
case WSAEINPROGRESS:
printf("正在阻止Windows Sockets 1.1操作,当前函数运行期间,因为某些原因造成阻塞\n");
break;
case WSAEFAULT:
printf("lpWASData参数不是有效的指针,参数写错了\n");
break;
default:
break;
}
}
}
// 2.检验版本
void CheckVersion()
{
if (2 != HIBYTE(WSAData.wVersion) || 2 != LOBYTE(WSAData.wVersion))
{
// 打开版本号 2.2 失败
printf("打开版本号 2.2 失败, 正在退出程序...\n");
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 3.创建socket
SOCKET CreateSocket()
{
// 3.创建套接字 socket 创建服务端的socket
SOCKET socketCreate = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
// 检验是否成功创建套接字
if (INVALID_SOCKET == socketCreate)
{
// 创建套接字失败
// 获取错误码
int error = WSAGetLastError();
printf("创建套接字失败,返回错误号为:%d\n", error);
// 关闭网络库
WSACleanup();
// 退出程序
return 0;
}
return socketCreate;
}
// 4.连接服务器
void Connection(SOCKET socket_)
{
// 4.连接到服务器
// 创建connect() 函数中参数2所需的结构体 用来装IP地址和端口号
sockaddr_in client_s;
//给结构体成员赋值
client_s.sin_family = AF_INET; //IP地址类型
client_s.sin_port = htons(12345); //端口号
client_s.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
int nCon = connect(socket_, (struct sockaddr*)&client_s, sizeof(client_s));
// 检验是否成功连接到服务端
if (SOCKET_ERROR == nCon)
{
// 连接服务端失败
// 获取错误码
int error = WSAGetLastError();
printf("连接服务端失败,返回错误号为:%d\n", error);
// 关闭套接字
closesocket(socket_);
// 关闭网络库
WSACleanup();
// 退出程序
return;
}
}
// 5.与服务端发送信息
void RecvAndSend(SOCKET socket_)
{
// 客户端只进行发送消息,因为服务端代码没有给客户端send消息,所以就没有进行接收消息处理
printf("我是客户端,正在发送信息,请稍等...\n");
// 5.与服务端进行收发信息
while (1)
{
// 发信息
char sendBuf[1024] = { 0 };
scanf_s("%s", sendBuf, 1024);
// 设置输入 0 时自动退出
if ('0' == sendBuf[0])
{
break; //退出循环
}
send(socket_, sendBuf, 1024, 0);
}
}
int main()
{
// 1,打开网络库
OpenWSAStarup();
// 2.校验版本
CheckVersion();
// 3.创建socket
SOCKET socketServer = CreateSocket();
// 4.连接服务器
Connection(socketServer);
// 5.与服务端发送信息
RecvAndSend(socketServer);
// 关闭套接字
closesocket(socketServer);
// 关闭网络库
WSACleanup();
system("pause");
return 0;
}
程序运行结果:
?
|