IOCP 浅析与实例

转载:http://www.cppfans.org/1267.html

 

这一年半来一直在做游戏项目逻辑层,学会了不少东西,觉得自己应该看看服务器底层的东西了,主要的东西就是网络模块,网络模块是沿用以前项目的,在 我们项目中被我们头改动过几次,现在还是比较稳定的。因为是Windows平台,所以用的依然是被大多数人神话了的IOCP,不过的确IOCP 表现的非常不错。

什么是IOCP

众所周知,为了绝对同步,所以很多模式都采用的是同步模式,而不是异步,这样就会产生很大情况下在等待,CPU在切换时间片,从而导致效率比较低。自从MSwinsocket2中引入了IOCP这个模型之后,他才开始被大家所认知。

IOCP I/O Completion Port),中文译作IO完成端口,他是一个异步I/O操作的API,他可以高效的将I/O事件通知给我们的应用程序,那游戏项目来说,就是客户端或者服务器。

他与Socket基础API  select()或其他异步方法不同的是,他需要讲一个Socket和一个完成端口绑定在一起,然后就可以进行网路通信了。

什么是同步/异步?

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。

异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

逻辑上通俗来讲是完成一件事再去做另外一件事情就是同步,而一起做两件或者两件以上的事情就是异步了。类似于Win32API中的SendMessage()PostMessage(),你可以将他理解成单线程和多线程的区别。

拿游戏服务器与客户端通信来说:

如果是同步:

ClientA发送一条Msg1Req消息给Server,这个时候ClientA就会等待Server处理Msg1Req。这段时间内ClientA只有等待,因为Server还没有给ClientA回复Msg1Ack消息,所以ClientA只能痴痴的等,等到回复之后,才能处理第二条Msg2Req消息,这样无疑就会大大的降低性能,产生非常差的用户体验。

如果是异步:

ClientA发送一条Msg1Req消息给ServerClientA有发送第二条Msg2Req消息给ServerServer会将他们都存入队列,一条一条处理,处理完之后回复给ClientA,这样用户就可以不必等待,效率就会非常高。

什么是阻塞/非阻塞?

阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。可能阻塞和同步有点类似,但是同步调用的时候线程还是激活的,而阻塞时线程会被挂起。

非阻塞调用和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。

对象的阻塞模式和阻塞函数调用

对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。阻塞对象上可以有非阻塞的调用方 式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞。而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数 select就是这样的一个例子。

IOCP的评价如何?

I/O完成端口可能是Win32提供的最复杂的内核对象。 Jeffrey Richter

这是实现高容量网络服务器的最佳方法。Microsoft Corporation

完成端口模型提供了最好的伸缩性。这个模型非常适用来处理数百乃至上千个套接字。Anthony Jones & Jim Ohlund

I/O completion ports特别显得重要,因为它们是唯一适用于高负载服务器[必须同时维护许多连接线路]的一个技术。Completion ports利用一些线程,帮助平衡由I/O请求所引起的负载。这样的架构特别适合用在SMP系统中产生的scalable服务器。 Jim Beveridge & Robert Wiener

IOCP中的完成是指什么意思?

网络通信说白了就是将一堆数据发过来发过去,到底还是数据的操作。不过大家都知道I/O操作是非常慢的,包括打印机、调制解调器、硬盘等,至少相对于CPU来说是非常慢的。坐等I/O是很浪费时间的事情,可能你只需要读取100KB的数据,假设读了0.1秒,假设CPU3.0G Hz,那么CPU已经运行了0.3G次了,所以CPU这个时候就不满意了,哥这么NB,为什么要等你?

所以我们用另外一个线程来处理I/O操作,使用重叠IOOverlapped I/O)技术,应用程序可以要求OS为其传输数据,在完成的时候通知应用程序,然后在进行相应操作,这也就是为什么叫完成的原因。这可以使得应用程序在I/O传输期间可以做其他事情,这也可以最大限度的利用线程,而让最NBCPU不至于痴痴等待。

下来会将IOCP和网络有什么关系,以及IOCP的简单应用。

 

上一篇《IOCP浅析》中翻翻的谈了一下IOCP的简单含义,这篇稍微深入讨论下IOCP到底有什么好的,让大家将他推向神坛,同时简单的讨论下基本函数。

IOCP出现的意义?

