深入 CSocket 编程之阻塞和非阻塞模式

本文详细介绍了CSocket编程中的阻塞和非阻塞模式,主要针对服务器端。阻塞模式下,服务器端在主线程中运行会阻塞,需要额外线程处理。非阻塞模式则利用socket事件的消息机制实现异步通信,适合处理多个客户端连接请求。文章还涉及网络传输服务提供者、socket事件和socket窗口等概念。
摘要由CSDN通过智能技术生成

阅读本文请先注意 :


• 这里的阻塞和非阻塞的概念仅适用于 server 端 socket 程序。


• socket 意为套接字,它与 socket 不同,请注意首字母的大小写。


说明:客户端与***的通信简单来讲:*** socket 负责监听,应答,接收和发送消息,而客户端 socket 只是连接,应答,接收,发送消息。此外,如果你对于采用 csocket 类编写 client/server 网络程序的原理不是很了解,请先查询一下( 详见:参考书籍和在线帮助 )。


在此之前,有必要先讲述一下: 网络传输服务提供者, ws2_32.dll , socket 事件 和 socket window 。


1. 网络传输服务提供者(网络传输服务进程), socket 事件, socket window


网络传输服务提供者 ( transport service provider )是以 dll 的形式存在的,在 windows 操作系统启动时由服务进程 svchost.exe 加载。当 socket 被创建时,调用 api 函数 socket (在 ws2_32.dll 中), socket 函数会传递三个参数 : 地址族,套接字类型 ( 注 2 ) 和协议,这三个参数决定了是由哪一个类型的 网络传输服务提供者 来启动网络传输服务功能。所有的网络通信正是由网络传输服务提供者完成 , 这里将 网络传输服务提供者 称为 网络传输服务进程 更有助于理解,因为前文已提到 网络传输服务提供者 是由 svchost.exe 服务进程所加载的。


下图描述了网络应用程序、 csocket ( wsock32.dll )、 socket api(ws2_32.dll) 和 网络传输服务进程 之间的接口层次关系:


hspace=0


当 client 端 socket 与 server 端 socket 相互通信时,两端均会触发 socket 事件 :


这里仅简要说明两个 socket 事件 :


fd_connect: 连接事件 , 通常 client 端 socket 调用 socket api 函数 connect 时所触发,这个事件发生在 client 端。


fd_accept :正在引入的连接事件,通常 server 端 socket 正在接收来自 client 端 socket 连接时触发,这个事件发生在 server 端。


网络传输服务进程 将 socket 事件 保存至 socket 的事件队列中。


此外, 网络传输服务进程 还会向 socket window 发送消息 wm_socket_notify , 通知有 socket 事件 产生,见下文对 socket window 的详细说明:


调用 csocket::create 函数后, socket 被创建。 socket 创建过程中调用 casyncsocket::attachhandle(socket hsocket, casyncsocket* psocket, bool bdead) 。该函数的作用是:


a. 将 socket 实例句柄和 socket 指针添加至 当前模块状态 ( 注 1 )的一个映射表变量 m_pmapsockethandle 中。


b. 在 attachhandle 过程中,会 new 一个 csocketwnd 实例 ( 基于 cwnd 派生 ) ,这里将这个实例称之为 socket window ,进一步理解为它是存放所有 sockets 的消息池 ( window 消息),请仔细查看,这里 socket 后多加了一个 s ,表示创建的多个 socket 将共享一个 消息池 。


c. 当 client 端 socket 与 server 端相互通信时 , 此时 网络传输服务进程 向 socket window 发送消息 wm_socket_notify ,需要说明的是 csocketwnd 窗口句柄保存在 当前模块状态 的 m_hsocketwindow 变量中。


 


2. 阻塞模式


阻塞模式下 server 端与 client 端之间的通信处于同步状态下。


