DELPHI下的Winsock编程(三)--一模式和I/O控制

I/O控制指令
        一系列套接字I/O控制函数用于在套接字之上,控制I/O的行为,同时获取与那个套接字上进行的I/O操作有关的信息。其中,第一个函数是ioctlsocket,起源于Winsock 1规范,声明如下:
int ioctlsocket (
    SOCKET s,
    long cmd,
    u_long FAR* argp
   );

 
        其中,参数s指定的是要在上面采取I/O操作的套接字描述符,而cmd是一个预定义的标志,用于打算执行的I/O控制命令。最后一个参数argp对应的是一个指针,指向与命令密切相关的一个变量。描述好每个命令之后,再给出要求变量的类型。

标准I/O控制命令
1. FIONBIO
       该命令可在套接字s上允许或禁止“非锁定”(Nonblocking)模式。默认情况下,所有套接字在创建好后,都会自动进入“锁定”套接字。若随FIONBIO这个I/O控制命令来调用ioctlsocket,那么应设置argp,令其传递指向一个“无符号”(无正负号)长整数的指针;若打算启用非锁定模式,应将那个长整数的值设为一个非零值。而若设为0值,意味着套接字进入锁定模式。
       调用WSAAsyncSelect或WSAEventSelect函数的时候,会将套接字自动设为非锁定模式。调用了其中任何一个函数之后,再有任何将套接字设回锁定模式的企图,都会以失败告终,并返回WSAEINVAL 错误。要想将套接字改回锁定模式,应用程序首先必须禁止WSAAsyncSelect。具体的做法是调用WSAAsyncSelect,同时令其lEvent参数等于0。或者调用WSAEventSelect,令lNetworkEvents参数等于0,从而禁止WSAEventSelect
2. FIONREAD
       该命令用于决定可从套接字上自动读入的数据量。对ioctlsocket 来说,argp值会返回一个无符号的整数,其中包含了打算读入的字节数。若套接字s是一个“面向数据流”的套接字(类型为SOCK_STREAM),那么FIONREAD会返回在单独一次接收调用中,可返回的数据总量。要注意的是,若使用这种或其他形式的消息预先“窥视”机制,并一定保证能够返回正确的数据量。若在一个数据报套接字(类型为SOCK_DGRAM)上使用I/O控制命令,返回值就是在套接字上排队的第一条消息的大小。
3. SIOCAT M A R K
       若一个套接字配置成接收带外(OOB)数据,而且已设置成以内嵌方式读取这种OOB数据(通过设置SO_OOBINLINE套接字选项),那么本I/O控制命令就会返回一个布尔值,指出接下来是否准备接收OOB数据。如答案是肯定的,则返回TRUE;否则,便返回FALSE,而且下一次接收操作会返回OOB数据之前的所有或部分数据。对ioctlsocket来说,argp会返回一个指向布尔变量的指针。

套接字模式
      Windows套接字在两种模式下执行I/O操作:锁定和非锁定(阻塞和非阻塞)。
     在锁定模式下,在I/O操作完成前,执行操作的Winsock函数(比如send和recv)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下, Winsock函数无论如何都会立即返回。
      对于处在锁定模式的套接字,我们必须多加留意,因为在一个锁定套接字上调用任何一个Winsock API函数,都会产生相同的后果—耗费或长或短的时间“等待”。大多数Winsock应用都是遵照一种“生产者-消费者”模型来编制的。在这种模型中,应用程序需要读取(或写入)指定数量的字节,然后以它为基础执行一些计算。这种方式下的使用,一定要注意到阻塞作用产生的副作用,例如,我们编写了了一个“服务器端”的进程,创建一个套接字,然后在主线程中用一个循环接受客户端发起的连接请求,我们用到了ACCEPT函数,那么在阻塞模式下,当没有客户端请求发送时,调用accept函数的线程(这里是主线程)将一直阻塞下去,不会返回,这也就意味着你其他的并发操作无法执行,例如你的程序带有GUI界面,那么你将无法操作窗口上的其他按钮。
        为了解决上述问题,我们注意到阻塞的作用是针对调用它的线程,也就是说,如果我们在主线程中创建一个辅助线程来进行轮循操作,那么虽然此辅助线程可能被阻塞,但不会影响到主线程的工作。
       对锁定套接字来说,它的一个缺点在于:应用程序很难同时通过多个建好连接的套接字通信。使用前述的办法,我们可对应用程序进行修改,令其为连好的每个套接字都分配一个读线程,以及一个数据处理线程。尽管这仍然会增大一些开销,但的确是一种可行的方案。唯一的缺点便是扩展性极差,以后想同时处理大量套接字时,恐怕难以下手。

