WSAAsyncSelect
其实关于这个模型,网络上也有一个案例说明
老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,微软提供的WSAAsyncSelect模型就是这个意思。
异步选择(WSAAsyncSelect)模型是一个有用的异步 I/O 模型。利用这个模型,应用程序可在一个套接字上,接收以 Windows 消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型的核心即是WSAAsyncSelect函数。
WSAAsyncSelect函数定义如下:
int WSAAPI WSAAsyncSelect(
SOCKET s,
HWND hWnd,
u_int wMsg,
long lEvent
);
2
3
4
5
6
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说明的部分字段,完整说明参阅:
https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-wsaasyncselect
多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用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的高字(高位字)包含了可能出现的任何错误代码。
);
2
3
4
5
6
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.
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
客户端代码不变,可以使用相同模型也可以不同模型