「12.网络编程」5.Udp Socket 编程

「12.网络编程」5.Udp Socket 编程5.Udp Socket 编程在采用 TCP/IP 网络协议的应用中,网络应用程序之间的主要通信方式时客户/服务器(C/S)模式。即服务器在某端

大家好,欢迎来到IT知识分享网。

5.Udp Socket 编程

在采用 TCP/IP 网络协议的应用中,网络应用程序之间的主要通信方式时客户/服务器(C/S)模式。即服务器在某端口监听客户端的请求,而客户端向服务器发出服务请求,服务器在收到客户端请求后,提供相应的服务。在前面几节的内容中,我们实现了客户/服务器模式的应用程序开发,本节我们介绍基于数据报的 UDP 通信模式,在 Udp Socket 编程中,主要通过 Indy 组件的 TIdUDPServer 和 TI的UDPClient 组件进行开发。

5.1 TIdUDPServer 组件

该组件位于 Indy Server 页,用于实现基于 UDP 的服务器通信。

5.1.1 TIdUDPServer 组件的主要属性

  • Bindings
  • 服务器分配的 Socket 句柄,通过 TIdUDPListenerThread 来访问 Socket 句柄和协议栈提供的底层方法。
  • DefaultPort

用来标识服务器创建的新的 Socket 绑定的端口,新的连接用该端口号来进行监听。

  • Active

激活并使服务端启动监听

  • Binding

用于发送和接收数据的 Socket 绑定。

  • BroadcastEnabled

是否允许对网络上的所有计算机广播数据报

  • BufferSize

指定能通过 Binding 发送和接收的最大 UDP 包,默认数据包的最大值为 8192

  • ReceiveTimeout

用于标识 ReceiveString 方法等待的最长时间,单位为毫秒

  • LocalName

用户计算机的系统名称

5.1.2 TIdUDPServer 组件的主要方法

  • Broadcast

原型格式:

procedure Broadcast( const AData: string; const APort: integer );

向网络中所有的计算机广播数据,其中 AData 为数据,APort指定计算机的端口号

  • ReceiveBuffer

原型格式:

function ReceiveBuffer( var ABuffer: TIdBytes; var VPeerIP: string; var VPeerPort: integer; AMSec: Integer = IdTimeoutDefault ): integer;

从 VPeerIP 和 VPeerPort 指定的计算机中读取数据到 ABuffer 中。AMSec 参数指定超时时长,默认为 IdTimeoutDefault ,

IdTimeoutDefault = -1;
  • Send

原型格式:

procedure Send( AHost: string; const APort: Integer; const AData: string );

将 AData 中的数据发送到 AHost 和 APort 指定的计算机

  • SendBuffer

原型格式:

procedure SendBuffer( AHost: string; const APort: Integer; const ABuffer: TIdBytes ); 

将 ABuffer 中的数据发送到 AHost 和 APort 指定的计算机

  • BeginWork

原型格式:

procedure BeginWork( AWorkMode: TWorkMode; const ASize: Int64 = 0 ); 

用于触发 OnBeginWork 事件,可以被嵌套调用,但只在第一次调用时触发事件。

其中:AWorkMode 参数表示连接的工作模式,取值为:wmRead | wmWrite ; ASize 参数表示读或写的字节数。

  • DoWork

原型格式:

procedure DoWork( AWorkMode: TWorkMode; const ACount: Int64 );

用于触发 OnWork 事件,在调用 DoWork 过程之前必须先调用 BeginWork 过程,否则 DoWork 过程将不会产生任何效果。

  • EndWork

原型格式:

procedure EndWork( AWorkMode: TWorkMode );

用于触发 OnEndWork 事件,该方法可以嵌套调用,但只有在第一次调用时会触发事件。

5.1.3 TIdUDPServer 组件的主要事件

  • OnUDPRead

当数据从 Scoket 中读取出来可以被服务器使用时触发

  • OnStatus

当前连接状态发生改变时触发

5.2 TIdUDPClient 组件

该组件位于 Indy Clients 页,用于实现基于 UDP 的客户端通信。

5.2.1 TIdUDPClient 组件的主要属性

  • Host

远程计算机的地址

  • Port

远程计算机的端口

  • ReceiveTimeOut

表示接收数据的最大时长,单位为毫秒数

  • Active

表示 Socket 绑定是否已分配

  • Binding

用于发送和接收数据的 Socket 绑定

  • BroadcastEnabled

指定 Socket 绑定是否可用执行广播传输

  • BufferSize

表示传输的 UDP 数据包的最大字节数,默认为8192

  • LocalName

本地计算机名

5.2.2 TIdUDPClient 组件的主要方法

  • Send