非锁定模式
        除了锁定模式,我们还可考虑采用非锁定模式的套接字。尽管这种套接字在使用上存在着些许难度,但只要排除了这项困难,它在功能上还是非常强大的。除具备锁定套接字已有的各项优点之外,还进行了少许扩充,功能更强。将一个套接字置为非锁定模式之后, Winsock API调用会立即返回。大多数情况下,这些调用都会“失败”,并返回一个WSAEWOULDBLOCK错误。什么意思呢?它意味着请求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中,尚不存在“待决”的数据,那么recv(接收数据)调用就会返回WSAEWOULDBLOCK错误。通常,我们需要重复调用同一个函数,直至获得一个成功返回代码。
       由于非锁定调用会频繁返回WSAEWOULDBLOCK错误,所以在任何时候,都应仔细检查所有返回代码,并作好“失败”的准备。许多程序员易犯的一个错误便是连续不停地调用一个函数,直到它返回成功的消息为止。
       锁定和非锁定套接字模式都存在着优点和缺点。其中,从概念的角度说,锁定套接字更易使用。但在应付建立连接的多个套接字时,或在数据的收发量不均,时间不定时,却显得极难管理。而另一方面,假如需要编写更多的代码,以便在每个Winsock调用中,对收到一个WSAEWOULDBLOCK错误的可能性加以应付,那么非锁定套接字便显得有些难于操作。在这些情况下,可考虑使用“套接字I / O模型”,它有助于应用程序通过一种异步方式,同时对一个或多个套接字上进行的通信加以管理。

一个例子:
   为了阐述锁定模式和非锁定模式的区别,可以用下面这个例子来演示:

{
    作者:wudi_1982
    联系方式:wudi_1982@hotmail.com
    转载请著名出处
}

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs,winsock, StdCtrls;

type
  TForm1 
=   class (TForm)
    Button1: TButton;
    Button2: TButton;
    ckbxB: TCheckBox;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  
private
    
{ Private declarations }
  
public
    
{ Public declarations }
  end;
  TSockReadThread
=   class (TThread)
    
private
      FSocket : TSocket;
      FBuf : array[
0 .. 255 ] of Char;
      FMemo : TMemo;
      procedure GetResult;
    
protected
      procedure Execute;
override ;
    
public
      constructor Create(pSocket : TSocket;mm : TMemo);
  end;

var
  Form1: TForm1;
  WSAData : TWSAData;

implementation

{$R *.dfm}

procedure StartUp;
var
  ErrorCode : integer;
begin
  
// 加载winSock dll
  ErrorCode : =  WSAStartup($ 0101 , WSAData);
  
if  ErrorCode  <>   0  then
  begin
    ShowMessage(
' 加载失败 ' );
    exit;
  end;
end;
// 创建一个服务器
procedure TForm1.Button1Click(Sender: TObject);
var
  ErrorCode,AddSize  : integer;
  SockAdd_In,Add: TSockAddrIn;
  tm : Longint;
  WSAData : TWSAData;
  FSock,AcceptSock : TSocket;
begin

  
// 创建一个使用TCP协议的套接字
  FSock : =  socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
if  FSock  =  SOCKET_ERROR then
  begin
    showmessage(Format(
' %s;ErrorCode:%d ' ,[ ' 套接字创建失败 ' ,WSAGetLastError]) );
    Exit;
  end;
  
// 根据一个TCheckBox控件的选择情况来决定使用锁定模式还是非锁定模式
   if  ckbxB.Checked then
   tm :
=   1   // 非锁定模式
   else  tm : =   0 ;   // 锁定模式
  ioctlsocket(FSock,FIONBIO,tm);

  SockAdd_In.sin_family :
=  PF_INET;
  SockAdd_In.sin_port :
=  htons( 5151 );
  SockAdd_In.sin_addr.S_addr :
=  htonl(INADDR_ANY);
  
// 绑定
  ErrorCode : =  bind(FSock,SockAdd_In, sizeof (SockAdd_In));
  