在 server 端直接实例化 csocket 类,调用 create 方法创建 socket ,然后调用方法 listen 开始侦听,最后用一个 while 循环阻塞调用 accept 函数用于等待来自 client 端的连接,如果这个 socket 在主线程(主程序)中运行,这将导致主线程的阻塞。因此,需要创建一个新的线程以运行 socket 服务。


调试跟踪至 csocket::accept 函数源码: 

while(!accept(...)) 

if (getlasterror() == wsaewouldblock) // the socket is marked as nonblocking and no connections are present to be accepted. 
pumpmessage(fd_accept); 
else 
return false; 
}


它不断调用 casyncsocket::accept ( csocket 派生自 casyncsocket 类)判断 server 端 socket 的事件队列中是否存在正在引入的连接事件 - fd_accept (见 1 ),换句话说,就是判断是否有来自 client 端 socket 的连接请求。


如果当前 server 端 socket 的事件队列中存在正在引入的连接事件, accept 返回一个非 0 值。否则, accept 返回 0 ,此时调用 getlasterror 将返回错误代码 wsaewouldblock ,表示队列中无任何连接请求。


注意到在循环体内有一句代码:


pumpmessage(fd_accept);


pumpmessage 作为一个消息泵使得 socket window 中的消息能够维持在活动状态。


实际跟踪进入 pumpmessage 中,发现这个消息泵与 accept 函数的调用并不相关,它只是使很少的 socket window 消息(典型的是 wm_paint 窗口重绘消息)处于活动状态,而绝大部分的 socket window 消息被阻塞,被阻塞的消息中含有 wm_socket_notify 。


很显然,如果没有来自 client 端 socket 的连接请求, csocket 就会不断调用 accept 产生循环阻塞,直到有来自 client 端 socket 的连接请求而解除阻塞。


阻塞解除后,表示 server 端 socket 和 client 端 socket 已成功连接, server 端与 client 端彼此相互调用 send 和 receive 方法开始通信。


3. 非阻塞模式


在非阻塞模式下 利用 socket 事件 的消息机制, server 端与 client 端之间的通信处于异步状态下。


通常需要从 csocket 类派生一个新类,派生新类的目的是重载 socket 事件 的消息函数,然后在 socket 事件 的消息函数中添入合适的代码以完成 client 端与 server 端之间的通信,与阻塞模式相比,非阻塞模式无需创建一个新线程。


这里将讨论当 server 端 socket 事件 - fd_accept 被触发后,该事件的处理函数 _disibledevent="#ffa500">本文的结束:


server 端 socket 处于阻塞调用模式下,它必须在一个新创建的线程中工作,防止主线程被阻塞。


当有多个 client 端 socket 与 server 端 socket 连接及通信时, server 端采用阻塞模式就显得不适合了,应该采用非阻塞模式 , 利用 socket 事件 的消息机制来接受多个 client 端 socket 的连接请求并进行通信。


在非阻塞模式下,利用 csocketwnd 作为所有 sockets 的消息池,是实现 socket 事件 的消息机制的关键技术。 
 



文中存在用词不妥和可能存在的技术问题,请大家原谅,也请批评指正,谢谢! 
注:


1. 当前模块状态


用于保存当前线程和模块状态的一个结构,可以通过 afxgetthreadmodule() 获得。 afx_module_thread_state 在 csocket 重新定义为 _afx_sock_thread_state 。


2. socket 类型


在 tcp/ip 协议中, client/server 网络程序采用 tcp 协议:即 socket 类型为 sock_stream ,它是可靠的连接方式。在这里不采用 udp 协议:即 socket 类型为 sock_dgram ,它是不可靠的连接方式。


 


源代码参考:


1 . 采用 csocket 类编写的基于 client/server 的网络文件传输程序,它是基于阻塞模式的 client/server 端网络程序典型示例。


2 . 采用 csocket 类编写的基于 client/server 的网络聊天程序,它是基于非阻塞模式的 client/server 端网络程序典型示例。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值