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

时间:2021-11-18 21:36:50

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

  这和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;