if  ErrorCode  =  SOCKET_ERROR then
  begin
    showmessage(Format(
' %s;ErrorCode:%d ' ,[ ' 绑定失败: ' ,WSAGetLastError]) );
    Exit;
  end;
  
// 置为监听模式
  listen(FSock, 5 );

    
// 用一个循环来反复判断是否有客户端请求,如果存在请求就创建一个用来接受数据的读取线程
   while   true   do
  begin
    AddSize :
=   sizeof (Add);
    AcceptSock :
=  accept(FSock,@Add,@AddSize);
    
if  AcceptSock  <>  INVALID_SOCKET then
    TSockReadThread.Create(AcceptSock,Memo1);
    Application.ProcessMessages;

  end;

end;

{ TSockReadThread }

constructor TSockReadThread.Create(pSocket: TSocket; mm: TMemo);
begin
   FMemo :
=  mm;
   FSocket :
=  pSocket;
   inherited Create(
false );
end;

procedure TSockReadThread.Execute;
var
  ret : integer;
  FdSet : TFDSet;
  TimeVal : TTimeVal;
begin
  inherited;
  FreeOnTerminate :
=  True;
  
while  not terminated  do
  begin
   
{ FD_ZERO(FdSet);
    FD_SET(FSocket,FdSet);
    TimeVal.tv_sec :
= 0;
    TimeVal.tv_usec :
= 500;
    
if (select(0,@fdSet,nil,nil,@TimeVal) > 0) and
      not terminated then
    begin  }

      ret :
=  recv(FSocket,fbuf, 256 , 0 );
      
if  ret  >   0  then Synchronize(GetResult)
      
else  Break;
   
//  end;
  end;
end;

procedure TSockReadThread.GetResult;
begin
  FMemo.Lines.Add(FBuf);
end;
// 创建客户端,并发送数据
procedure TForm1.Button2Click(Sender: TObject);
var
  ErrorCode : integer;
  buf : array[
0 .. 10 ] of Char;
  SockAdd_Inc : TSockAddrIn;
  SkC : TSocket;
begin
  skc :
=  socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
if  skc  =  SOCKET_ERROR then
  begin
    showmessage(
' 创建失败 ' );
    Exit;
  end;
  SockAdd_Inc.sin_family :
=  PF_INET;
  SockAdd_Inc.sin_port :
=  htons( 5151 );
  SockAdd_Inc.sin_addr.S_addr :
=  inet_addr(pchar( ' 127.0.0.1 ' ));
  
// 连接
  connect(skc,SockAdd_Inc, sizeof (SockAdd_Inc));
  
// 发送数据
  buf : = ' wudi_1982 ' ;
  send(SkC,buf,
10 * sizeof ( char ), 0 );
  
// 断开连接
  shutdown(skc,SD_SEND);
  closesocket(skc);

end;

initialization
  StartUp;
finalization
  WSACleanup;

end.


       在上面的例子中,首先通过点击一个按钮创建一个服务器,如果选择的是阻塞模式,你可以发现程序就想“死”了一样,这是阻塞作用产生的效果,因为上面例子调用accept函数的地方是在主线程中,而此时没有客户端发起连接,因此accept将无法返回,主线程被阻塞。这种情况下,你根本无法点击那个用来创建客户端并发送数据的按钮。然后再此执行程序,使用非阻塞模式,你会看到程序执行成功,创建客户端按钮可以执行。如果有兴趣,最好在两种模式下使用单步执行,来看以下效果,主要是关产accept函数执行的情况。当然,你还可以把用来接受客户端请求的那段代码封装到一个线程中去做,例如上面例子的读取线程。
       如果你仔细关产上面代码,你可以看到在读取线程中,有一段被我注释掉的程序,它的主体是select,它是干什么的呢?下面我们就进入Winsock i/o模式。

套接字I/O模型
      共有五种类型的套接字I/O模型,可让Winsock应用程序对I/O进行管理,它们包括: select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)以及completion port(完成端口)。因为本文的出发点是DELPHI中的TServerSocket控件,基于此控件的实现,在这里,我打算向大家解释主要解释select以及WSAAsyncSelectI/O模型。

