看到一个很有意思的解释:
老陈有一个在外地事情的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。
这和Socket模型非常类似。下面我就以老陈接收信件为例讲解SocketI/O模型。
select模型:
老陈非常想看到女儿的信。以至于他每隔10分钟就下楼查抄信箱,看是否有女儿的信,在这种情况下,“下楼查抄信箱”然后回到楼上迟误了老陈太多的时间,以至于老陈无法做其他事情。
select模型和老陈的这种情况非常相似:周而复始地去查抄......如果有数据......接收/发送......
WSAAsyncSelect模型:
后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!今后,老陈再也不必频繁上下楼查抄信箱了,牙也不疼了,你瞅准了,蓝天......不是,微软......
微软供给的WSAAsyncSelect模型就是这个意思。
WSAAsyncSelect模型是Windows下最简单易用的一种Socket I/O模型。使用这种模型时,Windows会把网络事件以动静的形势通知应用措施。
WSAEventSelect模型:
后来,微软的信箱非常脱销,采办微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝蚁力神都欠好使。微软改造 了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件到临,此装置会发出“新信件达到”声,提醒老陈去收信。盖茨终于可以睡觉了。
Overlapped I/O 事件通知模型:
后来,微软通过查询拜访发明,,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改造他们的信箱。新式的信箱给与了更为先进的技术,只要用户报告微软本身的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后报告用户,你的信件已经放到你的家中了!老陈很开心,因为他不必再亲自收发信件了!
Overlapped I/O 事件通知模型和WSAEventSelect模型在实现上非常相似,主要区别在“Overlapped”,Overlapped模型是让应用措施使用重叠数据布局(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。这些提交的请求完成后,应用措施会收到通知。什么意思呢?就是说,如果你想从socket上接收数据,只需要报告系统,由系统为你接收数据,而你需要做的只是为系统供给一个缓冲区~~~~~
Overlapped I/O 完老例程模型:
老陈接收到新的信件后,一般的措施是:打开信封----掏出信纸----阅读信件----答复信件......为了进一步减轻用户承担,微软又开发了 一种新的技术:用户只要报告微软对信件的操纵法式,微软信箱将凭据这些法式去措置惩罚惩罚信件,不再需要用户亲自拆信/阅读/答复了!老陈终于过上了小资生活!
IOCP模型:
微软信箱似乎很完美,老陈也很对劲。但是在一些至公司情况却完全差别!这些至公司有数以万计的信箱,每秒钟都有数以百计的信件需要措置惩罚惩罚,以至于微软信箱经常因超负荷运转而瓦解!需要从头启动!微软不得不使出杀手锏......
微软给每个至公司派了一名名叫“Completion Port”的超级机器人,让这个机器人去措置惩罚惩罚那些信件!
“Windows NT小组注意到这些应用措施的性能没有预料的那么高。特另外,措置惩罚惩罚很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的 [没有被挂起和期待产生什么事],Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context],线程就没有得到很多 CPU时间来做它们的事情。大家可能也都觉得到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小,但也远不是没有开销的。我们不妨事设想一下:如果事先开好N个线程,让它们在那hold[堵塞],然后可以将所有用户的请求都投递到一个动静行列队伍中去。然后那N个线程逐一从动静行列队伍中去取出动静并加以措置惩罚惩罚。就可以制止针对每一个用户请求都开线程。不只减少了线程的资源,也提高了线程的操作率。
先看一下IOCP模型的实现:
//创建一个完成端口
FCompletPort := CreateIoCompletionPort( INVALID_HANDLE_VALUE, 0,0,0 );
//接受长途连接,并把这个连接的socket句柄绑定到适才创建的IOCP上
AConnect := accept( FListenSock, addr, len);
CreateIoCompletionPort( AConnect, FCompletPort, nil, 0 );
//创建CPU数*2 + 2个线程
for i:=1 to si.dwNumberOfProcessors*2+2 do
begin
AThread := TRecvSendThread.Create( false );
AThread.CompletPort := FCompletPort;//报告这个线程,你要去这个IOCP去访谒数据
end;
就这么简单,我们要做的就是成立一个IOCP,把长途连接的socket句柄绑定到适才创建的IOCP上,最后创建n个线程,并报告这n个线程到这个IOCP上去访谒数据就可以了。
再看一下TRecvSendThread线程都干些什么:
procedure TRecvSendThread.Execute;
var
......
begin
while (not self.Terminated) do
begin
//盘问IOCP状态(数据读写操纵是否完成)
GetQueuedCompletionStatus( CompletPort, BytesTransd, CompletKey, POVERLAPPED(pPerIoDat), TIME_OUT );
if BytesTransd <> 0 then
....;//数据读写操纵完成
//再投递一个读数据请求
WSARecv( CompletKey, @(pPerIoDat^.BufData), 1, BytesRecv, Flags, @(pPerIoDat^.Overlap), nil );
end;
end;
读写线程只是简单地查抄IOCP是否完成了我们投递的读写操纵,如果完成了则再投递一个新的读写请求。
应该注意到,我们创建的所有TRecvSendThread都在访谒同一个IOCP(因为我们只创建了一个IOCP),并且我们没有使用临界区!难道不会孕育产生斗嘴吗?不用考虑同步问题吗?
这正是IOCP的玄妙地址。IOCP不是一个普通的东西,不需要考虑线程安适问题。它会自动调配访谒它的线程:如果某个socket上有一个线程A正在访谒,那么线程B的访谒请求会被分配到此外一个socket。这一切都是由系统自动调配的,我们无需干预干与。
注:iocp都要使用线程池,此中的线程数目一般为当前电脑中cpu个数的2倍。