delphi 局域网用tcp还是udp(TcpSocket编程之Delphi与其他语言的字节码通信)
delphi 局域网用tcp还是udp(TcpSocket编程之Delphi与其他语言的字节码通信)该类定义在 IdBuffer 单元中,定义如下:TBytes = array of Byte;可见是一个字节数组。由于是不同语言之间的数据通信,显然,在 Delphi 中使用 Record 进行通信的可能性就不大了。所以,在 Delphi 中采用 Indy 进行数据通信的情况下,主要会使用到如下一些数据类型:该类型被定义在 IdGlobal 单元中,定义如下:TIdBytes = TBytes;TBytes 定义如下:
关键字:Tcp Scoket、Delphi、Indy、Python、Twisted
对于 Tcp Socket 编程,异种语言之间的通信在日常开发中经常会用到。今天,我们通过 Delphi 和 Python 语言来做一个 Socket 通信的示例。通信数据采用字节码(也就是字节数组)形式来直接传输。
客户端:Delphi、Indy
服务端:Python、Twisted
由于是不同语言之间的数据通信,显然,在 Delphi 中使用 Record 进行通信的可能性就不大了。所以,在 Delphi 中采用 Indy 进行数据通信的情况下,主要会使用到如下一些数据类型:
- TIdBytes
- TIdBuffer
该类型被定义在 IdGlobal 单元中,定义如下:
TIdBytes = TBytes;
TBytes 定义如下:
TBytes = array of Byte;
可见是一个字节数组。
8.2 TIdBuffer该类定义在 IdBuffer 单元中,定义如下:
TIdBuffer = class(TIdBaseObject);
TIdBuffer 是一个 TObject 子类,它实现了一个用于 Indy 库中输入和输出操作的缓冲区。TIdBuffer用作通信层的读和/或写缓冲器。TIdBuffer 针对在缓冲区末尾添加数据和从缓冲区开始提取数据进行了优化。
TIdBuffer 隔离了在 Indy 库支持的平台上实现的内存分配和指针操作之间的差异。
TIdBuffer实现了用于管理缓冲区类实例的大小、增长和编码的属性,包括:
Property |
Usage |
Capacity |
为缓冲区分配的最大尺寸。 |
Encoding |
用于添加到缓冲区的字符串值。 |
GrowthFactor |
分配额外缓冲区存储的阈值。 |
Size |
缓冲区存储中的字节数。 |
AsString |
作为字符串数据类型的缓冲区内容。 |
在开发中,我们主要会使用其 Write 方法和 Extract 系列方法。
Write 方法:
- TIdBuffer.Write (Byte Integer)
- TIdBuffer.Write (Cardinal Integer)
- TIdBuffer.Write (Int64 Integer)
- TIdBuffer.Write (TIdBytes Integer)
- TIdBuffer.Write (TIdIPv6Address Integer)
- TIdBuffer.Write (TIdStream Integer)
- TIdBuffer.Write (Word Integer)
- TIdBuffer.Write (string TIdEncoding Integer)
Extract 系列方法:
- Extract
- ExtractToByte
- ExtractToBytes
- ExtractToCardinal
- ExtractToIdBuffer
- ExtractToInt64
- ExtractToIPv6
- ExtractToStream
- ExtractToWord
具体可以参考 Indy 的帮助文档。
8.3 示例阐述示例:客户端定时实时检测所在机器的屏幕分辨率上行到服务端,服务端接收到数据后,根据其屏幕分辨率随机生成一个坐标并下发给客户端,客户端将应用程序的窗体位置放置到相应的坐标上。
- 当发送屏幕宽度时,回复 x 坐标;
- 当发送屏幕高度时,回复 y 坐标;
- 当发送结束时,回复结束;客户端收到结束时更改窗体位置;
通信协议:
8.4 服务端服务端采用 Python 及其 Twisted 框架开发,业务逻辑也比较简单,所以,不做赘述,代码如下:
# coding: utf-8
from twisted.internet.Protocol import Protocol
from twisted.internet.protocol import Factory
from twisted.internet.endpoints import TCP4ServerEndpoint
from twisted.internet import reactor
import struct
import random
class Device(Protocol):
def connectionMade(self):
print('Connected from %s:%s.' % (self.transport.getPeer().host self.transport.getPeer().port))
self.transport.write('$Welcome!$'.encode('utf-8'))
def connectionLost(self reason):
print('Disconnected from %s:%s.' % (self.transport.getPeer().host self.transport.getPeer().port))
print(reason)
def dataReceived(self data):
# 接收数据
print('Recieved from %s:%s.' % (
self.transport.getPeer().host self.transport.getPeer().port))
# 输出十六进制表示
print(data.hex())
total_length = len(data)
print('命令长度: ' total_length)
# 初始化宽度和高度
width = height = 0
# 解包,取长度
start length tmp end = struct.unpack('>1s H {0}s 1s'.format(total_length - 4) data)
# 解包,取各个字段
start length part desc value end = struct.unpack('>1s H 1s {0}s H 1s'.format(length - 3) data)
print(start length part desc value end)
if part == b'W':
# 宽度处理
width = value
print(desc.decode('utf-8') width)
# 计算 x 坐标
x = random.randint(0 width)
command = struct.pack('>1s H 1s {0}s H 1s'.format(4) b'#' 4 3 b'X' b'left' x b'#')
print(command.hex())
# 下发指令
self.transport.write(command)
if part == b'H':
# 宽度处理
height = value
print(desc.decode('utf-8') height)
# 计算 y 坐标
y = random.randint(0 height)
command = struct.pack('>1s H 1s {0}s H 1s'.format(3) b'#' 3 3 b'Y' b'top' y b'#')
print(command.hex())
# 下发指令
self.transport.write(command)
if part == b'E':
# 结束处理
print(desc.decode('utf-8'))
command = struct.pack('>1s H 1s {0}s H 1s'.format(3) b'#' 3 3 b'E' b'end' 0 b'#')
print(command.hex())
# 下发结束指令
self.transport.write(command)
class DeviceFactory(Factory):
def buildProtocol(self addr):
print(addr) # addr为创建连接时客户端的地址
return Device()
endpoint = TCP4ServerEndpoint(reactor 9123)
endpoint.listen(DeviceFactory())
reactor.run()
注:代码仅用于测试,没有异常处理,多线程处理等
8.5 客户端客户端采用 Typhon 、Indy 组件进行开发,界面如下:
界面主要组件元素及其属性:
组件 |
属性 |
值 |
说明 |
TEdit |
name |
HostEdit |
主机 |
TSpinEdit |
name |
PortSpinEdit |
端口 |
TButton |
name |
ConnectButton |
连接按钮 |
caption |
连接 | ||
TButton |
name |
DisconnectButton |
断开按钮 |
caption |
断开 | ||
TMemo |
name |
DataMemo |
传输的数据内容显示 |
readonly |
True | ||
TIdTcpClient |
name |
IdTCPClient | |
TTimer |
name |
Timer1 | |
interval |
1000 |
客户端主要代码包括:TTimer 的定时事件发送数据和读取线程接收数据。
- TTimer 的定时事件发送数据
procedure TForm1.Timer1Timer(Sender: TObject);
var
w h: Integer;
bytes: TIdBytes;
begin
// 定时器
Count:=Count 1;
if Count > 99 then Count:=1;
w:=Screen.Width;
h:=Screen.Height;
if IdTCPClient.Connected then
try
if Count mod 3 = 1 then
begin
bytes:=BuildOrder('W' 'width' w);
IdTCPClient.IOHandler.Write(bytes);
DataMemo.Lines.Add('发送 width: ' inttostr(w));
end;
if Count mod 3 = 2 then
begin
bytes:=BuildOrder('H' 'height' h);
IdTCPClient.IOHandler.Write(bytes);
DataMemo.Lines.Add('发送 height: ' inttostr(h));
end;
if Count mod 3 = 0 then
begin
bytes:=BuildOrder('E' 'end' 0);
IdTCPClient.IOHandler.Write(bytes);
DataMemo.Lines.Add('发送 end: ' inttostr(0));
end;
except
end;
end;
在上面的代码中主要完成每次定时器触发事件,根据计数器 Count 的值来决定发送什么,发送数据时,调用了一个 BuildOrder 函数,该函数完成指令构建的过程,代码如下:
function BuildOrder(Part Desc: String; Value: UInt16): TIdBytes;
var
buffer: TIdBuffer;
bytes: TIdBytes;
l: UInt16;
begin
buffer:=TIdBuffer.Create;
l := length(Desc) 3;
buffer.Write(bytesof('#'));
buffer.Write(l);
buffer.Write(bytesof(Part));
buffer.Write(bytesof(Desc));
buffer.Write(Value);
buffer.Write(bytesof('#'));
buffer.ExtractToBytes(bytes -1 False);
buffer.Free;
Result:=bytes;
end;
构建指令时,主要采用了 TIdBuffer 来组装数据,然后导出 TIdBytes,在组装过程中,对于字符串可以使用 bytesof 函数获取其字节数据,返回值为字节数组。
- 读取线程接收数据
unit unitreadthread;
{$mode ObjFPC}{$H }
interface
uses
Classes SysUtils IdGlobal IdBuffer;
type
TReadThread = class(TTHread)
private
procedure AddTextToMainDataMemo;
procedure ChangeMainPos;
protected
procedure Execute; Override;
end;
var
info: String;
X Y: Integer;
implementation
uses unitmain;
procedure TReadThread.Execute;
var
bytes: TIdBytes;
buffer: TIdBuffer;
startflag endflag part desc: String;
length value: UInt16;
begin
X:=0;
Y:=0;
while not Self.Terminated do
begin
if not Form1.IdTCPClient.Connected then
Self.Terminate
else
try
Form1.IdTCPClient.IOHandler.ReadBytes(bytes -1 False);
buffer:=TIdBuffer.Create(bytes);
startflag := buffer.ExtractToString(1);
length := buffer.ExtractToUInt16(-1);
if length > 10 then continue;
part := buffer.ExtractToString(1);
desc := buffer.ExtractToString(length - 3);
value := buffer.ExtractToUInt16(-1);
endflag := buffer.ExtractToString(1);
info:= startflag ' ' inttostr(length) ' ' part ' ' desc ' ' inttostr(value) ' ' endflag;
Synchronize(@AddTextToMainDataMemo);
buffer.Free;
if part = 'X' then X:=value;
if part = 'Y' then Y:=value;
if part = 'E' then
begin
info:='改变窗体位置:(' inttostr(X) ' ' inttostr(Y) ')';
Synchronize(@AddTextToMainDataMemo);
Synchronize(@ChangeMainPos);
end;
except
end;
end;
end;
procedure TReadThread.AddTextToMainDataMemo;
begin
Form1.DataMemo.Lines.Add(info);
end;
procedure TReadThread.ChangeMainPos;
begin
Form1.Left:=X;
Form1.Top:=Y;
end;
end.
读取线程在收到数据时,首先通过 IdTCPClient.IOHandler.ReadBytes(bytes -1 False) 来获取接收到的数据,然后采用 TIdBuffer.Create 创建 TIdBuffer 的实例,其参数为 bytes,最后通过 TIdBuffer 的 Extract 系列函数分解其中的数据。
注:以上代码仅用于测试
8.6 运行效果