其实关于这个模型, 网络上也有一个案例说明
老陈使用了微软公司的新式信箱. 这种信箱非常先进, 一旦信箱里有新的信件, 盖茨就会给老陈打电话: 喂, 大爷, 你有新的信件了! 从此, 老陈再也不必频繁上下楼检查信箱了, 牙也不疼了, 微软提供的 WSAAsyncSelect 模型就是这个意思.
异步选择 (WSAAsyncSelect) 模型是一个有用的异步 I/O 模型. 利用这个模型, 应用程序可在一个套接字上, 接收以 Windows 消息为基础的网络事件通知. 具体的做法是在建好一个套接字后, 调用 WSAAsyncSelect 函数. 该模型的核心即是 WSAAsyncSelect 函数.
WSAAsyncSelect 函数定义如下:
- c++
- int WSAAPI WSAAsyncSelect(
- SOCKET s,
- HWND hWnd,
- u_int wMsg,
- long lEvent
- );
- delphi
- function WSAAsyncSelect(s: TSocket; hWnd: HWND; wMsg: u_int; lEvent: Longint): Integer; stdcall;
参数说明
参数名 | 具体含义 |
---|---|
s | 指定的是我们感兴趣的那个套接字。 |
hwnd | 指定一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口。 |
wMsg | 指定在发生网络事件时,打算接收的消息。该消息会投递到由 hWnd 窗口句柄指定的那个窗口。 |
lEvent | 指定一个位掩码,对应于一系列网络事件的组合 |
注意事项
wMsg 参数指定的消息通常是我们自定义的消息, 应用程序需要将这个消息设为比 Windows 的 WM_USER 大的一个值, 避免网络窗口消息与系统预定义的标准窗口消息发生混淆与冲突.
lEvent 参数指定的网络类型为: FD_READ,FD_WRITE,FD_ACCEPT,FD_CONNECT,FD_CLOSE 等. 当然, 到底使用 FD_ACCEPT, 还是使用 FD_CONNECT 类型, 要取决于应用程序的身份是客户端, 还是服务器. 如应用程序同时对多个网络事件有兴趣, 只需对各种类型执行一次简单的按位 OR(或)运算就 OK.
Value | Meaning |
---|---|
FD_READ | 应用程序想要接收有关是否可读的通知,以便读入数据 |
FD_WRITE | 应用程序想要接收有关是否可写的通知,以便写入数据 |
FD_ACCEPT | 应用程序想接收与进入连接有关的通知 |
FD_CONNECT | 应用程序想接收与一次连接完成的通知 |
FD_CLOSE | 应用程序想接收与套接字关闭的通知 |
摘取 MSDN 说明的部分字段, 完整说明参阅:
多个事件务必在套接字上一次注册! 另外还要注意的是, 一旦在某个套接字上允许了事件通知, 那么以后除非明确调用 closesocket 命令, 或者由应用程序针对那个套接字调用了 WSAAsyncSelect, 从而更改了注册的网络事件类型, 否则的话, 事件通知会永远有效! 若将 lEvent 参数设为 0, 效果相当于停止在套接字上进行的所有网络事件通知
若应用程序针对一个套接字调用了 WSAAsyncSelect, 那么套接字的模式会从 "阻塞" 变成 "非阻塞". 这样以来, 如果调用了像 WSARecv 这样的 Winsock 函数, 但当时却并没有数据可用, 那么必然会造成调用的失败, 并返回 WSAEWOULDBLOCK 错误. 为防止这一点, 应用程序应依赖于由 WSAAsyncSelect 的 uMsg 参数指定的用户自定义窗口消息, 来判断网络事件类型何时在套接字上发生; 而不应盲目地进行调用.
应用程序在一个套接字上成功调用了 WSAAsyncSelect 之后, 会在与 hWnd 窗口句柄对应的窗口例程中, 以 Windows 消息的形式, 接收网络事件通知. 窗口例程通常定义如下:
- LRESULT CALLBACK WindowProc(
- HWND hwnd, // 指定一个窗口的句柄, 对窗口例程的调用正是由那个窗口发出的.
- UINT uMsg, // 指定需要对哪些消息进行处理. 这里我们感兴趣的是 WSAAsyncSelect 调用中定义的消息.
- WPARAM wParam, // 指定在其上面发生了一个网络事件的套接字.(假若同时为这个窗口例程分配了多个套接字, 这个参数的重要性便显示出来了.)
- LPARAM lParam // 包含了两方面重要的信息. 其中, lParam 的低字 (低位字) 指定了已经发生的网络事件, 而 lParam 的高字 (高位字) 包含了可能出现的任何错误代码.
- );
Delphi 对这个函数的参数做了封装, 对应的结构体是 TMessage, 所以我们实际使用的只需要定义对应消息的处理函数即可
大家可以看出上面的文字说明很明显是 C++ 的, 大部分内容我是摘抄自网络, Delphi 版的我在网上没找到啥有用资料
参考博客: https://www.cnblogs.com/venow/archive/2012/06/09/2543053.html
代码实现
- unit MainFrm;
- interface
- uses
- Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
- System.Classes, Vcl.Graphics,
- Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ComCtrls, Vcl.StdCtrls;
- const
- MY_WM_SOCKET = WM_USER + 55;
- type
- TForm1 = class(TForm)
- Button1: TButton;
- StatusBar1: TStatusBar;
- Memo1: TMemo;
- Button2: TButton;
- procedure Button1Click(Sender: TObject);
- procedure FormCreate(Sender: TObject);
- private
- { Private declarations }
- procedure WMSocket(var Msg: TMessage); message MY_WM_SOCKET;
- public
- { Public declarations }
- end;
- var
- Form1: TForm1;
- implementation
- {$R *.dfm}
- uses Winapi.WinSock2, ScktComp;
- var
- WSAData: TWSAData;
- // 套接字对象, 用于监听
- ClientSocket, Server: TSocket;
- ServerRecord: sockaddr_in;
- procedure TForm1.Button1Click(Sender: TObject);
- begin
- // 初始化版本库
- if WSAStartup(WINSOCK_VERSION, WSAData) <> ERROR_SUCCESS then
- begin
- WSACleanup;
- Self.StatusBar1.Panels[0].Text := '初始化失败';
- Exit;
- end;
- // 初始化 socket
- Server := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
- // 创建失败
- if Server = INVALID_SOCKET then
- begin
- closesocket(Server);
- WSACleanup;
- Self.StatusBar1.Panels[0].Text := '初始化 socket 失败';
- Exit;
- end;
- // 指定 IP, 端口号和协议类型
- with ServerRecord do
- begin
- sin_family := PF_INET;
- sin_port := htons(10086);
- sin_addr.S_addr := inet_addr(PAnsiChar(AnsiString('127.0.0.1')));;
- end;
- // 绑定 IP 和端口号
- if bind(Server, TSockAddr(ServerRecord), SizeOf(ServerRecord)) = SOCKET_ERROR
- then
- begin
- closesocket(Server);
- WSACleanup;
- Self.StatusBar1.Panels[0].Text := '端口号被占用';
- Exit;
- end;
- if listen(Server, SOMAXCONN) = SOCKET_ERROR then
- begin
- closesocket(Server);
- WSACleanup;
- Self.StatusBar1.Panels[0].Text := '监听失败';
- Exit;
- end;
- // 核心函数
- WSAAsyncSelect(Server, Self.Handle, MY_WM_SOCKET, FD_ACCEPT or FD_READ or
- FD_WRITE or FD_CLOSE);
- // 禁用按钮
- Button1.Enabled := false;
- end;
- procedure TForm1.FormCreate(Sender: TObject);
- begin
- if WSACleanup <> ERROR_SUCCESS then
- Self.StatusBar1.Panels[0].Text := '初始化失败';
- if Server <> INVALID_SOCKET then
- closesocket(Server);
- Self.StatusBar1.Panels[0].Text := '网络库初始化成功';
- end;
- // 当产生网络消息的时候核心的处理函数
- procedure TForm1.WMSocket(var Msg: TMessage);
- begin
- if (Msg.Msg = MY_WM_SOCKET) then
- Self.StatusBar1.Panels[0].Text := '网络消息';
- case WSAGetSelectEvent(Msg.LParam) of
- FD_ACCEPT:
- begin
- var
- AddSize := SizeOf(ServerRecord);
- ClientSocket := accept(Server, @ServerRecord, @AddSize);
- var
- CustomWinSocket := TCustomWinSocket.Create(ClientSocket);
- Form1.Memo1.Lines.Add('客户端 IP:' + CustomWinSocket.RemoteAddress);
- end;
- FD_READ:
- begin
- end;
- FD_WRITE:
- begin
- end;
- end;
- end;
- end.
客户端代码不变, 可以使用相同模型也可以不同模型
来源: https://www.cnblogs.com/coder163/p/13814335.html