通用性IOCP网络模型

来源:互联网 发布:网络教育要交几年学费 编辑:程序博客网 时间:2024/05/16 16:08

同步,异步,阻塞,非阻塞之间的关系。

所谓同步和异步,就是应用程序等待api与否,应用程序等待api,就是同步,不等待,就是异步。

假设一个api需要10秒钟执行完成,应用程序调用api,操作系统切换到内核态,同时挂起应用程序,直到操作系统执行完毕,把结果返回给应用程序,就是同步调用。

而应用程序把api请求投递给操作系统,操作系统收到请求后执行api,此时操作系统来决定要不要挂起这个应用程序(如果挂起就是阻塞,不挂起就是非阻塞),如果操作系统不挂起这个程序,程序就可以去执行别的东西,直到某一刻收到操作系统的通知或者主动查询结果,这就是异步调用。

而阻塞和非阻塞主要用于io操作,上面已经提到过,操作系统api执行的时候,需要挂起应用程序,直到io完成后恢复应用程序,这就是阻塞。如果操作系统不挂起应用程序,而是按照常规的cpu时间片分配运行进程,直到完成io操作为止(完成时可能通知调用程序,也可能不通知),就是非阻塞。


基本io模型:

1. block/多线程block

同步发送/接收,会在没有数据的时候阻塞住,需要线程太多,并发性不高。

2. select 

选择模型,bsd套接字模型,关键在于使用select "偷窥" 指定的fd_set里面有没有数据,可不可写(用的较少),有没有错误,再用fd_isset判断后执行发送/接收操作。缺点在于空循环会浪费时间,而且可能产生很多wouldblock错误。

3. WSAAsyncSelect

通过对指定socket注册感兴趣的事件,并制定接收窗口来处理消息,只在窗口应用程序中有用。

4. WSAEventSelect (对应于unix的信号驱动模型)

通过对指定socket注册感兴趣的事件给某个WSAEVENT,当事件触发的时候会触发这个event,可以用WSAWaitForMultipleEvents获取消息事件。

相比select省略了空循环t的时间,WSAWaitForMultipleEvents是同步函数。

5. overlapped(对应于unix的异步模型)

把io处理和结果分开来,异步非阻塞的投递io请求,之后通过回调函数处理返回结果。

相比于前一种模型overlapped模型不用注册wsaevent,而是在调用api的时候指定overlapped结构的指针,再通过WSAWaitForMultipleEvents得到事件结果。

6. IOCP

iocp模型在overlapped之上增加了完成端口概念,用于处理大量数据缓存。完成端口是一个系统内核,通过createiocompletionport创建得到句柄,之后通过句柄来访问,关闭时调用closehandler就可以了。

在使用时,通过getqueuedcompletionstatus得到调用的结果,再对结果进行处理。



简单的完成端口使用实例。

需要的资源:

1. 一个监听线程

2. 若干个完成端口,可以配合若干线程使用。这些线程不断的获取io操作的结果,并加以处理。

3. 维护tcp/udp连接的类conn,用来保存接受/发送的数据缓存,socket,以及oop回调等。

4. 一个定时线程,用来处理一些延时请求,比如重连延迟等。


初始化:

1.  wsastartup

2. 服务启动的时候调用Init函数,指定服务的类型(server,client,混合等)

3.  创建n个完成端口,绑定在线程中用来处理iocp请求回调。

4.  由于udp监听是没有连接可供accept的,需要的话可以让其中的一些线程指定udp处理。


socket属性:

tcp: AF_INET, SOCK_STREAM, ..... WSA_FLG_OVERLAPPED

setsocketopt  —— (SOL_SOCKET)SO_KEEP_ALIVE, SO_SNDBUF, SO_RECVBUF, SO_REUSEADDR, (IPPROTO_TCP) TCP_NODELAY

udp: AF_INET, SOCK_DGRAM

setsocketopt  —— (SOL_SOCKET) SO_REUSEADDR

