网站首页 > 技术文章 正文
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 编程的实现过程,在代码中,我没有进行异常处理,在实践开发中,必须对数据发送和接收进行异常处理。
猜你喜欢
- 2024-12-16 FFmpeg功能命令集合(超详细) ffmpeg 命令大全
- 2024-12-16 1.2 动态链接库与API 动态链接库原理
- 2024-12-16 QT开发经验详解 qt开发gui
- 2024-12-16 16k Star!一个开源的命令行视频播放器
- 2024-12-16 Qt多线程编程之QThread qt多线程例子
- 2024-12-16 「某CMS漏洞」SQL注入漏洞分析 sql注入漏洞代码
- 2024-12-16 「3.Lazarus执行外部程序」3.TProcess
- 2024-12-16 Qt开发-Qt中的多线程编程 qt如何写多个线程
- 2024-12-16 k8s系列-06-containerd的基本操作
- 2024-12-16 实例讲解Simulink建立模型的版本管理
- 最近发表
- 标签列表
-
- cmd/c (90)
- c++中::是什么意思 (84)
- 标签用于 (71)
- 主键只能有一个吗 (77)
- c#console.writeline不显示 (95)
- pythoncase语句 (88)
- es6includes (74)
- sqlset (76)
- apt-getinstall-y (100)
- node_modules怎么生成 (87)
- chromepost (71)
- flexdirection (73)
- c++int转char (80)
- mysqlany_value (79)
- static函数和普通函数 (84)
- el-date-picker开始日期早于结束日期 (76)
- js判断是否是json字符串 (75)
- c语言min函数头文件 (77)
- asynccallback (87)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- java (73)
- js数组插入 (83)
- mac安装java (72)
- 无效的列索引 (74)