[TOC]
1. 网络编程常见的模型
1.1. Linux平台
1.1.1. 阻塞I/O
1.1.2. 非阻塞I/O
1.1.3. I/O多路复用
select模型
poll模型
epoll模型
select/poll/epoll比较
1.1.4. 信号驱动I/O(SIGIO)
1.1.5. 异步I/O
1.2. Windows平台
1.2.1. select模型
含义
对多个socket 进行管理 调用select()可以获取指定socket状态,即select 选择获得有响应的指定的socket
目的
解决基本C/S模型中,accept()、recv()、send()阻塞的问题
select模型与C/S模型的不同点
C/S模型中accept()会阻塞一直等待socket来连接 select模型只解决accept()傻等的问题,不解决recv(),send()执行阻塞问题
在应用程序使用select函数时会发生阻塞现象。可以通过select的timeout参数设置阻塞的时间。在设置的时间内,select函数等待,直到一个或多个套接字满足可读或可写的条件。
用法
服务端 1.一般创建非阻塞步骤(初始化,创建,绑定ip和端口,监听,ioctlsocket设置非阻塞) 2.装填socket数组 FD_SET(socketServer, &allSockets); 3.调用select()等待,返回有响应的socket,对有响应的socket 做相应处理(accept,send,recv)
1.2.2. WSAAsyncSelect模型
1.2.3. 含义
应用程序可以在一个socket上接收以windows消息为基础的网络事件通知,它实现了读写数据的异步通知功能,但不提供异步的数据传输。
目的
解决select模型 调用select()会阻塞的问题。
与select模型的区别
WSAAsyncSelect是非阻塞的。Windows sockets程序在调用recv或send之前,调用WSAAsyncSelect注册网络事件。WSAAsyncSelect函数立即返回。当系统中数据准备好时,会向应用程序发送消息。此此消息的处理函数中可以调用recv或send进行接收或发送数据。
用法
1.初始化socket环境,并创建win32自定义事件的socket事件 WM_SOCKET 2.调用WSAAsyncSelect()绑定事件到窗口消息队列 3.通过GetMessage()轮询消息,当消息事件发生时,实现对socket的处理(包括accept,send,recv)
1.2.4. WSAEventSelect模型
含义
允许在多个Socket上接收以事件为基础的网络事件通知,应用程序在创建Socket后,调用WSAEventSelect()函数将事件对象与网络事件集合相关联。当网络事件发生时,应用程序以事件的形式接收网络事件通知。
目的
与WSAAsyncSelect、select模型的区别
Select:程序员主动调用select函数,获取指定socket状态, WSAEventSelect,WSAAsyncSelect:被动地选择系统通知应用程序socket状态变化。
WSAEventSelect,WSAAsyncSelect不同之处:在于socket事件的通知方法,WSAAsyncSelec模型利用窗口句柄和消息映射函数通知网络事件,而WSAEventSelect模型利用WSAEVENT通知网络事件。
用法
1.2.5. 重叠I/O模型
含义
重叠模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。针对这些提交的请求,在它们完成之后,应用程序会收到通知,于是就可以通过自己另外的代码来处理这些数据了。 重叠i/o是真正意义上的异步io模型 调用输入/输出函数后 立即返回(WSARecv,WSASend)。
目的
区别
1.可以运行在支持Winsock2的所有Windows平台 ,而不像完成端口只是支持NT系统。
2.比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。 因为它和这4种模型不同的是,使用重叠模型的应用程序通知缓冲区收发系统直接使用数据,也就是说,如果应用程序投递了一个10KB大小的缓冲区来接收数据,且数据已经到达套接字,则该数据将直接被拷贝到投递的缓冲区。不需要等待调用recv时才拷贝 而这4种模型种,数据到达并拷贝到单套接字接收缓冲区中,此时应用程序会被告知可以读入的容量。当应用程序调用接收函数之后,数据才从单套接字缓冲区拷贝到应用程序的缓冲区,差别就体现出来了。
1.2.6. 完成端口模型
含义
利用线程池处理异步I/O请求,利用完成端口模型可以管理成百上千Socket。
可以把完成端口看成系统维护的一个队列,操作系统把重叠I/O操作完成的事件通知放到该队列中,因此称其为“完成”端口,当Socket被创建后,可以将其与一个完成端口联系起来。
一个应用程序可以创建多个工作线程用于处理完成端口上的通知事件,通常应该为每个CPU创建一个线程。
一个完成端口实际就是一个通知队列,操作系统把已经完成的重叠I/O请求的通知放到队列中,当某项IO操作完成后,系统会向服务端完成端口发送一个i/o完成数据包,此操作在系统内部完成,应用程序在收到I/o完成数据包后,完成端口队列的一个线程被唤醒,为客户端提供服务,服务完成后,该线程会继续在完成端口上等待.
目的
(1) 首先,如果使用“同步”的方式来通信的话,这里说的同步的方式就是说所有的操作都在一个线程内顺序执行完成,这么做缺点是很明显的:因为同步的通信操作会阻塞住来自同一个线程的任何其他操作,只有这个操作完成了之后,后续的操作才可以完成;一个最明显的例子就是咱们在MFC的界面代码中,直接使用阻塞Socket调用的代码,整个界面都会因此而阻塞住没有响应!所以我们不得不为每一个通信的Socket都要建立一个线程,多麻烦?这不坑爹呢么?所以要写高性能的服务器程序,要求通信一定要是异步的。
(2) 各位读者肯定知道,可以使用使用“同步通信(阻塞通信)+多线程”的方式来改善(1)的情况,那么好,想一下,我们好不容易实现了让服务器端在每一个客户端连入之后,都要启动一个新的Thread和客户端进行通信,有多少个客户端,就需要启动多少个线程,对吧;但是由于这些线程都是处于运行状态,所以系统不得不在所有可运行的线程之间进行上下文的切换,我们自己是没啥感觉,但是CPU却痛苦不堪了,因为线程切换是相当浪费CPU时间的,如果客户端的连入线程过多,这就会弄得CPU都忙着去切换线程了,根本没有多少时间去执行线程体了,所以效率是非常低下的,承认坑爹了不?
(3) 而微软提出完成端口模型的初衷,就是为了解决这种"one-thread-per-client"的缺点的,它充分利用内核对象的调度,只使用少量的几个线程来处理和客户端的所有通信,消除了无谓的线程上下文切换,最大限度的提高了网络通信的性能,