中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档 | 网通镜像
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > Delphi > 综合文章
基于Delphi的Socket I/O模型全接触
作者:Sabine  时间:2006-10-05 12:27 出处:ccidnet.com 责编:月夜寒箫
              摘要:基于Delphi的Socket I/O模型全接触

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

这和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;
关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有