当前位置:编程学习 > 网站相关 >>

非阻塞模式WinSock编程入门

介绍

WinSock是Windows提供的包含了一系列网络编程接口的套接字程序库。在这篇文章中,我们将介绍如何把它的非阻塞模式引入到应用程序中。文章中所讨论的通信均为面向连接的通信(TCP),为清晰起见,文章对代码中的一些细枝末节进行了删减,大家可以依照文末的链接下载完整的工程源码来获取这部分内容。

 

阻塞模式WinSock

         下述伪代码给出了阻塞模式下WinSock的使用方式。

view plaincopy to clipboardprint?
//---------------------------------------  
// 服务器  
//----------------------------------------  
// WinSock初始化  
WSAStartup();  
 
// 创建服务器套接字  
SOCKET server = socket();  
 
// 绑定到本机端口  
bind(server);   
 
// 开始监听  
listen(server);   
 
// 接收到客户端连接,分配一个客户端套接字  
SOCKET client = accept(server);   
 
// 使用新分配的客户端套接字进行消息收发  
send(client);   
recv(client);  
 
// 关闭客户端套接字  
closesocket(client);   
 
// 关闭服务器套接字  
closesocket(server);  
 
// 卸载WinSock  
WSACleanup(); 
//---------------------------------------
// 服务器
//----------------------------------------
// WinSock初始化
WSAStartup();

// 创建服务器套接字
SOCKET server = socket();

// 绑定到本机端口
bind(server);

// 开始监听
listen(server);

// 接收到客户端连接,分配一个客户端套接字
SOCKET client = accept(server);

// 使用新分配的客户端套接字进行消息收发
send(client);
recv(client);

// 关闭客户端套接字
closesocket(client);

// 关闭服务器套接字
closesocket(server);

// 卸载WinSock
WSACleanup(); 

view plaincopy to clipboardprint?
//---------------------------------------  
// 客户端  
//---------------------------------------  
WSAStartup();  
 
// 创建客户端套接字  
SOCKET client = socket();  
 
// 绑定本机端口  
bind(client);  
 
// 连接到服务器  
ServerAddress server;  
connect(client, server);  
 
// 确立连接后收发消息  
recv(client);  
send(client);  
 
// 关闭客户端套接字  
closesocket(client);  
 
WSACleanup(); 
//---------------------------------------
// 客户端
//---------------------------------------
WSAStartup();

// 创建客户端套接字
SOCKET client = socket();

// 绑定本机端口
bind(client);

// 连接到服务器
ServerAddress server;
connect(client, server);

// 确立连接后收发消息
recv(client);
send(client);

// 关闭客户端套接字
closesocket(client);

WSACleanup(); 

         代码中,服务器端的accept(),客户端的connect(),以及服务器和客户端易做图同的recv()、send()函数均会产生阻塞。

服务器在调用accept()后不会返回,直到接收到客户端的连接请求;

客户端在调用connect()后不会返回,直到对服务器连接成功或者失败;

服务器和客户端在调用recv()后不会返回,直到接收到并读取完一条消息;

服务器和客户端在调用send()后不会返回,直到发送完待发送的消息。

如果这两段代码被放在Windows程序的主线程中,你会发现消息循环被阻塞,程序不再响应用户输入及重绘请求。为了解决这个问题,你可能会想到开辟另外一个线程来运行这些代码。这是可行的,但是考虑到每个SOCKET都不应该被其他SOCKET的操作所阻塞,是不是需要为每个SOCKET开辟一个线程?再考虑到同一SOCKET的一个读写操作也不应该被另外一个读写操作所阻塞,是不是应该再为每个SOCKET的读和写分别开辟一个线程?一般来说,这种自实现的多线程解决方案带来的诸多线程管理方面的问题,是你绝对不会想要遇到的。

 

非阻塞模式WinSock

         所幸的是,WinSock同时提供了非阻塞模式,并提出了几种I/O模型。最常见的I/O模型有select模型、WSAAsyncSelect模型及WSAEventSelect模型,下面选择其中的WSAAsyncSelect模型进行介绍。

         使用WSAAsyncSelect模型将非阻塞模式引入到应用程序中的过程看起来很简单,事实上你只需要多添加一个函数就够了。

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

该函数会自动将套接字设置为非阻塞模式,并且把发生在该套接字上且是你所感兴趣的事件,以Windows消息的形式发送到指定的窗口,你需要做的就是在传统的消息处理函数中处理这些事件。参数hWnd表示指定接受消息的窗口句柄;参数wMsg表示消息码值(这意味着你需要自定义一个Windows消息码);参数IEvent表示你希望接受的网络事件的集合,它可以是如下值的任意组合:

FD_READ, FD_WRITE, FD_OOB, FD_ACCEPT, FD_CONNECT, FD_CLOSE

         之后,就可以在我们熟知的Windows消息处理函数中处理这些事件。如果在某一套接字s上发生了一个已命名的网络事件,应用程序窗口hWnd会接收到消息wMsg。参数wParam即为该事件相关的套接字s;参数lParam的低字段指明了发生的网络事件,lParam的高字段则含有一个错误码,事件和错误码可以通过下面的宏从lParam中取出:

#define WSAGETSELECTEVENT(lParam) LOWORD(lParam)

#define WSAGETSELECTERROR(lParam) HIWORD(lParam)

 

下面继续使用伪代码来帮助阐述如何将上一节的阻塞模式WinSock应用升级到非阻塞模式。

首先自定义一个Windows消息码,用于标识我们的网络消息。

view plaincopy to clipboardprint?
#define WM_CUSTOM_NETWORK_MSG (WM_USER + 100) 
#define WM_CUSTOM_NETWORK_MSG (WM_USER + 100)

 

服务器端,在监听之前,将监听套接字置为非阻塞模式,并且标明其感兴趣的事件为FD_ACCEPT。

view plaincopy to clipboardprint?
…  
WSAAsyncSelect(server, wnd, WM_CUSTOM_NETWORK_MSG, FD_ACCEPT);  
 
// 开始监听  
listen(server); 

WSAAsyncSelect(server, wnd, WM_CUSTOM_NETWORK_MSG, FD_ACCEPT);

// 开始监听
listen(server); 

客户端,在连接之前,将套接字置为非阻塞模式,并标明其感兴趣的事件为FD_CONNECT。

view plaincopy to clipboardprint?
…  
WSAAsyncSelect(client, wnd, WM_CUSTOM_NETWORK_MSG, FD_CONNECT);  
 
// 连接到服务器  
ServerAddress server;  
connect(client, server); 

WSAAsyncSelect(client, wnd, WM_CUSTOM_NETWORK_MSG, FD_CONNECT);

// 连接到服务器
ServerAddress server;
connect(client, server); 

接着,在Windows消息处理函数中,我们将处理监听事件、连接事件、及读写事件,方便起见,这里将服务器和客户端的处理代码放在了一起。

view plaincopy to clipboardprint?
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
{  
    switch (message)  
    {  
    …  
    case WM_CUSTOM_NETWORK_MSG: // 自定义的网络消息码  
        {  
            SOCKET socket = (SOCKET)wParam; // 发生网络事件的套接字  
            long event = WSAGETSELECTEVENT(lParam); // 事件  
            int error = WSAGETSELECTERROR(lParam); // 错误码  
 
            switch (event)  
            {  
            case FD_ACC

补充:综合编程 , 其他综合 ,
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,