写过网络程序的朋友应该很清楚网络程序的原型代码,startup一个WSADATA,然后建立一个监听socket对象,绑定一个服务器地址,然后开始监听,无限循环的accept来自客户端的消息,建立一个线程来处理消息,accept之后线程就被挂起了,知道收到来自客户端的消息。

这样的模型中服务器对每个客户端都会创建一个线程,优点在于等待请求的线程只做很少的事情,大部分时间该线程都在休息,因为recv函数是阻塞的。

所以这样的效率并不是很高,NT小组意识到这样CPU的大部分时间都耗费在线程的上下文切换上,线程并没有抢到cpu时间来处理自己的工作。

NT小组想到了一个解决办法,实现开好N个线程,将用户的消息都投递到一个消息队列中去,然后事先开好的N个线程逐一从消息队列中取出消息并加以处理,就可以避免为每一个客户端的请求单独开线程,既减少了线程的资源,也提高了线程的利用率。所以I/O完成端口的内核对象在NT3.5中首次被引入,MS还是比较伟大的。

这里你也看到了,IOCP其实称作是一种消息处理的机制差不多,而叫完成端口估计也是有历史原因,亦或者是因为他提供了用户与操作系统的一种接口吧。

ICOP的基本函数接口
创建完成端口

第一个参数是指一个已经打开的文件句柄或者空句柄值,一般为客户端的socket 注意:第一个参数HANDLE在创建时需要在CreateFile()中制定FILE_FLAG_OVERLAPPED标志。
第二个参数是指一个已经存在的IOCP句柄或者NULL
第三个参数是指完成Key,是一个unsigned long的指针,可以为NULL
第四个参数才是我们比较关心的,是指已经创建好的线程数,一般我们会用一个公式来计算,预设的线程数 = CPU核心数 * 2 + 2,有人也说是 + 1,我是没明白为什么要这样计算,希望大神指教。

对于第三个参数的意思,MSDN上解释如下 Use the CompletionKey parameter to help your application track which I/O operations have completed.(用来检测那些IO操作已经完成)

该函数用于两个不同的目的

1.创建一个完成端口的句柄对象
HANDLE h = CreateIoCompletionPort((HANDLE) socket, hCompletionPort, dwCompletionKey, m_nIOWorkers);

2.将一个句柄和完成端口关联在一起

在绑定每一个CLIENT到IOCP时,需要传递一个DWORD CompletionKey, 该参数为CLIENT信息的一个指针。

IO的异步调用

第一个参数为创建的完成端口句柄
第二个参数传输了多少字节
第三个参数同样为完成键指针
第四个参数为重叠I/O buffer,其结构如下

线程的同步

第一个参数为创建的完成端口句柄
第二个参数同样为传输了多少字节
第三个参数同样为完成键指针
第四个参数为重叠I/O buffer
第五个参数的解释如下
The number of milliseconds that the caller is willing to wait for a completion packet to appear at the completion port. If a completion packet does not appear within the specified time, the function times out, returnsFALSE, and sets *lpOverlapped to NULL.(等待完成端口上的完成packet出现的毫秒数。如果一个完成在特殊时间内没有出现,则认为超时,返回false,同时将重叠buffer置为NULL)

大体上函数就有这么几个,大家有兴趣可以去看看MSDN上关于完成端口上的英文介绍,比较准确,中文翻译上难免出现歧义,同时还可以锻炼英文阅读。

好了,I/O完成端口的API就介绍到这里,下一篇会尝试着写出一个简单的完成端口模型来通讯,这篇可能会比较晚出来,因为自己也是摸索阶段,大家互勉。


本文是我在学习IOCP的时候,第一次写一个完整的例子出来,当然了,参考了CSDN上一些朋友的博客,大部分都是按照他们的思路写的,毕竟我是初学者,参考现成的学起来比较快。当然了,真正用到项目中的IOCP肯定不止这么简单的,还有内存池,环形缓冲区,socket连接池等高端内容,后面我会参考一些例子,写出一个完整的给大家看。

前面讲过IOCP里面一个很重要的东西就是IO重叠了,所以结构体里有一个OVERLAPPED结构。

整个过程还是类似于最基础的socket连接方式,主要部分就是使用IOCP的两个函数,创建IOCP和检测当前的状态。

大家先凑活看吧,后面本博客会有更精彩的IOCP内容呈现给大家,我也是逐步在学习,大家稍安勿躁。


 

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值