大体原理:WSAAsyncSelect模式允许以windows消息的形式接收网络事件通知。这个模式是为了适应windows消息驱动环境而设置的,对性能要求不高的网络应用程序可采用此模式。
要注意的地方:网络事件消息抵达消息处理函数后,应用程序首先检查Lparam参数的高位,以判断在套接字上发生了网络错误。宏WSAGETSELECTERROR返回高字节包含的错误信息。若应用程序发现套接字上没有产生任何错误便可用宏WSAGETSELECTEVENT读取LPARAM参数的低字位确定发生的网络事件。
优缺点:WSAAsyncSelect模型最突出的特点是与windows的消息驱动机制融在了一起,这使得开发带GUI界面的网络程序变得很简单。但是如果连接增加,单个WINDOWS函数处理上千个客户请求时,服务器性能势必发受到影响。
主要函数:WSAAsyncSelect。
主要参数:发送的通知码含义:
FD_READ:套接字接收到对方发送过来的数据包,表明可以去读套接字了。
FD_WRITE: 数据缓冲区满后再次变空时,通知应用 程序可以继续发送数据了。
注:send函数先将要发送的数据放入缓冲区中后,由windows负责将数据发送出去。如果缓冲区满了, 则不能发送数据了。当FD_WRITE后,可以再发送数据。
FD_ACCEPT,FD_CONNECT,FD_CLOSE。看单词就明白了。
DELPHI代码:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls,JwaWinsock2;
const
WM_SOCKET=WM_USER+100;
type
TForm1 = class(TForm)
mmo1: TMemo;
btn1: TButton;
mmo2: TMemo;
btn2: TButton;
procedure FormCreate(Sender: TObject);
procedure btn1Click(Sender: TObject);
procedure btn2Click(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
procedure Self_WndProc(var Msg: TMessage); //拦截窗体的所有消息
public
{ Public declarations }
end;
var
Form1: TForm1;
Oldwindowproc:TWndMethod;
sListen:TSocket;
WSData: TWSAData;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
OldWindowProc := Self.WindowProc; //保存原来的函数地址;
Self.WindowProc :=Self_WndProc; //指定新的函数地址;
WSAStartup($0202, WSData);
sListen:=INVALID_SOCKET;
sListen:=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
end;
procedure TForm1.Self_WndProc(var msg:TMessage);
var
sClient, sEvent: TSocket;
addrRemote: TSockAddrIn;
addrlen:Integer;
szText:array[0..255] of AnsiChar;
begin
if Msg.Msg=WM_SOCKET then
begin
sEvent:=Msg.WParam;
if WSAGETASYNCERROR(Msg.LParam)<>0 then
begin
closesocket(sEvent);
end
else
begin
case WSAGETSELECTEVENT(Msg.LParam) of
FD_ACCEPT: //当监听到套接字有连接进入时。
begin
addrlen:=SizeOf(addrRemote);
sClient:=accept(sEvent,psockaddr(@addrRemote),@addrlen);
WSAAsyncSelect(sClient,Self.Handle,WM_SOCKET,FD_ACCEPT or FD_CLOSE or FD_READ or FD_WRITE);
mmo1.Lines.Add('当前连接IP信息:'+strpas(inet_ntoa(addrRemote.sin_addr)));
end;
FD_WRITE:
begin
end;
FD_READ:
begin
if recv(sEvent,szText,SizeOf(szText),0)>0 then
begin
mmo2.Lines.Add('接收的数据是:'+strpas(sztext));
end
else
begin
closesocket(sEvent);
end;
end;
FD_CLOSE:
begin
closesocket(sEvent);
mmo1.Lines.Add('用户断开连接。');
end;
end;
end;
end;
OldWindowProc(Msg);
end;
procedure TForm1.btn1Click(Sender: TObject); //启动临听服务
var
sins:sockaddr_in;
begin
if sListen<>INVALID_SOCKET then
begin
Sins.sin_family :=AF_INET;
sins.sin_port :=htons(4567);
sins.sin_addr.S_addr:=INADDR_ANY;
if bind(sListen,@sins,SizeOf(sins))=SOCKET_ERROR then
begin
ShowMessage('无法进行端口绑定。');
Exit;
end;
WSAAsyncSelect(sListen,Self.Handle,WM_SOCKET,FD_ACCEPT or FD_CLOSE or FD_READ or FD_WRITE);
listen(sListen,5);
btn1.Enabled:=False;
end;