• 网络IO模型-异步选择模型(Delphi版)


    其实关于这个模型,网络上也有一个案例说明

    老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,微软提供的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说明的部分字段,完整说明参阅: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的高字(高位字)包含了可能出现的任何错误代码。
    );
    
    • 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.
    

    客户端代码不变,可以使用相同模型也可以不同模型

  • 相关阅读:
    Markdown标签
    macbook使用
    git的使用
    HTTPS的原理
    javascript中的对象
    javascript中this的指向问题
    javascript中的闭包
    javaScript中的return、break和continue
    Promise对象
    ORACLE_11G归档空间满,由于数据库装完后使用的是默认空间是闪回区
  • 原文地址:https://www.cnblogs.com/coder163/p/13814335.html
Copyright © 2020-2023  润新知