select模型
      select(选择)模型是Winsock中最常见的I/O模型。之所以称其为“ select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理!最初设计该模型时,主要面向的是某些使用Unix操作系统的计算机,它们采用的是Berkeley套接字方案。select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。由于Winsock 1.1向后兼容于Berkeley套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。
       利用select函数,我们判断套接字上是否存在数据,或者能否向一个套接字写入数据。之所以要设计这个函数,唯一的目的便是防止应用程序在套接字处于锁定模式中时,在一次I / O绑定调用(如send或recv)过程中,被迫进入“锁定”状态;同时防止在套接字处于非锁定模式中时,产生WSAEWOULDBLOCK错误。除非满足事先用参数规定的条件,否则select函数会在进行I/O操作时锁定。select的函数原型如下:
int select (
    int nfds,
    fd_set FAR * readfds,
    fd_set FAR * writefds,
    fd_set FAR * exceptfds,
    const struct timeval FAR * timeout
   );

       其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程序的兼容。大家可注意到三个fd_set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据(exceptfds)。从根本上说,fd_set数据类型代表着一系列特定套接字的集合。其中, readfds集合包括符合下述任何一个条件的套接字:
■ 有数据可以读入。
■ 连接已经关闭、重设或中止。
■ 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
■ 有数据可以发出。
■ 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
最后,exceptfds集合包括符合下述任何一个条件的套接字:
■ 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
■ 有带外(OOB)数据可供读取。

      例如,假定我们想测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合,再等待select函数完成。select完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。在三个参数中(readfds、writefds和exceptfds),任何两个都可以是空值( NULL);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;否则, select函数便没有任何东西可以等待。最后一个参数timeout对应的是一个指针,它指向一个timeval结构,用于
决定select最多等待I/O操作完成多久的时间。如timeout是一个空指针,那么select调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。对timeval结构的
定义如下:
  timeval = record
    tv_sec: Longint;
    tv_usec: Longint;
  end;

       其中,tv_sec字段以秒为单位指定等待时间;tv_usec字段则以毫秒为单位指定等待时间。若将超时值设置为( 0 , 0),表明select会立即返回,允许应用程序对select操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。select成功完成后,会在fd_set结构中,返回刚好有未完成的I / O操作的所有套接字句柄的总量。若超过timeval设定的时间,便会返回0。不管由于什么原因,假如select调用失败,都会返回SOCKET_ERROR。用select对套接字进行监视之前,在自己的应用程序中,必须将套接字句柄分配给一个集合,设置好一个或全部读、写以及例外fd_set结构。将一个套接字分配给任何一个集合后,再来调用select,便可知道一个套接字上是否正在发生上述的I / O活动。
        例如上面的例程,在阻塞模式下,我们将while 循环调用accept的那段代码做如下修改,执行一下,你会发现在阻塞模式下,刚才无法完成的动作现在可以了。

var
  FdSet : TFDSet;
  TimeVal : TTimeVal;
  ...
begin
  
// 前面的代码不便
     while   true   do
  begin
    FD_ZERO(FdSet);
    FD_SET(FSock,FdSet);
    TimeVal.tv_sec :
=   0 ;
    TimeVal.tv_usec :
=   500 ;
    
// 使用select函数
     if  (select( 0 ,@fdSet,nil,nil,@TimeVal)  >   0 ) then
    begin
    AddSize :
=   sizeof (Add);
    AcceptSock :
=  accept(FSock,@Add,@AddSize);
    
if  AcceptSock  <>  INVALID_SOCKET then
    TSockReadThread.Create(AcceptSock,Memo1);
    end;
    Application.ProcessMessages;  end;
end;

WSAAsyncSelect
      Winsock提供了一个有用的异步I/O模型。利用这个模型,应用程序可在一个套接字上,接收以Windows消息为基础的网络事件通知。具体的做法是在建好一个套接字后,调用WSAAsyncSelect函数。该模型最早出现于Winsock的1.1版本中,用于帮助应用程序开发者面向一些早期的16位Windows平台,适应其“落后”的多任务消息环境。应用程序仍可从这种模型中得到好处,特别是它们用一个标准的Windows例程(常称为“ winproc”),对窗口消息进行管理的时候。

消息通知
        要想使用WSAAsyncSelect模型,程序必须具备一个窗口,然后有消息循环系统,我们通常会自定义一个消息,然后调用WSAAsyncSelect函数将此消息投递到制定的窗口句柄中。
