WinSock I/O 模型 -- WSAAsyncSelect 模型

简介

WSAAsyncSelect 模型也是 WinSock 中常见的异步 I/O 模型。

使用这个模型,网络应用程序通过接收以 Windows 消息为基础的网络事件通知来处理网络请求。

这篇文章我们就来看看如何使用 WSAAsyncSelect api 来实现一个简单的 TCP 服务器.

API 基础

要使用 WSAAsyncSelect 模型,我们必须创建一个窗口, 再为该窗口对象提供一个窗口历程(WinProc). 通过适当的配置之后,当有网络请求到来的时候,windows 会将网络消息投递到我们所创建的窗口对象上,然后我们通过对应的窗口例程来处理该请求.

WinProc

WindowProc 回调函数用来处理 Windows 系统投递到特定窗口的消息。

它的方法签名如下:

LRESULT CALLBACK WindowProc(
  _In_ HWND   hwnd,
  _In_ UINT   uMsg,
  _In_ WPARAM wParam,
  _In_ LPARAM lParam
);
  • hwnd:当前窗口消息关联的窗口句柄
  • uMsg:消息值
  • wParam: 额外的消息信息。 具体含义依赖于 uMsg 的值
  • lParam:额外的消息信息。 具体含义依赖于 uMsg 的值
RegisterClass

RegisterClass 用来注册一个窗口类型,以便在后续的 CreateWindow 或 CreateWindowEx 中使用.

ATOM RegisterClassA(
  const WNDCLASSA *lpWndClass
);

这里不详细介绍该函数的用法,参考 实例 章节。
值得注意的是,我们的窗口例程(WinProc 函数)便需要设置到 lpWndClass 对象上. 同时非常重要的是,这个类型上还需要包含我们需要注册的窗口类型的名称.

CreateWindowEx

CreateWindowEx 用来创建一个窗口对象.

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
);

在我们的程序中,我们仅仅需要一个简单的窗口对象,因此在我们的实例中绝大部分参数都是用默认值。 这里也不详细展开,用法参考 实例 章节。

WSAAsyncSelect

WSAAsyncSelect 用于将窗口和 SOCKET 对象绑定起来,可以指定关心的 SOCKET 事件.

int WSAAsyncSelect(
  SOCKET s,
  HWND   hWnd,
  u_int  wMsg,
  long   lEvent
);

wMsg 指定一个消息值,当对应的 SOCKET 上有 SOCKET 事件发生的时候,窗口例程会被调用,这个消息值会被传回来给我们。主要用于区别系统的窗口事件和我们自定义的事件.

GetMessage

GetMessage 从当前线程的消息队列中获取消息。

BOOL GetMessage(
  LPMSG lpMsg,
  HWND  hWnd,
  UINT  wMsgFilterMin,
  UINT  wMsgFilterMax
);
  • lpMsg: 是一个 MSG 结构体,用来接收消息信息
  • hWnd:指定想要获取窗口信息的窗口句柄
  • wMsgFilterMin:略
  • wMsgFilterMax:略
TranslateMessage

TranslateMessage 用于将 virtual-key message 转化为 character message. character mesage。 character message 可以再使用 DispatchMessage 将消息分发到窗口例程(WinProc 函数)。

BOOL TranslateMessage(
  const MSG *lpMsg
);
DispatchMessage

DispatchMessage 用于将窗口消息分发到窗口例程(WinProc 函数)。

LRESULT DispatchMessage(
  const MSG *lpMsg
);

实现思路

  1. 创建一个窗口对象,指定对应窗口的 WinProc 函数
  2. 创建 SOCKET 对象,作为监听的 SOCKET
  3. 使用 WSAAsyncSelect 函数将窗口与 SOCKET 关联起来. 同时指定SOCKET消息的消息值和 关心的 SOCKET 事件
  4. 调用 listen,开始接收客户端连接
  5. 使用 GetMessage 函数来从消息队列中获取可用的消息
  6. 获取到消息后,使用TranslateMessage 处理消息,然后调用 DispatchMessage 来分发 SOCKET 消息到我们步骤1中指定的窗口 WinProc 函数中。
  7. 循环 5-6 步骤
  8. 再 WinProc 函数中使用 WSAGETSELECTEVENT 来判断具体的 SOCKET 消息,并进行处理
  9. 如果有新的SOCKET连接到来,接收它,并再次使用 WSAAsyncSelect 将该客户端 SOCKET 与我们步骤1 中创建的窗口关联起来,并指定关心的 SOCKET 事件

实例

这里我们通过一个实例来看看如何实现:

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS

#include <winsock2.h>
#include <windows.h>
#include <stdio.h>
#include <conio.h>

#pragma comment(lib,"ws2_32.lib")

#define PORT 8080
#define DATA_BUFSIZE 8192

typedef struct _SOCKET_CONTEXT {
   
   BOOL   RecvPosted;
   CHAR   Buffer[DATA_BUFSIZE];
   WSABUF DataBuf;
   SOCKET Socket;
   DWORD  BytesSEND;
   DWORD  BytesRECV;
   struct _SOCKET_CONTEXT *Next;
} SOCKET_CONTEXT, *LPSOCKET_CONTEXT;

// 我们使用 WM_SOCKET 作为 SOCKET 消息的消息值, 这样在 WinProc 中我们可以通过检查
// 当前消息的消息值是否是 VM_SOCKET来决定是否处理该消息
#define WM_SOCKET (WM_USER + 1)

void             CreateSocketContext(SOCKET s);
LPSOCKET_CONTEXT GetSocketContext(SOCKET s);
void             FreeSocketContext(SOCKET s);
HWND             MakeWorkerWindow(void);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
LPSOCKET_CONTEXT SocketContexts;

int main()<
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值