这篇文章给出Windows网络编程的Socket相关知识。

WinSock的两种I/O模式 与 五种I/O模型。

从最基础的 接受应答 到 选择模型 – 重叠I/O – 完成端口 模型。

两种I/O模式分为 阻塞模式 和 非阻塞模式

阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。套接字默认为阻塞模式的情况下,可以通过多线程来处理。

非阻塞模式:执行I/O操作时,WinSock函数会返回并交出控制权。这种模式比较复杂,因为函数在没有运行完成就进行返回,并会不断地返回 WSAEWOULDBLOCK错误,但是它功能很强大。

为了解决上述非阻塞模式的问题,提出了进行I/O操作的一些I/O模型。

如果在Windows平台上搭建服务器应用,那么I/O模型是必须考虑的。

Windows操作系统提供了 选择(Select)、异步选择(WSAAsynSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O) 和 完成端口(Completion Port).

每一种 I/O模型均适用于一种特定的场景。程序员应对自己的应用需求非常明确,而且综合考虑到程序的扩展性 和 可移植性等因素,做出选择。

接下来我们会以一个回射服务器来介绍这五种I/O模型。

首先我们来看看:

零、最普通的回射服务器:

客户端代码如下: 看注释就好了。

我们所有的客户端都会使用以上代码。

接下来我们看一下最简单的服务端代码:

这两段代码都是阻塞模式的…存在的问题就是前面说到的:如果接受不到信息,就会一直阻塞在那里.

而且这里还有一个很大的问题,是Sam跟我说过的,本以为多线程就可以解决,没想到还是存在这种情况:

就是,阻塞的情况下如果接受不到信息,服务端关闭,就会导致客户端崩溃。

操作步骤如下:

  1. 将服务端的Send代码注释掉
  2. 打开服务端 – 打开客户端 – 连接上了
  3. 客户端发送一行数据
  4. 此时服务端接受到了,但是不会有信息返回
  5. 客户端就卡在这里一直等待数据
  6. 此时直接关闭服务端…
  7. 客户端崩溃了…

大致步骤如上。很危险的。

 

一、选择模型

Select模型是WinSock中最常见的I/O模型。之所以称其为Select模型,是由于它的“中心思想”便是利用Select函数,实现对I/O的管理。

最初设计该模型时,主要面向的是Unix操作系统的计算机,它们采用的是Berkeley套接字方案。Select模型已经集成到WinSock1.1中,它,采用一种有序的方式,同时对多个套接字的管理。

服务端代码如下:直接看注释就好了…

这里可能不好理解的是 fd_set 及其相关的操作。

该模型有个最大的缺点就是,它需要一个死循环不停的去遍历所有的客户端套接字集合,询问是否有数据到来,这样,如果连接的客户端很多,势必会影响处理客户端请求的效率,但它的优点就是解决了每一个客户端都去开辟新的线程与其通信的问题。

如果有一个模型,可以不用去轮询客户端套接字集合,而是等待系统通知,当有客户端数据到来时,系统自动的通知我们的程序,这就解决了select模型带来的问题了。

 

二、异步消息选择

WsaAsyncSelect模型就是这样一个解决了普通select模型问题的异步I/O模型。利用这个模型应用程序可以在一个套接字上接受以Windows消息为基础的网络事件通知。当有客户端数据到来时,系统发送消息给我们的程序,我们的程序只要定义好消息的处理方法就可以了,用到的函数主要是WSAAsyncSelect.

该模型被MFC中的CSocket对象所采纳。

主要实现思路如下:

  1. 首先我们定义一个消息,告诉系统当有客户端消息到达的时候,发送该消息通知我们。
  2. 然后在消息处理函数里面添加对消息的处理即可。

实现代码如下: – 老规矩,看代码。VS2013-Win32项目,应该是直接编译运行的。

先运行服务端,再运行客户端即可。

这个模型其实是比较简单的,基于Win32窗口程序:

  1. 在 WM_CREATE 消息处理函数中,初始化 Windows Socket library,创建监听套接字,绑定,监听,并且调用WSAAsyncSelect函数表示我们关心在监听套接字上发生的FD_ACCEPT事件;
  2. 自定义一个消息WM_SOCKET,一旦在我们所关心的套接字(监听套接字和客户端套接字)上发生了某个事件,系统就会调用 WndProc 并且 message 参数被设置为 WM_SOCKET ;
  3. 在 WM_SOCKET 的消息处理函数中,分别对 FD_ACCEPT、FD_READ 和FD_CLOSE 事件进行处理;
  4. 在窗口销毁消息(WM_DESTROY)的处理函数中,我们关闭监听套接字,清除Windows Socket library