WSAAsyncSelect函数定义如下:
int WSAAsyncSelect (
    SOCKET s,
    HWND hWnd,
    unsigned int wMsg,
    long lEvent
   );

       其中, s参数指定的是我们感兴趣的那个套接字。hWnd参数指定的是一个窗口句柄,它对应于网络事件发生之后,想要收到通知消息的那个窗口或对话框。wMsg参数指定在发生网络事件时,打算接收的消息。该消息会投递到由hWnd窗口句柄指定的那个窗口。最后一个参数是lEvent,它指定的是一个位掩码,对应于一系列网络事件的组合,应用程序感兴趣的便是这一系列事件。大多数应用程序通常感兴趣的网络事件类型包括: FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT和FD_CLOSE。当然,到底使用FD_ACCEPT,还是使用FD_CONNECT类型,要取决于应用程序的身份到底是一个客户机呢,还是一个服务器。如应用程序同时对多个网络事件有兴趣,只需对各种类型执行一次简单的按位O R(或)运算,然后将它们分配给lEvent就可以了。
       特别要注意的是,多个事件务必在套接字上一次注册!另外还要注意的是,一旦在某个套接字上允许了事件通知,那么以后除非明确调用closesocket命令,或者由应用程序针对那个套接字调用了WSAAsyncSelect,从而更改了注册的网络事件类型,否则的话,事件通知会永远有效!若将lEvent参数设为0,效果相当于停止在套接字上进行的所有网络事件通知。
       若应用程序针对一个套接字调用了WSAAsyncSelect ,那么套接字的模式会从“锁定”自动变成“非锁定”,我们在前面已提到过这一点。

事件类型        含义
FD_READ 应用程序想要接收有关是否可读的通知,以便读入数据
FD_WRITE 应用程序想要接收有关是否可写的通知,以便写入数据
FD_OOB 应用程序想接收是否有带外( O O B)数据抵达的通知
FD_ACCEPT 应用程序想接收与进入连接有关的通知
FD_CONNECT 应用程序想接收与一次连接或者多点join操作完成的通知
FD_CLOSE 应用程序想接收与套接字关闭有关的通知
FD_QOS 应用程序想接收套接字“服务质量”(Q o S)发生更改的通知
FD_GROUP_QOS 应用程序想接收套接字组“服务质量”发生更改的通知(现在没什么用处,为未来套接字组的使用保留)
FD_ROUTING_INTERFACE_CHANGE 应用程序想接收在指定的方向上,与路由接口发生变化的通知
FD_ADDRESS_LIST_CHANGE应用程序想接收针对套接字的协议家族,本地地址列表发生变化的通知
       应用程序在一个套接字上成功调用了WSAAsyncSelect 之后,应用程序会在与hWnd窗口句柄参数对应的窗口例程中,以Windows消息的形式,接收网络事件通知。
       就我们的情况来说,感兴趣的是WSAAsyncSelect 调用中定义的消息。wParam参数指定在其上面发生了一个网络事件的套接字。假若同时为这个窗口例程分配了多个套接字,这个参数的重要性便显示出来了。在lParam参数中,包含了两方面重要的信息。其中, lParam的低字(低位字)指定了已经发生的网络事件,而lParam的高字(高位字)包含了可能出现的任何错误代码。
        网络事件消息抵达一个窗口例程后,应用程序首先应检查lParam的高字位,以判断是否在套接字上发生了一个网络错误。若应用程序发现套接字上没有产生任何错误,接着便应调查到底是哪个网络事件类型,造成了这条Windows消息的触发—具体的做法便是读取lParam之低字位的内容。此时可使用另一个特殊的宏:WSAGetSelectEvent(在DELPHI中,它以一个函数的形式存在),用它返回lParam的低字部分。

一个例子:

{
    作者:wudi_1982
    联系方式:wudi_1982@hotmail.com
    转载请著名出处
}

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls,winsock;

const
  
// 自定义一个消息
  WM_MySockMessage = wm_user  +  $ 0101 ;

type
  TForm1 
=   class (TForm)
    Button1: TButton;
    ListBox1: TListBox;
    mmSRec: TMemo;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  
private
    
// 消息处理过程
    procedure SockMessage(var msg : TMessage);message WM_MySockMessage;
  
public
    
{ Public declarations }
  end;

var
  Form1: TForm1;
  WSAData : TWSAData;

implementation

{$R *.dfm}

procedure StartUp;
var
  ErrorCode : integer;
begin
  ErrorCode :
=  WSAStartup($ 0101 , WSAData);
  
