大家好,欢迎来到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 服务器端
界面设计如下图:
界面比较简单,组件属性也基本上不需要设置,主要设置各个组件的 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 客户端
客户端界面设计如下图所示:
客户端主要实现 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