`
alienchang
  • 浏览: 30757 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

windows网络通信之IO模型

阅读更多
《Socket I/O模型全接触》
作  者: flyinwuhan (制怒·三思而后行)

本文简单介绍了当前Windows支持的各种Socket I/O模型,如果你发现其中存在什么错误请务必赐教。

一:select模型
二:WSAAsyncSelect模型
三:WSAEventSelect模型
四:Overlapped I/O 事件通知模型
五:Overlapped I/O 完成例程模型
六:IOCP模型

老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。
这和Socket模型非常类似。下面我就以老陈接收信件为例讲解Socket I/O模型~~~

一:select模型

老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信~~~~~
在这种情况下,"下楼检查信箱"然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。
select模型和老陈的这种情况非常相似:周而复始地去检查......如果有数据......接收/发送.......

使用线程来select应该是通用的做法:
procedure TListenThread.Execute;
var
addr : TSockAddrIn;
fd_read : TFDSet;
timeout : TTimeVal;
ASock,
MainSock : TSocket;
len, i : Integer;
begin
MainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
addr.sin_family := AF_INET;
addr.sin_port := htons(5678);
addr.sin_addr.S_addr := htonl(INADDR_ANY);
bind( MainSock, @addr, sizeof(addr) );
listen( MainSock, 5 );

while (not Terminated) do
begin
FD_ZERO( fd_read );
FD_SET( MainSock, fd_read );
timeout.tv_sec := 0;
timeout.tv_usec := 500;
if select( 0, @fd_read, nil, nil, @timeout ) > 0 then //至少有1个等待Accept的connection
begin
if FD_ISSET( MainSock, fd_read ) then
begin
for i:=0 to fd_read.fd_count-1 do //注意,fd_count <= 64,也就是说select只能同时管理最多64个连接
begin
len := sizeof(addr);
ASock := accept( MainSock, addr, len );
if ASock <> INVALID_SOCKET then
....//为ASock创建一个新的线程,在新的线程中再不停地select
end;
end;
end;
end; //while (not self.Terminated)

shutdown( MainSock, SD_BOTH );
closesocket( MainSock );
end;

二:WSAAsyncSelect模型

后来,老陈使用了微软公司的新式信箱。这种信箱非常先进,一旦信箱里有新的信件,盖茨就会给老陈打电话:喂,大爷,你有新的信件了!从此,老陈再也不必频繁上下楼检查信箱了,牙也不疼了,你瞅准了,蓝天......不是,微软~~~~~~~~
微软提供的WSAAsyncSelect模型就是这个意思。

WSAAsyncSelect模型是Windows下最简单易用的一种Socket I/O模型。使用这种模型时,Windows会把网络事件以消息的形势通知应用程序。
首先定义一个消息标示常量:
const WM_SOCKET = WM_USER + 55;
再在主Form的private域添加一个处理此消息的函数声明:
private
procedure WMSocket(var Msg: TMessage); message WM_SOCKET;
然后就可以使用WSAAsyncSelect了:
var
addr : TSockAddr;
sock : TSocket;

sock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
addr.sin_family := AF_INET;
addr.sin_port := htons(5678);
addr.sin_addr.S_addr := htonl(INADDR_ANY);
bind( m_sock, @addr, sizeof(SOCKADDR) );

WSAAsyncSelect( m_sock, Handle, WM_SOCKET, FD_ACCEPT or FD_CLOSE );

listen( m_sock, 5 );
....

应用程序可以对收到WM_SOCKET消息进行分析,判断是哪一个socket产生了网络事件以及事件类型:

procedure TfmMain.WMSocket(var Msg: TMessage);
var
sock : TSocket;
addr : TSockAddrIn;
addrlen : Integer;
buf : Array [0..4095] of Char;
begin
//Msg的WParam是产生了网络事件的socket句柄,LParam则包含了事件类型
case WSAGetSelectEvent( Msg.LParam ) of
FD_ACCEPT :
begin
addrlen := sizeof(addr);
sock := accept( Msg.WParam, addr, addrlen );
if sock <> INVALID_SOCKET then
WSAAsyncSelect( sock, Handle, WM_SOCKET, FD_READ or FD_WRITE or FD_CLOSE );
end;

FD_CLOSE : closesocket( Msg.WParam );
FD_READ : recv( Msg.WParam, buf[0], 4096, 0 );
FD_WRITE : ;
end;
end;

三:WSAEventSelect模型

后来,微软的信箱非常畅销,购买微软信箱的人以百万计数......以至于盖茨每天24小时给客户打电话,累得腰酸背痛,喝...都不好使~~~~~~
微软改进了他们的信箱:在客户的家中添加一个附加装置,这个装置会监视客户的信箱,每当新的信件来临,此装置会发出"新信件到达"声,提醒老陈去收信。盖茨终于可以睡觉了。

同样要使用线程:
procedure TListenThread.Execute;
var
hEvent : WSAEvent;
ret : Integer;
ne : TWSANetworkEvents;
sock : TSocket;
adr : TSockAddrIn;
sMsg : String;
Index,
EventTotal : DWORD;
EventArray : Array [0..WSA_MAXIMUM_WAIT_EVENTS-1] of WSAEVENT;
begin
...socket...bind...
hEvent := WSACreateEvent();
WSAEventSelect( ListenSock, hEvent, FD_ACCEPT or FD_CLOSE );
...listen...

while ( not Terminated ) do
begin
Index := WSAWaitForMultipleEvents( EventTotal, @EventArray[0], FALSE, WSA_INFINITE, FALSE );
FillChar( ne, sizeof(ne), 0 );
WSAEnumNetworkEvents( SockArray[Index-WSA_WAIT_EVENT_0], EventArray[Index-WSA_WAIT_EVENT_0], @ne );

if ( ne.lNetworkEvents and FD_ACCEPT ) > 0 then
begin
if ne.iErrorCode[FD_ACCEPT_BIT] <> 0 then
continue;

ret := sizeof(adr);
sock := accept( SockArray[Index-WSA_WAIT_EVENT_0], adr, ret );
if EventTotal > WSA_MAXIMUM_WAIT_EVENTS-1 then//这里WSA_MAXIMUM_WAIT_EVENTS同样是64
begin
closesocket( sock );
continue;
end;

hEvent := WSACreateEvent();
WSAEventSelect( sock, hEvent, FD_READ or FD_WRITE or FD_CLOSE );
SockArray[EventTotal] := sock;
EventArray[EventTotal] := hEvent;
Inc( EventTotal );
end;

if ( ne.lNetworkEvents and FD_READ ) > 0 then
begin
if ne.iErrorCode[FD_READ_BIT] <> 0 then
continue;
FillChar( RecvBuf[0], PACK_SIZE_RECEIVE, 0 );
ret := recv( SockArray[Index-WSA_WAIT_EVENT_0], RecvBuf[0], PACK_SIZE_RECEIVE, 0 );
......
end;
end;
end;



四:Overlapped I/O 事件通知模型

后来,微软通过调查发现,老陈不喜欢上下楼收发信件,因为上下楼其实很浪费时间。于是微软再次改进他们的信箱。新式的信箱采用了更为先进的技术,只要用户告诉微软自己的家在几楼几号,新式信箱会把信件直接传送到用户的家中,然后告诉用户,你的信件已经放到你的家中了!老陈很高兴,因为他不必再亲自收发信件了!

Overlapped I/O 事件通知模型和WSAEventSelect模型在实现上非常相似,主要区别在"Overlapped",Overlapped模型是让应用程序使用重叠数据结构(WSAOVERLAPPED),一次投递一个或多个Winsock I/O请求。这些提交的请求完成后,应用程序会收到通知。什么意思呢?就是说,如果你想从socket上接收数据,只需要告诉系统,由系统为你接收数据,而你需要做的只是为系统提供一个缓冲区~~~~~
Listen线程和WSAEventSelect模型一模一样,Recv/Send线程则完全不同:
procedure TOverlapThread.Execute;
var
dwTemp : DWORD;
ret : Integer;
Index : DWORD;
begin
......

while ( not Terminated ) do
begin
Index := WSAWaitForMultipleEvents( FLinks.Count, @FLinks.Events[0], FALSE, RECV_TIME_OUT, FALSE );
Dec( Index, WSA_WAIT_EVENT_0 );
if Index > WSA_MAXIMUM_WAIT_EVENTS-1 then //超时或者其他错误
continue;

WSAResetEvent( FLinks.Events[Index] );
WSAGetOverlappedResult( FLinks.Sockets[Index], FLinks.pOverlaps[Index], @dwTemp, FALSE, FLinks.pdwFlags[Index]^ );

if dwTemp = 0 then //连接已经关闭
begin
......
continue;
end else
begin
fmMain.ListBox1.Items.Add( FLinks.pBufs[Index]^.buf );
end;

//初始化缓冲区
FLinks.pdwFlags[Index]^ := 0;
FillChar( FLinks.pOverlaps[Index]^, sizeof(WSAOVERLAPPED), 0 );
FLinks.pOverlaps[Index]^.hEvent := FLinks.Events[Index];
FillChar( FLinks.pBufs[Index]^.buf^, BUFFER_SIZE, 0 );

//递一个接收数据请求
WSARecv( FLinks.Sockets[Index], FLinks.pBufs[Index], 1, FLinks.pdwRecvd[Index]^, FLinks.pdwFlags[Index]^, FLinks.pOverlaps[Index], nil );
end;
end;

五:Overlapped I/O 完成例程模型

老陈接收到新的信件后,一般的程序是:打开信封----掏出信纸----阅读信件----回复信件......为了进一步减轻用户负担,微软又开发了一种新的技术:用户只要告诉微软对信件的操作步骤,微软信箱将按照这些步骤去处理信件,不再需要用户亲自拆信/阅读/回复了!老陈终于过上了小资生活!

Overlapped I/O 完成例程要求用户提供一个回调函数,发生新的网络事件的时候系统将执行这个函数:
procedure WorkerRoutine( const dwError, cbTransferred : DWORD; const
lpOverlapped : LPWSAOVERLAPPED; const dwFlags : DWORD ); stdcall;
然后告诉系统用WorkerRoutine函数处理接收到的数据:
WSARecv( m_socket, @FBuf, 1, dwTemp, dwFlag, @m_overlap, WorkerRoutine );
然后......没有什么然后了,系统什么都给你做了!微软真实体贴!
while ( not Terminated ) do//这就是一个Recv/Send线程要做的事情......什么都不用做啊!!!
begin
if SleepEx( RECV_TIME_OUT, True ) = WAIT_IO_COMPLETION then //
begin
;
end else
begin
continue;
end;
end;

六:IOCP模型

微软信箱似乎很完美,老陈也很满意。但是在一些大公司情况却完全不同!这些大公司有数以万计的信箱,每秒钟都有数以百计的信件需要处理,以至于微软信箱经常因超负荷运转而崩溃!需要重新启动!微软不得不使出杀手锏......
微软给每个大公司派了一名名叫"Completion Port"的超级机器人,让这个机器人去处理那些信件!

"Windows NT小组注意到这些应用程序的性能没有预料的那么高。特别的,处理很多同时的客户请求意味着很多线程并发地运行在系统中。因为所有这些线程都是可运行的[没有被挂起和等待发生什么事],Microsoft意识到NT内核花费了太多的时间来转换运行线程的上下文[Context],线程就没有得到很多CPU时间来做它们的工作。大家可能也都感觉到并行模型的瓶颈在于它为每一个客户请求都创建了一个新线程。创建线程比起创建进程开销要小,但也远不是没有开销的。我们不妨设想一下:如果事先开好N个线程,让它们在那hold[堵塞],然后可以将所有用户的请求都投递到一个消息队列中去。然后那N个线程逐一从消息队列中去取出消息并加以处理。就可以避免针对每一个用户请求都开线程。不仅减少了线程的资源,也提高了线程的利用率。理论上很不错,你想我等泛泛之辈都能想出来的问题,Microsoft又怎会没有考虑到呢?"-----摘自nonocast的《理解I/O Completion Port》

先看一下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;

OK,就这么简单,我们要做的就是建立一个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。这一切都是由系统自动调配的,我们无需过问。
分享到:
评论

相关推荐

    windows网络通信IO模型 socket课件详细完整

    windows网络socket模型 异步通信模型 select模型 异步选择 异步事件 重叠IO 完成端口 详细介绍了区别 用法与实例

    windows网络与通信程序设计源代码

    这本书中介绍了诸多关于Windows网络通信的技术 从winSock编程接口和各种IO模型到网络安全中的各种技术 其中的代码很详细 很具有使用价值 在其基础上稍微改动即可用在实际的工程项目中

    test9Demo.rar_overlapped io

    windows网络通信IO模型 1、 参考SelectServerDemo理解select模型原理及过程。 2、 参考WSAEventSelectServerDemo理解WSAEventSelect模型原理及过程。 3、 参考OverlappedEventServerDemo1理解Overlapped I/O事件通知...

    Windows2000 服务器端应用程序开发设计指南

    08-06-13 WSAAsyncSelect网络编程框架 08-06-13 overlapped重叠IO模型网络编程框架 08-06-13 采用C++的ACE库实现的一个通用的C/S架构通信程序 08-06-13 ACE应用-第1章 应用模式语言开发应用级网关 08-06-13 ACE应用-...

    Delphi 跨平台 Socket 通讯库

    - 针对不同平台使用不同的IO模型: - IOCP &gt; Windows - KQUEUE &gt; FreeBSD(MacOSX, iOS...) - EPOLL &gt; Linux(Linux, Android...) - 支持极高的并发 - Windows &gt; 能跑10万以上的并发数, 需要修改注册表调整...

    EOS的经济模型是怎样的?.mp4

    EOS白皮书中称其有非常强大的拓展性能够提供账户、身份认证、数据库、异步通信等程序调用和并行运算,是一个已商用为目的的区块链平台,具有低延迟和免费交易费用。而EOS通证则是EOS平台中的价值载体,是获取网络...

    服务器概要设计说明.docx

    IOCP模型是Windows服务器开发中性能最好的非阻塞异步IO模型,所以通信层采用IOCP模型构建。Windows下有五种非阻塞I/O模型:选择〔Select〕、异步选择〔WSAAsyncSelect〕、事件选择〔WSAEventSelect〕、重叠I/O〔...

    寒江独钓-Windows内核安全编程(高清完整版).part4

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    寒江独钓-Windows内核安全编程(高清完整版).part7

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    寒江独钓-Windows内核安全编程(高清完整版).part1

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    寒江独钓-Windows内核安全编程(高清完整版).part5

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    寒江独钓-Windows内核安全编程(高清完整版).part6

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    IOCP.zip_IOCP_IOCP 通信_IOCP类 c++_IOCp pudn_pudn IOCP

    传说中的io完成端口,具有性能高的特点,是windows中性能最好的网络通信模型,这个是iocp的类。。。。。。。。。。。。。。。。。。。

    Windows内核安全与驱动开发光盘源码

    2.5 Windows的驱动开发模型 29 2.6 WDK编程中的特殊点 30 2.6.1 内核编程的主要调用源 30 2.6.2 函数的多线程安全性 30 2.6.3 代码的中断级 32 2.6.4 WDK中出现的特殊代码 32 第3章 字符串与链表 35 3.1 字符...

    寒江独钓-Windows内核安全编程(高清完整版).part2

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    寒江独钓-Windows内核安全编程(高清完整版).part3

    13.1.1 Windows网络架构总结 436 13.1.2 NDIS中间层驱动简介 437 13.1.3 NDIS中间层驱动的应用 438 13.1.4 NDIS包描述符结构深究 439 13.2 中间层驱动的入口与绑定 442 13.2.1 中间层驱动的入口函数 442 13.2.2 动态...

    Windows内核安全驱动开发(随书光盘)

    2.5 Windows的驱动开发模型 29 2.6 WDK编程中的特殊点 30 2.6.1 内核编程的主要调用源 30 2.6.2 函数的多线程安全性 30 2.6.3 代码的中断级 32 2.6.4 WDK中出现的特殊代码 32 第3章 字符串与链表 35 3.1 字符...

    数据可视化智能软件主要性能指标.doc

    C 支持IO通信多"进程"调度模式,根据实际需要可自由选择与分配信道与设备的数量来分 配多个或单个进程管理,优化数据采集开销; D 具有较强的数据读写能力,支持批量下置功能,支持写优先的策略,保证现场工况突发 ...

    win2000驱动程序设计指南

    网络驱动程序设计指南的向导 3 第二章 内核模式驱动程序的网络结构 6 2.1 Windows 2000 网络结构和OSI模型 6 2.2 NDIS驱动程序 7 2.2.1 NDIS微端口驱动程序 7 2.2.2 NDIS中间层驱动程序 8 2.2.3 ...

    VC++版完整端口的代理服务器原型程序代码

    内容索引:VC/C++源码,网络相关,代理服务器 这好像是一个多线程的代理程序,程序运行后以服务的形式运行,程序以应用windows socket IO完成端口模型提供代理服务的原型设计,目前支持http的一级和二级代理,连接断开...

Global site tag (gtag.js) - Google Analytics