if  ErrorCode  <>   0  then
  begin
    ShowMessage(
' 加载winsock dll失败 ' );
    exit;
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  ErrorCode : integer;
  SockAdd_In : TSockAddrIn;
  FSock : TSocket;
begin
  
// 建立套接字
  FSock : =  socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
if  FSock  =  SOCKET_ERROR then
  begin
    showmessage(Format(
' %s;ErrorCode:%d ' ,[ ' 套接字建立失败 ' ,WSAGetLastError]) );
    Exit;
  end;

  SockAdd_In.sin_family :
=  PF_INET;
  SockAdd_In.sin_port :
=  htons( 5150 );
  SockAdd_In.sin_addr.S_addr :
=  htonl(INADDR_ANY);
  ErrorCode :
=  bind(FSock,SockAdd_In, sizeof (SockAdd_In));
  
if  ErrorCode  =  SOCKET_ERROR then
  begin
    showmessage(Format(
' %s;ErrorCode:%d ' ,[ ' 绑定套接字失败 ' ,WSAGetLastError]) );
    Exit;
  end;
  
// 注意这里
  WSAAsyncSelect(FSock,Form1.Handle ,WM_MySockMessage,FD_READ or FD_ACCEPT);

  
// 进入监听状态
  listen(FSock, 5 );
end;

// 对自定义消息的处理过程
procedure TForm1.SockMessage(var msg: TMessage);
var
  AddSize,size,rev : integer;
  Acceptsk : TSocket;
  Sockin,Add : TSockAddrIn;
  buf : array[
0 .. 255 ] of  char ;
begin
   
case  WSAGetSelectEvent(msg.LParam) of
     FD_READ : begin
// 如果有数据可以读取,调用recv
        rev : =  recv(msg.WParam,buf, 256 , 0 );
        
if   rev  >   0  then
        begin
          Form1.mmSRec.Lines.Add(buf);
        end;
     end;
     FD_ACCEPT : begin
       
// 存在连接请求,调用accept
       AddSize : =   sizeof (Add);
       Acceptsk :
=  accept(msg.WParam,@Add,@AddSize);
       
if  Acceptsk  <>  INVALID_SOCKET then
       begin
          FillChar(SockIn, SizeOf(SockIn), 
0 );
          size :
=  SizeOf(SockIn);
          
// 将请求连接的客户端地址添加到一个listbox
          getpeername(Acceptsk,SockIn,size);
          Form1.ListBox1.Items.Add(inet_ntoa(SockIn.sin_addr));

       end;
     end;

   end;
end;

// 客户端代码
procedure TForm1.Button2Click(Sender: TObject);
var
  ErrorCode : integer;
  buf : array[
0 .. 10 ] of Char;
  SockAdd_Inc : TSockAddrIn;
  SkC : TSocket;
begin

  skc :
=  socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
if  skc  =  SOCKET_ERROR then
  begin
    showmessage(
' 套接字建立失败 ' );
    Exit;
  end;
  SockAdd_Inc.sin_family :
=  PF_INET;
  SockAdd_Inc.sin_port :
=  htons( 5150 );
  SockAdd_Inc.sin_addr.S_addr :
=  inet_addr(pchar( ' 192.168.1.3 ' ));
  
// 连接
  connect(skc,SockAdd_Inc, sizeof (SockAdd_Inc));
  
// 发送数据
  buf : = ' wudi_1982 ' ;
  send(SkC,buf,
10 * sizeof ( char ), 0 );
  shutdown(skc,SD_SEND);
  closesocket(skc);

end;

initialization
  StartUp;
finalization
  WSACleanup;
end.

I/O模型的问题
       现在,对于如何挑选最适合自己应用程序的I/O模型,大家心中可能还没什么数。前面已经提到,每种模型都有自己的优点和缺点。同开发一个简单的锁定模式应用相比(运行许多服务线程),其他每种I/O模型都需要更为复杂的编程工作。因此,针对客户机和服务器应用的开发,我们分别提供了下述建议。
1. 客户机的开发
   若打算开发一个客户机应用,令其同时管理一个或多个套接字,那么建议采用重叠I/O或WSAEventSelect模型(关于这两种模型,以后会有涉及),以便在一定程度上提升性能。然而,假如开发的是一个以Windows为基础的应用程序,要进行窗口消息的管理,那么WSAAsyncSelect模型恐怕是一种最好的选择,因为WSAAsyncSelect本身便是从Windows消息模型借鉴来的。若采用这种模型,我们的程序一开始便具备了处理消息的能力。
