这里的阻塞和非阻塞的概念仅适用于 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) 和 网络传输服务进程 之间的接口层次关系:
当 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) 。该函数的作用是:
* 将 socket 实例句柄和 socket 指针添加至 当前模块状态 ( 注 1 )的一个映射表变量 m_pmapSocketHandle 中。
* 在 AttachHandle 过程中,会 new 一个 CSocketWnd 实例 ( 基于 CWnd 派生 ) ,这里将这个实例称之为 socket window ,进一步理解为它是存放所有 sockets 的消息池 ( window 消息),请仔细查看,这里 socket 后多加了一个 s ,表示创建的多个 socket 将共享一个 消息池 。
* 当 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(...))
{
// The socket is marked as nonblocking and no connections are present to be accepted.
if (GetLastError() == WSAEWOULDBLOCK)
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 被触发后,该事件的处理函数 OnAccept 是如何进一步被触发的。其它事件的处理函数如 OnConnect, OnReceive 等的触发方式与此类似。
在 1 中已提到 Client/Server 端通信时, Server 端 socket 正在接收来自 Client 端 socket 连接请求,这将会触发 FD_ACCEPT 事件,同时 Server 端的 网络传输服务进程 向 Server 端的 socket window (CSocketWnd )发送事件通知消息 WM_SOCKET_NOTIFY , 通知有 FD_ACCEPT 事件产生 , CsocketWnd 在收到事件通知消息后,调用消息处理函数 OnSocketNotify:
LRESULT CSocketWnd::OnSocketNotify(WPARAM wParam, LPARAM lParam)
{
CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY, wParam, lParam);
CSocket::ProcessAuxQueue();
return 0L ;
}
消息参数 wParam 是 socket 的句柄, lParam 是 socket 事件 。这里稍作解释一下,CSocketWnd 类是作为 CSocket 类的 友元类 ,这意味着它可以访问 CSocket 类中的保护和私有成员函数和变量, AuxQueueAdd 和 ProcessAuxQueue 是 CSocket 类的静态成员函数,如果你对友元不熟悉,请迅速找本有关 C++ 书看一下友元的使用方法吧!
ProcessAuxQueue 是实质处理 socket 事件的函数,在该函数中有这样一句代码:
CAsyncSocket* pSocket = CAsyncSocket::LookupHandle((SOCKET)wParam, TRUE);
其实也就是由 socket 句柄得到发送事件通知消息的 socket 指针 pSocket:从 m_pmapSocketHandle 中查找(见 1 )!
最后, WSAGETSELECTEVENT(lParam) 会取出事件类型,在一个简单的 switch 语句中判断事件类型并调用事件处理函数。在这里,事件类型是 FD_ACCEPT ,当然就调用 pSocket->OnAccept !
结束语
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. http://www.codeproject.com/internet/SocketFileTransfer.asp 采用 CSocket 类编写的基于 Client/Server 的网络文件传输程序,它是基于阻塞模式的 Client/Server 端网络程序典型示例。
2. http://www.codeguru.com/Cpp/I-N/network/messaging/article.php/c5453 采用 CSocket 类编写的基于 Client/Server 的网络聊天程序,它是基于非阻塞模式的 Client/Server 端网络程序典型示例。
参考资料:
* Microsoft MSDN Library – January 2001
* 《Windows 网络编程》 清华大学出版社
分享到:
相关推荐
#### 三、深入CSocket编程之阻塞和非阻塞模式 **3.1 阻塞模式** 在阻塞模式下,当执行如`recv`或`send`等I/O操作时,如果数据未准备好,则调用会一直等待直到数据准备好或者超时。这会导致线程挂起,无法处理其他...
8. 深入 CSocket 编程之阻塞和非阻塞模式 CSocket类提供了阻塞和非阻塞两种模式,阻塞模式下,CSocket类将等待数据的到达,而非阻塞模式下,CSocket类将立即返回。了解CSocket类的阻塞和非阻塞模式,可以帮助开发者...
在IT行业中,网络通信是至关重要的一个领域,而基于CSocket编程的多人聊天程序就是这一领域的典型应用。本文将深入探讨如何使用CSocket类(Windows Socket API 的面向对象封装)来构建一个支持多人同时在线聊天的...
总结来说,"ChatRoom(Winsock).rar"项目提供了一个使用Winsock进行非阻塞I/O编程的实例,它涵盖了网络编程中的重要概念和技术,如非阻塞模式、I/O多路复用和事件驱动编程。对于想要提升网络编程技能的开发者来说,这...
本文将深入探讨如何使用MFC实现非阻塞Socket通信,并结合protobuf(Protocol Buffers)作为数据交换格式,构建一个允许多个客户端与单一服务器进行通信的系统。 首先,我们来理解“非阻塞Socket”。在传统的阻塞...
然后,可以创建CSocket对象并调用Create()函数初始化,指定使用的服务模型(如阻塞或非阻塞)以及套接字类型(如SOCK_STREAM对应TCP)。 4. **绑定与监听** 使用Bind()函数将套接字与本地IP地址和端口号关联,之后...
- **接收函数 `Receive`**:类似地,`Receive` 函数也采用异步非阻塞模式,如果接收数据过程中出现错误或数据未完全接收完成,剩余的数据会被放入内部接收缓冲区,并在适当时候自动调用 `OnReceive` 方法完成接收。...
CAsyncSocket类是对WinSock API的直接映射,它提供了异步非阻塞的通信方式。这意味着当你调用如Receive()、Send()等方法时,如果数据尚未准备好,这些方法不会阻塞,而是立即返回,此时可以通过GetLastError()检查...
这些函数可以阻塞当前线程直到数据完全传输完毕,也可以设置成非阻塞模式。 - **监听与接受连接**:服务器端需要通过`listen()`函数将套接字设置为监听状态,然后通过`accept()`函数接受客户端的连接请求。 #### 2....
### IO模型中的阻塞模型和非阻塞模型 在计算机科学领域中,输入/输出(Input/Output,简称IO)模型对于操作系统与应用程序之间的交互至关重要。根据数据读写操作是否可以立即返回,IO模型通常被划分为阻塞式...
3. **非阻塞I/O**:非阻塞I/O模式允许服务器在等待数据时不会被挂起,而是立即返回并继续处理其他任务。这对于多用户聊天室来说至关重要,因为服务器需要同时处理多个客户端的连接和数据传输。 【聊天室实现的关键...
本文将深入探讨由标题“CSocket CAsyncSocket demo集合”所提及的两种Windows平台下的Socket编程类库——CSocket和CAsyncSocket,并通过描述中提到的“可以利用这两个demo重新改写符合业务需求”来讲解如何利用它们...
`recv`函数提供了更多的控制和灵活性,比如它可以接受一个标志参数,用于指定接收行为,如阻塞模式、非阻塞模式,或者在接收特定数量的数据后立即返回。 在使用`recv`函数时,可以更精确地控制接收缓冲区大小,确保...
相比之下,同步阻塞模式的效率明显低于异步非阻塞模式。但是,异步模式效率高,但更麻烦,你一边要记录起跑同学的数据,一边要记录到达同学的数据,而且同学们回到终点的次序与起跑的次序并不相同,所以你还要不停地...
在Csocket中,可以使用`accept()`函数的非阻塞模式,或者使用I/O复用技术(如select、poll、epoll等)来处理多个连接。 此外,为了提高用户体验,聊天室通常还会包含一些附加功能,如用户名注册、私聊、表情支持等...
这种非阻塞I/O方式适用于多线程或需要高效并发处理的复杂应用。 在实际使用中,`CSocket` 更适合于那些需要保持简单逻辑和流程的应用,如客户端向服务器发送请求并等待响应的场景。而`CAsyncSocket` 则更适合于需要...
- **非阻塞模式**:与`CSocket`类不同,`CAsyncSocket`类支持非阻塞模式,这意味着像`Receive()`和`Send()`这样的函数可以在发出后立即返回,而不会等待操作完成。这种方式更适合于对实时性要求较高的应用场景。 - *...
- **非阻塞模式**:在非阻塞模式下,即使当前没有数据可读,函数也会立即返回,而不等待操作完成。这种方式提高了程序的响应性和效率,但需要程序员自行处理数据的完整性和同步问题。 **2.3 CSocket类的关键方法** ...