基于Delphi的Socket I/O模型全接触

本文以老陈与女儿通过信件联系的故事为主线,详细介绍了Socket模型,包括select模型、WSAAsyncSelect模型、WSAEventSelect模型、OverlappedI/O事件通知模型、OverlappedI/O完成例程模型以及IOCP模型,通过这些模型的描述,清晰地展示了SocketI/O在不同场景下的应用和优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的信箱里。

  这和Socket模型非常类似。下面我就以老陈接收信件为例讲解SocketI/O模型。

  一:select模型

  老陈非常想看到女儿的信。以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信,在这种情况下,“下楼检查信箱”然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。

  select模型和老陈的这种情况非常相似:周而复始地去检查......如果有数据......接收/发送.......

  使用线程来select应该是通用的做法:

procedureTListenThread.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(notTerminated)do
 begin
  FD_ZERO(fd_read);
  FD_SET(MainSock,fd_read);http://www.mscto.com
  timeout.tv_sec:=0;
  timeout.tv_usec:=500;
  ifselect(0,@fd_read,nil,nil,@timeout)>0then//至少有1个等待Accept的connection
  begin
   ifFD_ISSET(MainSock,fd_read)then
   begin
   fori:=0tofd_read.fd_count-1do//注意,fd_count<=64,也就是说select只能同时管理最多64个连接
   begin
    len:=sizeof(addr);
    ASock:=accept(MainSock,addr,len);
    ifASock<>INVALID_SOCKETthen
     ....//为ASock创建一个新的线程,在新的线程中再不停地select
    end;
   end;  
  end;
 end;//while(notself.Terminated)

 shutdown(MainSock,SD_BOTH);
 closesocket(MainSock);
end;
  二:WSAAsyncSelect模型

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

  微软提供的WSAAsyncSelect模型就是这个意思。

  WSAAsyncSelect模型是Windows下最简单易用的一种SocketI/O模型。使用这种模型时,Windows会把网络事件以消息的形势通知应用程序。

  首先定义一个消息标示常量:

constWM_SOCKET=WM_USER 55;
  再在主Form的private域添加一个处理此消息的函数声明:

private
procedureWMSocket(varMsg:TMessage);messageWM_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_ACCEPTorFD_CLOSE);

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

procedureTfmMain.WMSocket(varMsg:TMessage);
var
 sock:TSocket;
 addr:TSockAddrIn;
 addrlen:Integer;
 buf:Array[0..4095]ofChar;
begin
 //Msg的WParam是产生了网络事件的socket句柄,LParam则包含了事件类型
caseWSAGetSelectEvent(Msg.LParam)of
 FD_ACCEPT:
  begin
   addrlen:=sizeof(addr);
   sock:=accept(Msg.WParam,addr,addrlen);
   ifsock<>INVALID_SOCKETthen
    WSAAsyncSelect(sock,Handle,WM_SOCKET,FD_READorFD_WRITEorFD_CLOSE);
  end;

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

  三:WSAEventSelect模型

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

  同样要使用线程:

procedureTListenThread.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]ofWSAEVENT; 软件开发网
begin
 ...socket...bind...
 hEvent:=WSACreateEvent();
 WSAEventSelect(ListenSock,hEvent,FD_ACCEPTorFD_CLOSE);
 ...listen...

 while(notTerminated)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.lNetworkEventsandFD_ACCEPT)>0then
  begin
   ifne.iErrorCode[FD_ACCEPT_BIT]<>0then
    continue;

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

   hEvent:=WSACreateEvent();
   WSAEventSelect(sock,hEvent,FD_READorFD_WRITEorFD_CLOSE);
   SockArray[EventTotal]:=sock;
   EventArray[EventTotal]:=hEvent;
   Inc(EventTotal);

http://www.mscto.com
  end;

  if(ne.lNetworkEventsandFD_READ)>0then
  begin
   ifne.iErrorCode[FD_READ_BIT]<>0then
    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;
  四:OverlappedI/O事件通知模型

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

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

procedureTOverlapThread.Execute;
var
 dwTemp:DWORD;
 ret:Integer;
 Index:DWORD;
begin
 ......

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

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

  ifdwTemp=0then//连接已经关闭
  begin
   ......
   continue;
  endelse
 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;

  五:OverlappedI/O完成例程模型

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

  OverlappedI/O完成例程要求用户提供一个回调函数,发生新的网络事件的时候系统将执行这个函数:

procedureWorkerRoutine(constdwError,cbTransferred:DWORD;
const
lpOverlapped:LPWSAOVERLAPPED;constdwFlags:DWORD);stdcall;
  然后告诉系统用WorkerRoutine函数处理接收到的数据:

WSARecv(m_socket,@FBuf,1,dwTemp,dwFlag,@m_overlap,WorkerRoutine);
  然后......没有什么然后了,系统什么都给你做了!微软真实体贴!

while(notTerminated)do//这就是一个Recv/Send线程要做的事情......什么都不用做啊!!!
begin


 ifSleepEx(RECV_TIME_OUT,True)=WAIT_IO_COMPLETIONthen//
 begin
  ;
 endelse
 begin
  continue;
 end;
end;
  六:IOCP模型

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

  微软给每个大公司派了一名名叫“CompletionPort”的超级机器人,让这个机器人去处理那些信件!

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

//创建一个完成端口
FCompletPort:=CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0);

//接受远程连接,并把这个连接的socket句柄绑定到刚才创建的IOCP上
AConnect:=accept(FListenSock,addr,len);
CreateIoCompletionPort(AConnect,FCompletPort,nil,0);

