首页 > 其他分享 >《Socket I/O模型全接触》

《Socket I/O模型全接触》

时间:2023-04-28 17:41:47浏览次数:40  
标签:Index begin end Socket 模型 全接触 线程 FD addr


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

标签:Index,begin,end,Socket,模型,全接触,线程,FD,addr
From: https://blog.51cto.com/u_130277/6235014

相关文章

  • 解决 ERROR:Docker Got permission denied while trying to connect to the Docker da
    解决ERROR:DockerGotpermissiondeniedwhiletryingtoconnecttotheDockerdaemonsocketatunix在终端执行"dockerversion"命令,出现如下报错:”GotpermissiondeniedwhiletryingtoconnecttotheDockerdaemonsocketatunix:///var/run/docker.sock:Gethttp......
  • 模型层(models层) -- ORM
    目录Django模型层之ORM介绍sqlite3数据库pycharm查看sqlite3文件Navicate连接sqlite3测试环境的搭建单表操作单表的增删改查常见的十几种查询方法查看原生SQL语句query属性在settings中配置LOGGING日志基于双下划线查询多表查询之表关系操作一对多的外键增删改查数据多对多外键增......
  • 模型的显存和参数量计算
    模型的显存和参数量计算https://blog.csdn.net/weixin_49305813/article/details/119179849https://blog.csdn.net/NODIECANFLY/article/details/102854842......
  • 利用深度学习实现序列模型
    利用深度学习实现序列模型序列问题的含义是接收一个序列作为输入,然后期望预测这个序列的后续。例如继续预测2,4,6,8,10...。这在时间序列中是相当常见的,可以用来预测股市的波动、患者的体温曲线或者赛车所需的加速度。从原理上说,卷积神经网络可以有效处理空间信息,那么循环神经网......
  • 在vue中使用websocket
    封装根据项目的目录结构,在相应的文件夹下创建一个js文件,用来封装一个websocket类,假如项目中用到js的地方很多,最好把文件放在全局公共文件夹中;exportclassWS{ constructor(config){  this.ws=null;  this.url=null;  this.config=config;  ......
  • 相亲成功率100%!技术大佬都在用的SWOT模型【No.3】
    SWOT分析法,也称态势分析法或道斯矩阵。分别包含:内部要素的优势和劣势,外部要素的机会和威胁。SO:利用哪些优势,抓住市场机会ST:利用哪些优势,减少威胁OW:利用哪些机会,改善哪些劣势WT:在哪些威胁中改善哪些劣势刚京,男,32岁,职业:程序员。​如果刚......
  • C#使用委托在Socket Udp端口侦听线程内更新主窗口控件显示
    c#开启线程侦听SocketUDP端口,端口接收到网络读卡器的读卡数据后刷新UI界面显示接收数据,解析数据包信息并向读卡器发送显示文字、驱动读卡器播报语音、蜂鸣响声提示、开启继电器开关等操作。  .net提示通过设置:CheckForIllegalCrossThreadCalls=false,可以在子线程内强制更新......
  • 认识HTML5的WebSocket
    在HTML5规范中,我最喜欢的Web技术就是正迅速变得流行的WebSocketAPI。WebSocket提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术。这个新的API提供了一个方法,从客户端使用简单的语法有效地推动消息到服务器。让我们看一看HTML5的WebSocketAPI:它可用于客户端、服务器......
  • Python3+WebSockets实现WebSocket通信
    一、说明1.1背景说明前段时间同事说云平台通信使用了个websocket的东西,今天抽空来看一下具体是怎么个通信过程。从形式上看,websocket是一个应用层协议,socket是数据链路层、网络层、传输层的抽像;从应用场合上看,websocket可以使用javascript实现,而socket不能用javascript实现(真......
  • 安装Amos结构方程模型分析软件的方法
      本文介绍IBMSPSSAmos软件的安装方法。  Amos是IBM公司旗下一款强大的结构方程建模软件。其捆绑在高级版的SPSSStatistics软件中,但其它版本的SPSSStatistics中并不含Amos,需要单独下载、安装。1软件下载  关于Amos软件下载,由于其版本较多,大家自行搜索需要的版本即可......