原型格式:

procedure Send( AData: string );

将 AData 中的数据传输给远程计算机。

  • SendBuffer

原型格式:

procedure SendBuffer( AHost: string; const APort: Integer; const ABuffer: TIdBytes );

传输数据给远程计算机

  • Broadcast

原型格式:

procedure Broadcast( const AData: string; const APort: integer );

向网络中的所有计算机广播数据

  • ReceiveBuffer

原型格式:

function ReceiveBuffer( var ABuffer: TIdBytes; var VPeerIP: string; var VPeerPort: integer; AMSec: Integer = IdTimeoutDefault ): integer;

从 VPeerIP 和 VPeerPort 参数指定的远程计算机读取数据到 ABuffer 缓冲区

  • ReceiveString

原型格式:

function ReceiveString( var VPeerIP: string; var VPeerPort: integer; const AMSec: Integer = IdTimeoutDefault ): string;

从 VPeerIP 和 VPeerPort 参数指定的远程计算机读取字符串数据

  • BeginWork

原型格式:

procedure BeginWork( AWorkMode: TWorkMode; const ASize: Int64 = 0 );

用于触发 OnBeginWork 事件,同时维护读写堵塞操作的数量,以及初始读写操作的大小。

  • DoWork

原型格式:

procedure DoWork( AWorkMode: TWorkMode; const ACount: Int64 ); 

用于触发 OnWork 事件,在调用该方法之前必须调用 BeginWork 过程,否则该过程将不会产生任何效果。

  • EndWork

原型格式:

procedure EndWork( AWorkMode: TWorkMode );

用于触发 OnEndWork 事件,该方法可以嵌套调用,但是 OnEndWork 事件仅在第一次调用时触发。

5.2.3 TIdUDPClient 组件的主要事件

  • OnStatus

当前连接状态改变时触发

5.3 Indy UDP Socket 编程示例

本节采用 Indy 10 提供的组件 TIdUDPServer 和 TIdUDPClient 来演示 TCP Socket 编程。示例仍然采用前面的,只是使用 UDP Socket 来实现。

示例:客户端定时实时检测所在机器的屏幕分辨率上行到服务端,服务端接收到数据后,根据其屏幕分辨率随机生成一个坐标并下发给客户端,客户端将应用程序的窗体位置放置到相应的坐标上。

5.3.1 服务器端

界面设计如下图:

「12.网络编程」5.Udp Socket 编程

界面比较简单,组件属性也基本上不需要设置,主要设置各个组件的 Name 属性,在此不做说明。服务器端代码相对比较简单,只需要实现 TIdUDPServer 组件的 OnUDPRead 和 OnUDPException 事件即可。

首先确定传输的数据结构:

TCommBlock = Record // 客户端上传: W-屏幕宽度, H-屏幕高度, E-结束; // 服务端下发: X-水平坐标, Y-垂直坐标, E-结束; Part: String[1]; Desc: String[16]; // 描述 Value: Integer; // 数据值 end; 

“启动”按钮的单击事件:

procedure TForm1.StartButtonClick(Sender: TObject); begin IdUDPServe.DefaultPort:=PortSpinEdit.Value; IdUDPServe.Active:=True; PortSpinEdit.Enabled:=False; StartButton.Enabled:=False; end; 

实现 TIdUDPServer 组件的 OnUDPException 事件:

procedure TForm1.IdUDPServeUDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage: String; const AExceptionClass: TClass); begin LogMemo.Lines.Add(AMessage); end; 

实现 TIdUDPServer 组件的 OnUDPRead 事件:

procedure TForm1.IdUDPServeUDPRead(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); var Ip: String; Port: Integer; CommBlock: TCommBlock; W, H, X, Y: Integer; begin Ip:=AThread.Binding.PeerIP; Port:=AThread.Binding.PeerPort; BytesToRaw(AData, CommBlock, SizeOf(CommBlock)); LogMemo.Lines.Add(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); if CommBlock.Part = 'W' then W:=CommBlock.Value; if CommBlock.Part = 'H' then H:=CommBlock.Value; if CommBlock.Part = 'E' then begin Randomize; X:=Random(W); Y:=Random(H); // 发送水平坐标 CommBlock.Part:='X'; CommBlock.Desc:='水平坐标'; CommBlock.Value:=X; LogMemo.Lines.Add(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); AThread.Server.Sendbuffer(Ip, Port, RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送垂直坐标 CommBlock.Part:='Y'; CommBlock.Desc:='垂直坐标'; CommBlock.Value:=Y; LogMemo.Lines.Add(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); AThread.Server.Sendbuffer(Ip, Port, RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送结束标志 CommBlock.Part:='E'; CommBlock.Desc:='结束'; CommBlock.Value:=0; LogMemo.Lines.Add(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); AThread.Server.Sendbuffer(Ip, Port, RawToBytes(CommBlock, SizeOf(CommBlock))); end; end; 