//创建CPU数*2 2个线程
fori:=1tosi.dwNumberOfProcessors*2 2do
begin
 AThread:=TRecvSendThread.Create(false);
 AThread.CompletPort:=FCompletPort;//告诉这个线程,你要去这个IOCP去访问数据
end;
  就这么简单,我们要做的就是建立一个IOCP,把远程连接的socket句柄绑定到刚才创建的IOCP上,最后创建n个线程,并告诉这n个线程到这个IOCP上去访问数据就可以了。

  再看一下TRecvSendThread线程都干些什么:

procedureTRecvSendThread.Execute;
var
 ......
begin
 while(notself.Terminated)do
 begin
  //查询IOCP状态(数据读写操作是否完成)
  GetQueuedCompletionStatus(CompletPort,BytesTransd,CompletKey,POVERLAPPED(pPerIoDat),TIME_OUT);

  ifBytesTransd<>0then
   ....;//数据读写操作完成
  
   //再投递一个读数据请求
   WSARecv(CompletKey,@(pPerIoDat^.BufData),1,BytesRecv,Flags,@(pPerIoDat^.Overlap),nil);
  end;
end;
  读写线程只是简单地检查IOCP是否完成了我们投递的读写操作,如果完成了则再投递一个新的读写请求。

  应该注意到,我们创建的所有TRecvSendThread都在访问同一个IOCP(因为我们只创建了一个IOCP),并且我们没有使用临界区!难道不会产生冲突吗?不用考虑同步问题吗?

  这正是IOCP的奥妙所在。IOCP不是一个普通的对象,不需要考虑线程安全问题。它会自动调配访问它的线程:如果某个socket上有一个线程A正在访问,那么线程B的访问请求会被分配到另外一个socket。这一切都是由系统自动调配的,我们无需过问。



资源下载链接为: https://pan.quark.cn/s/abbae039bf2a 无锡平芯微半导体科技有限公司生产的A1SHB三极管(全称PW2301A)是一款P沟道增强型MOSFET,具备低内阻、高重复雪崩耐受能力以及高效电源切换设计等优势。其技术规格如下:最大漏源电压(VDS)为-20V,最大连续漏极电流(ID)为-3A,可在此条件下稳定工作;栅源电压(VGS)最大值为±12V,能承受正反向电压;脉冲漏极电流(IDM)可达-10A,适合处理短暂高电流脉冲;最大功率耗散(PD)为1W,可防止器件过热。A1SHB采用3引脚SOT23-3封装,小型化设计利于空间受限的应用场景。热特性方面,结到环境的热阻(RθJA)为125℃/W,即每增加1W功率损耗,结温上升125℃,提示设计电路时需考虑散热。 A1SHB的电气性能出色,开关特性优异。开关测试电路及波形图(图1、图2)展示了不同条件下的开关性能,包括开关上升时间(tr)、下降时间(tf)、开启时间(ton)和关闭时间(toff),这些参数对评估MOSFET在高频开关应用中的效率至关重要。图4呈现了漏极电流(ID)与漏源电压(VDS)的关系,图5描绘了输出特性曲线,反映不同栅源电压下漏极电流的变化。图6至图10进一步揭示性能特征:转移特性(图7)显示栅极电压(Vgs)对漏极电流的影响;漏源开态电阻(RDS(ON))随Vgs变化的曲线(图8、图9)展现不同控制电压下的阻抗;图10可能涉及电容特性,对开关操作的响应速度和稳定性有重要影响。 A1SHB三极管(PW2301A)是高性能P沟道MOSFET,适用于低内阻、高效率电源切换及其他多种应用。用户在设计电路时,需充分考虑其电气参数、封装尺寸及热管理,以确保器件的可靠性和长期稳定性。无锡平芯微半导体科技有限公司提供的技术支持和代理商服务,可为用户在产品选型和应用过程中提供有
了解服务器端如何处理大量并发连接对于掌握MMORPG游戏开发至关重要。《传奇MMORPG游戏源码深度分析》一书深入探讨了Mir2的服务器架构,特别是其Socket I/O模型的实现,这对于游戏的性能有着直接的影响。 参考资源链接:[传奇MMORPG游戏源码深度分析](https://wenku.youkuaiyun.com/doc/3sihbd3bvj?spm=1055.2569.3001.10343) 首先,Mir2服务器可能采用了高效的非阻塞I/O或多路复用技术,比如epoll模型,这种模型能够在单个线程中监听多个网络连接,极大地提高了处理并发连接的能力,从而保证了即使在大量玩家同时在线的情况下,游戏也能保持流畅。 具体实现上,服务器端代码中会有一个主循环监听网络事件,并根据事件类型分发处理逻辑给不同的模块。例如,当一个客户端发起连接请求时,服务器端会通过Socket API接收连接,并创建一个新的线程或者使用线程池来处理与该客户端的后续通信。 在多线程环境下,服务器需要妥善管理线程间的同步和通信,确保数据的一致性和线程安全。面向对象技术在这里发挥了作用,将相关的数据和方法封装在对象中,有助于维护和复用代码。 此外,使用Delphi或类似的开发工具,可以更加高效地编写和调试此类高性能服务器代码,使得源码的可读性和可维护性得到了提升。 综上所述,Socket I/O模型在Mir2服务器中的应用,使得服务器能够高效地管理并发连接,这对于整个游戏的稳定运行和玩家体验有着决定性的影响。而掌握这些技术,尤其是对现有的游戏开发有着重要的借鉴意义。对于希望进一步深化理解MMORPG服务器端技术的开发者,我强烈推荐深入学习《传奇MMORPG游戏源码深度分析》一书。 参考资源链接:[传奇MMORPG游戏源码深度分析](https://wenku.youkuaiyun.com/doc/3sihbd3bvj?spm=1055.2569.3001.10343)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值