这样就完美了。下面表列出了一些网络事件类型。

网络事件 含义
FD_READ 接受可读通知
FD_WRITE 接受可写通知
FD_OOB 接受是否外带数据(OOB)抵达通知
FD_ACCEPT 接受有连接通知
FD_CONNECT 接收与一次连接或者多点join操作完成的通知
FD_CLOSE 接收与套接字关闭有关的通知
FD_QOS 接收套接字“服务质量”(QoS)发生更改的通知
FD_GROUP_QOS 接收套接字组“服务质量”发生更改的通知
FD_ROUTING_INTERFACE_CHANGE 接收在指定的方向上,与路由接口发生变化的通知
FD_ADDRESS_LIST_CHANGE 接收针对套接字的协议家族,本地地址列表发生变化的通知

以上常用的一般就是 READ、WRITE、ACCEPT、CONNECT这四个了。

异步消息选择 解决了普通select模型的问题,但是它最大的缺点就是它只能用在windows程序上,因为它需要一个接收系统消息的窗口句柄,那么有没有一个模型既可以解决select模型的问题,又不限定只能是windows程序才能用呢?

 

三、WsaEventSelect模型

继续更新哈…

Winsock 提供了另一个有用的异步I/O模型 – WsaEventSelect 模型。和WSAAsyncSelect模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知。对于上面的表中总结的-由WSAAsyncSelect模型采用的网络事件来说,这些事件均可原封不动地移植到新模型。在用新模型开发的应用程序中,也能接收和处理所有那些事件。该模型最主要的差别在于网络事件会投递至一个事件对象句柄,而非投递至一个窗口例程。

事件选择模型不用主动去轮询所有客户端套接字是否有数据到来的模型,它也是在客户端有数据到来时,系统发送通知给我们的程序,但是,它不是发送消息,而是通过事件的方式来通知我们的程序,这就解决了WsaAsyncSelect模型只能用在Win32窗口程序的问题。

该模型的实现,我们也可以开辟两个线程来进行处理,一个用来接收客户端的连接请求,一个用来与客户端进行通信,用到的主要函数有WSAEventSelect,WSAWaitForMultipleEvents,WSAEnumNetworkEvents.

还是一样的,看代码吧…注释很清晰的。

当然这里要对几个函数进行讲解。

WSAEventSelelct 函数:将Socket套接字与事件对象关联,并制定需要关注的网络事件,一旦在某个套接字上发生了我们关注的事件(FD_READ 和 FD_CLOSE),与之相关联的 WSAEVENT 对象被触发。

三个参数 第一个参数指定套接字,第二个参数指定事件对象,第三个字段是前面的网络事件表中的类型。

WSAWaitForMultipleEvent函数:等待事件对象被触发。

五个参数:

  1. 第一个参数是事件对象的数量
  2. 第二个参数是事件对象句柄的数组
  3. 第三个参数指定是等待全部还是任意一个
  4. 第四个参数是等待时间
  5. 第五个参数是指定函数返回时是否执行完成例程

WSAResetEvent 函数就不讲了,重置事件。

WSAEnumNetworkEvents  – 判断是哪个网络事件触发了对象

三个参数:

  1. 套接字
  2. 事件对象
  3. 网络事件对象

大致就是这四个函数。

这里要注意的是,我并没有在各个函数调用后面进行成功与否的判断,这是比较危险的。当然为了代码的简洁性我也尽量去掉了这些东西,大家如果是真正的做服务器还是需要考虑到这一点的。

事件选择模型通过一个死循环里面调用 WSAWaitForMultipleEvents 函数来等待客户端套接字对应的事件的到来,一旦事件通知到达,就通过该套接字去接收数据。虽然WsaEventSelect模型的实现较前两种方法复杂,但它在效率和兼容性方面是最好的。

值得注意的是以上三种选择模型虽然在效率方面有了不少的提升,但它们都存在一个问题,就是都预设了只能接收64个客户端连接,虽然我们在实现时可以不受这个限制(数组,循环判断),但是那样,它们所带来的效率提升又将打折扣,那又有没有什么模型可以解决这个问题呢?

【WinSock】网络编程 – I/O模型(二) – 重叠I/O

【WinSock】网络编程 – I/O模型(一) – 选择模型
Tagged on:
0 0 投票数
Article Rating
订阅评论
提醒

0 评论
内联反馈
查看所有评论