在上面的代码中,使用 AThread.Server.Sendbuffer 方法来将返回的数据发送给客户端。

5.3.2 客户端

客户端界面设计如下图所示:

「12.网络编程」5.Udp Socket 编程

客户端主要实现 TTimer 组件的 OnTimer 事件,用于发送数据,接收数据仍然采用启动一个单独的接收数据线程来实现。

声明与服务端通信的数据结构:

TCommBlock = Record // 客户端上传: W-屏幕宽度, H-屏幕高度, E-结束; // 服务端下发: X-水平坐标, Y-垂直坐标, E-结束; Part: String[1]; Desc: String[16]; // 描述 Value: Integer; // 数据值 end; 

声明数据读取线程:

TClientHandleThread = class(TTHread) private Logs: String; procedure HandleLog; procedure HandlePos; protected procedure Execute; Override; end; 

声明应用程序使用的变量:

var Form1: TForm1; X, Y: Integer; Ip: String; Port: Integer; ClientHandleThread: TClientHandleThread; 

“开始”按钮的单击事件:

procedure TForm1.StartButtonClick(Sender: TObject); begin if HostEdit.Text = '' then begin Application.MessageBox('请设置地址!', '提示'); Exit; end; HostEdit.Enabled:=False; PortSpinEdit.Enabled:=False; StartButton.Enabled:=False; Ip:=HostEdit.Text; Port:=PortSpinEdit.Value; IdUDPClien.Host:=Ip; IdUDPClien.Port:=Port; SendTimer.Interval:=1000*10; SendTimer.Enabled:=True; // 启动读取线程 ClientHandleThread:=TClientHandleThread.Create(True); ClientHandleThread.FreeOnTerminate:=True; ClientHandleThread.Start; end;

数据读取线程的具体实现我们在后面介绍。

通过 TTimer 的 OnTimer 事件实现数据发送:

procedure TForm1.SendTimerTimer(Sender: TObject); var CommBlock: TCommBlock; w, h: Integer; begin // 定时器 w:=Screen.Width; h:=Screen.Height; LogMemo.Lines.Add('分辨率: ' + inttostr(w) + ' * ' + inttostr(h)); // 发送宽度 CommBlock.Part:='W'; CommBlock.Desc:='宽度'; CommBlock.Value:=w; IdUDPClien.SendBuffer(RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送高度 CommBlock.Part:='H'; CommBlock.Desc:='高度'; CommBlock.Value:=h; IdUDPClien.SendBuffer(RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送结束标志 CommBlock.Part:='E'; CommBlock.Desc:='结束'; CommBlock.Value:=0; IdUDPClien.SendBuffer(RawToBytes(CommBlock, SizeOf(CommBlock))); end; 

数据读取线程的实现:

procedure TClientHandleThread.HandleLog; begin Form1.LogMemo.Lines.Add(Logs); Logs:=''; end; procedure TClientHandleThread.HandlePos; begin Form1.Left:=X; Form1.Top:=Y; end; procedure TClientHandleThread.Execute; var CommBlock: TCommBlock; bytes: TIdBytes; begin while not Self.Terminated do begin SetLength(bytes, Form1.IdUDPClien.BufferSize); Form1.IdUDPClien.ReceiveBuffer(bytes); BytesToRaw(bytes, CommBlock, SizeOf(CommBlock)); Logs := Logs + CommBlock.Desc + ': ' + inttostr(CommBlock.Value); Synchronize(@HandleLog); if CommBlock.Part = 'X' then X:=CommBlock.Value; if CommBlock.Part = 'Y' then Y:=CommBlock.Value; if CommBlock.Part = 'E' then Synchronize(@HandlePos); end; end;

在上面的代码中,通过 TIdUDPClient 的 ReceiveBuffer 方法读取服务端返回的数据,在使用该方法读取之前,必须对 TIdBytes 进行初始化,这个是与 TIdTCPClient 组件不同的地方,我们来看一下 TIdBytes 类型:

TIdBytes = TBytes;

再来看一下 TBytes 类型:

TBytes = array of Byte;

也就是说,TIdBytes 是一个动态字节数组,所以,在读取数据之前,要先初始化该变量,代码如下:

SetLength(bytes, Form1.IdUDPClien.BufferSize);

以上内容就是使用 Indy 组件进行 UDP Socket 编程的实现过程,在代码中,我没有进行异常处理,在实践开发中,必须对数据发送和接收进行异常处理。

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/54730.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信