注:由于overlapped自动把socket设置为非阻塞,这里就不用调用ioctlsocket(, FIONBIO,) 了。


工作线程:

1. 如果有udp监听需要,创建一个socket,用createiocompletionport绑定到iocp中,并在工作者线程中投递wsarecv/wsarecvfrom请求

2. 处理其他连接创建后(如accept/connect创建的socket)投递请求

3. 通过循环getqueuedconpletionstatus函数得到io处理结果,可以得到传输的数据,socket号,overlapped结构体。可以用socket号得到确切的io操作者,也可以通过overlapped结构体指针使用containing_record函数获得包含这个指针的类。(overlapped结构体就是调用wsasend/recv时传递的那个),这么做的好处是不用在查询socket归属的类容器时上锁(因为有新连接加入时的插入可能会造成冲突)

4. 根据overlapped结构体的oerpation参数得到该返回值的类型,处理数据接收或者标志数据发送完成。


创建监听:

1 . 通过ip/port值创建sockaddr_in结构体,创建一个sck,并绑定sck和sockaddr_in,使用listen创建监听。

2. 创建一个新线程,这个线程用空循环不断的wsaacept

3 当有新连接接入时,创建一个conn,绑定相关的回调资源,绑定给某个完成端口(处于负载均匀的考虑,可以查询所有线程的负载情况决定,也可以简单的用sck值取模决定)

4. 在工作线程中投递接受请求


连接远程sck:

1. 创建一个conn类,保存相关数据和回调

2. 通过ip/port值创建sockaddr_in结构体,创建sck,通过connect连接。若成功,tcp socket会自动被操作系统绑定到一个系统空闲端口。

3. 如果连接成功,把这个sck绑定给完成端口和相关线程4

4. 在工作线程中投递接受请求


发送数据:

1. 在调用者线程中通过send/sendudp发送。

2. 如果调用者很多,可以通过fiber或者新线程来管理这些发送。

3, 发送最终会通过getqueuedcompletionstatus返回,在工作者线程中可以得到结果。

注:iocp的tcp发送时一个阻塞过程,除非inv - inv/ack - ack这个过程完成,否则getqueuedcompletionstatus不会返回。这就意味着如果某些客户端不返回ack,iocp的发送请求会越来越多,导致系统资源被耗尽(当然系统也有一些处理这种情况的措施,比如增加请求的数量判断等),所以在发送前可以指定一些请求的数量限制,发送时数量+1,得到结果后数量-1,当达到上限就强制关闭socket


接收数据:

1. 在绑定iocp后就可以调用wsarecv投递请求了。

2. 接收请求最终会通过getqueuedcompletionstatus返回,如果返回字节数为0说明客户端主动关闭连接(当然某些iocp_recvzero的操作除外)。

3. 由于我们调用recv的时候把数据缓冲区指定了,所以得到回复之后直接访问缓冲区里面的数据就可以了,这时候保存缓冲区的conn可以通过回调通知应用程序。

4. 如果缓冲区不够写,或者其他出错情况


关闭sck:

1. 在工作线程里面加入该conn的关闭函数

2. 关闭时先调用shutdown关闭数据收发

3. 处理剩余数据,掉用关闭回调,closesocket


关闭服务(线程):

通过postqueuedconpltionsttus给iocp句柄发送消息,然后通过waitforsingleobject终止线程


重连的情况:

在timequeue里面插入sck操作,定时调用


相关资料:

http://blog.csdn.net/historyasamirror/article/details/4270633 (同步异步等概念)

http://blog.csdn.net/phunxm/article/details/5085869 (winsock基础)

http://blog.csdn.net/phunxm/article/details/5085898 (winsock模型)

http://www.cppblog.com/Lee7/archive/2008/01/07/40650.html (overlapped和iocp的关联)

http://www.cnblogs.com/cylee025/archive/2011/10/22/2221476.html