幽默的讲解六种Socket I/O模型_很幽默的讲解六种socket i/o模型-程序员宅基地

技术标签: 网络编程  

很幽默的讲解六种Socket I/O模型

本文简单介绍了当前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。这一切都是由系统自动调配的,我们无需过问。

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/jadeshu/article/details/88712554

智能推荐

Oracle JAVAVM 组件 Reload 说明_java vm组件更新-程序员宅基地

文章浏览阅读1.1w次。一.JAVAVM 组件 说明 有关Oracle 所有组件的概述,参考:Oracle 8i/9i/10g/11g 组件(Components) 说明http://blog.csdn.net/tianlesoftware/article/details/5937382 现在我们查看组件的信息:SQL> col comp_id for a15SQL> col version for a15SQL> co_java vm组件更新

Spring Boot Actuator 未授权的测试与利用思路_sb-actuator-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏4次。Spring Boot Actuator 未授权的测试与利用思路0x0 前言  最近遇到的一个漏洞,但是因为目标关掉了一些端点导致没利用起来达到RCE的效果,不过在提升漏洞危害的时候,参考了不少文章,也做了一些尝试,所以分享出来自己的经历,希望大家如果遇到跟我一样的情况,可以省下自己调试时间,来做更有意义的事情。0x1 Actuator 简介官方简介: Spring Boot Actuator: Production-ready FeaturesSpring Bo..._sb-actuator

在python3.7+vs2019环境下使用f2py将fortran和c++程序编译为python库_python调用fortran代码-程序员宅基地

文章浏览阅读1.3k次。在python3.7+vs2019环境下使用f2py将fortran和c++程序编译为python库前言步骤1安装Fortran和Visual Studio编译环境2配置vs在python中的路径3测试c++编译环境4测试Fortran编译环境参考链接前言今天务必开贴记录下将Fortran或c++程序编译为python库的方法。起因是老师给了一个Fortran函数,无奈本人看不懂,因而想办法..._python调用fortran代码

uboot模式下怎么备份uboot和uImage_uboot备份固件命令-程序员宅基地

文章浏览阅读1.3k次。uboot中如果支持spi/qspi flash, 那么可以使用sf的erase, read, write命令操作spi flashsf read用来读取flash数据到内存sf write写内存数据到flashsf erase 擦除指定位置,指定长度的flash内容, 擦除后内容全1以备份uboot文件举例:1 、设置环境变量setenv serverip 192.168.230.111setenv ipaddr 192.168.230.124saping 192.168.230.111_uboot备份固件命令

看了此文,Oracle SQL优化文章不必再看!-程序员宅基地

文章浏览阅读965次。看了此文,Oracle SQL优化文章不必再看! 第一章 看了此文,Oracle SQL优化文章不必再看! DBAplus社群 | 2015-11-17 23:44 目录SQL优化的本质 SQL优化Road Map 2.1 制定SQL优化目标 2.2 检查执行计划 2.3 检查统计信息 2.4 检查高效访问结构 2.5 检查影...__optimizer_cost_based_transformation

vue 判断多个input 是否为空_vue form判断input不为空-程序员宅基地

文章浏览阅读9.3k次。vue 判断页面所有input的值不等于空,提交的时候并给出提示,1.html<input placeholder="必填" alt="开户支行" v-model="bank_branch" :value='bank_branch' />2.js//获取页面所有的inputvar els = document.getElementsByTagName("input"..._vue form判断input不为空

随便推点

将链表转换为树_链表node转树形接口-程序员宅基地

文章浏览阅读1.6k次。题目来源今天做了个题:将一个链表里的数据组装树形结构,链表里的数据已经满足树形结构要求这道题描述的很简单,但是有很多种情况。他只说了链表数据满足树形结构要求,并没有说明数据到底是什么样的,也就是题目参数具有多样性,这样其实我们给出一种解决方案就可以。而且也只要求将链表转换为树,并没有说是什么树。所以这道题说难也难,说简单也简单。解题思路最近也将平衡二叉树的原理看了一下,正好借着这..._链表node转树形接口

设计模式六大原则-程序员宅基地

文章浏览阅读151次。单一职责原则(Single Responsibility Principle)定义:不要存在多于一个导致类变更的原因。通俗的说,即一个类只负责一项职责。问题由来:类T负责两个不同的职责:职责P1,职责P2。当由于职责P1需求发生改变而需要修改类T时,有可能会导致原本运行正常的职责P2功能发生故障。解决方案:遵循单一职责原则。分别建立两个类T1、T2,使T1完成职责P1功能,T2完成职责..._b10原则

8天学通MongoDB——第一天 基础入门_传智mongodb-程序员宅基地

文章浏览阅读1.2k次。关于mongodb的好处,优点之类的这里就不说了,唯一要讲的一点就是mongodb中有三元素:数据库,集合,文档,其中“集合”就是对应关系数据库中的“表”,“文档”对应“行”。 一: 下载 上MongoDB官网 ,我们发现有32bit和64bit,这个就要看你系统了,不过这里有两点注意: ①:根据业界规则,偶数为“稳定版”(如:1.6.X,1.8.X_传智mongodb

常用Python第三方库 简介_python有用第三方库-程序员宅基地

文章浏览阅读6.3k次,点赞2次,收藏16次。常用Python第三方库简介 如果说强大的标准库奠定了Python发展的基石,丰富的第三方库则是python不断发展的保证,随着python的发展一些稳定的第三库被加入到了标准库里面,这里有6000多个第三方库的介绍:点这里或者访问:http://pypi.python.org/pypi?%3Aaction=index。下表中加粗并且标红的都是我平时使用较多的一些第三方库。(P.S.C_python有用第三方库

kubelet配置cni插件_第四步:树莓派Kubernetes集群安装-程序员宅基地

文章浏览阅读374次。4. Kubernetes集群安装4.1 master节点部署4.1.1 提前下载所需镜像看一下kubernetes v1.15.1需要哪些镜像:$ kubeadm config images list --kubernetes-version=v1.15.1k8s.gcr.io/kube-apiserver:v1.15.1k8s.gcr.io/kube-controller-manager:v1..._kubelet如何指定cni插件目录

xshell 隧道设置说明_xshell 隧道窗格说明-程序员宅基地

文章浏览阅读1.4w次。###图中仅对部分设备ip进行标注,描述如下场景50.237仅能访问171.7但由于安全策略等原因,无法直接访问171.8,因此利用xshell做隧道进行237与171.8之间的通信。###通讯方向说明192.168.50.237--&gt;192.168.171.7--&gt;192.168.171.81.xshell上在打开192.168.171.7的终端后,文件-&gt;属性-&gt;连接-..._xshell 隧道窗格说明