2. 服务器的开发
若开发的是一个服务器应用,要在一个给定的时间,同时控制几个套接字,建议大家采用重叠I/O模型,这同样是从性能出发点考虑的。但是,如果预计到自己的服务器在任何给定的时间,都会为大量I/O请求提供服务,便应考虑使用I/O完成端口模型,从而获得更好的性能。
 

评论 3 您还未登录,请先 登录 后发表或查看评论
本例子就是Delphi中如何利用Socket编写通信程序。 计算机网络是由系列网络通信协议组成,其中核心协议是传输层TCP/IPUDP协议。TCP是面向连接,通信双方保持条通路,好比目前电话线,使用telnet登陆BBS,用就是TCP协议;UDP是无连接,通信双方都不保持对方状态,浏览器访问Internet时使用HTTP协议就是基于UDP协议。TCPUDP协议都非常复杂,尤其是TCP协议,为了保证网络传输正确性有效性,必须进行系列复杂纠错排序等处理。   Socket是建立在传输层协议(主要是TCPUDP)上种套接字规范,最初是由美国加州Berkley大学提出,它定义两台计算机间进行通信规范也是编程规范,如果说两台计算机是利用个“通道“进行通信,那么这个“通道“两端就是两个套接字。套接字屏蔽了底层通信软件具体操作系统差异,使得任何两台安装了TCP协议软件实现了套接字规范计算机之间通信成为可能。   微软Windows Socket规范(简称winsock)对Berkley套接字规范进行了扩展,利用标准Socket方法,可以同任何平台上Socket进行通信;利用其扩展,可以更有效地实现在Windows平台上计算机间通信。在Delphi中,其底层Socket也应该是WindowsSocketSocket减轻了编写计算机间通信软件难度,但总说来还是相当复杂点在后面具体会讲到;Inprise在Delphi中对Windows Socket进行了有效封装,使得用户可以很方便地编写网络通信程序。 本例子就是Delphi中如何利用Socket编写通信程序。
delphi socket call php socket 例子,可根据需要扩展写成聊天室、网站助理类似淘宝助理,有订单提醒。 <?php //确保在连接客户端时不会超时 set_time_limit(0); $port = 10081 ; $ip = '192.168.1.102'; // create a streaming socket, of type TCP/IP $sock = socket_create ( AF_INET , SOCK_STREAM , SOL_TCP ); // set the option to reuse the port socket_set_option ( $sock , SOL_SOCKET , SO_REUSEADDR , 1 ); // "bind" the socket to the address to "localhost", on port $port // so this means that all connections on this port are now our resposibility to send/recv data, disconnect, etc.. socket_bind ( $sock , $ip , $port ); // start listen for connections socket_listen ( $sock ); // create a list of all the clients that will be connected to us.. // add the listening socket to this list $clients = array( $sock ); while ( true ) { // create a copy, so $clients doesn't get modified by socket_select() $read = $clients ; // get a list of all the clients that have data to be read from // if there are no clients with data, go to next iteration if ( socket_select ( $read , $write = NULL , $except = NULL , 0 ) < 1 ) continue; // check if there is a client trying to connect if ( in_array ( $sock , $read )) { // accept the client, and add him to the $clients array $clients [] = $newsock = socket_accept ( $sock ); // send the client a welcome message socket_write ( $newsock , "这是delphi(客户端) socket 与 PHP_socket(服务器 通信例子 测试,交流QQ:410578660。 but ill make an exception :)\n" . "There are " .( count ( $clients ) - 1 ). " client(s) connected to the server\n" ); socket_getpeername ( $newsock , $ip ); echo "New client connected: { $ip } \n" ; // remove the listening socket from the clients-with-data array $key = array_search ( $sock , $read ); unset( $read [ $key ]); } // loop through all the clients that have data to read from foreach ( $read as $read_sock ) { // read until newline or 1024 bytes // socket_read while show errors when the client is disconnected, so silence the error messages $data = @ socket_read ( $read_sock , 1024 , PHP_NORMAL_READ ); // check if the client is disconnected if ( $data === false ) { // remove client for $clients array $key = array_search ( $read_sock , $clients ); unset( $clients [ $key ]); echo "client disconnected.\n" ; // continue to the next client to read from, if any continue; }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

wudi_1982

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值