Tcp Socket 编程之Delphi与其他语言的字节码通信
yuyutoo 2024-12-31 15:36 4 浏览 0 评论
关键字:Tcp Scoket、Delphi、Indy、Python、Twisted
对于 Tcp Socket 编程,异种语言之间的通信在日常开发中经常会用到。今天,我们通过 Delphi 和 Python 语言来做一个 Socket 通信的示例。通信数据采用字节码(也就是字节数组)形式来直接传输。
客户端:Delphi、Indy
服务端:Python、Twisted
由于是不同语言之间的数据通信,显然,在 Delphi 中使用 Record 进行通信的可能性就不大了。所以,在 Delphi 中采用 Indy 进行数据通信的情况下,主要会使用到如下一些数据类型:
- TIdBytes
- TIdBuffer
8.1 TIdBytes
该类型被定义在 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!#39;.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 运行效果
相关推荐
- 史上最全的浏览器兼容性问题和解决方案
-
微信ID:WEB_wysj(点击关注)◎◎◎◎◎◎◎◎◎一┳═┻︻▄(页底留言开放,欢迎来吐槽)●●●...
-
- 平面设计基础知识_平面设计基础知识实验收获与总结
-
CSS构造颜色,背景与图像1.使用span更好的控制文本中局部区域的文本:文本;2.使用display属性提供区块转变:display:inline(是内联的...
-
2025-02-21 16:01 yuyutoo
- 写作排版简单三步就行-工具篇_作文排版模板
-
和我们工作中日常word排版内部交流不同,这篇教程介绍的写作排版主要是用于“微信公众号、头条号”网络展示。写作展现的是我的思考,排版是让写作在网格上更好地展现。在写作上花费时间是有累积复利优势的,在排...
- 写一个2048的游戏_2048小游戏功能实现
-
1.创建HTML文件1.打开一个文本编辑器,例如Notepad++、SublimeText、VisualStudioCode等。2.将以下HTML代码复制并粘贴到文本编辑器中:html...
- 今天你穿“短袖”了吗?青岛最高23℃!接下来几天气温更刺激……
-
最近的天气暖和得让很多小伙伴们喊“热”!!! 昨天的气温到底升得有多高呢?你家有没有榜上有名?...
- CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
-
之前也有写过CSS优惠券样式《CSS3径向渐变实现优惠券波浪造型》,这次再来温习一遍,并且将更为详细的讲解,从布局到具体样式说明,最后定义CSS变量,自定义主题颜色。布局...
- 你的自我界限够强大吗?_你的自我界限够强大吗英文
-
我的结果:A、该设立新的界限...
- 行内元素与块级元素,以及区别_行内元素和块级元素有什么区别?
-
行内元素与块级元素首先,CSS规范规定,每个元素都有display属性,确定该元素的类型,每个元素都有默认的display值,分别为块级(block)、行内(inline)。块级元素:(以下列举比较常...
-
- 让“成都速度”跑得潇潇洒洒,地上地下共享轨交繁华
-
去年的两会期间,习近平总书记在参加人大会议四川代表团审议时,对治蜀兴川提出了明确要求,指明了前行方向,并带来了“祝四川人民的生活越来越安逸”的美好祝福。又是一年...
-
2025-02-21 16:00 yuyutoo
- 今年国家综合性消防救援队伍计划招录消防员15000名
-
记者24日从应急管理部获悉,国家综合性消防救援队伍2023年消防员招录工作已正式启动。今年共计划招录消防员15000名,其中高校应届毕业生5000名、退役士兵5000名、社会青年5000名。本次招录的...
- 一起盘点最新 Chrome v133 的5大主流特性 ?
-
1.CSS的高级attr()方法CSSattr()函数是CSSLevel5中用于检索DOM元素的属性值并将其用于CSS属性值,类似于var()函数替换自定义属性值的方式。...
- 竞走团体世锦赛5月太仓举行 世界冠军杨家玉担任形象大使
-
style="text-align:center;"data-mce-style="text-align:...
- 学物理能做什么?_学物理能做什么 卢昌海
-
作者:曹则贤中国科学院物理研究所原标题:《物理学:ASourceofPowerforMan》在2006年中央电视台《对话》栏目的某期节目中,主持人问过我一个的问题:“学物理的人,如果日后不...
-
- 你不知道的关于这只眯眼兔的6个小秘密
-
在你们忙着给熊本君做表情包的时候,要知道,最先在网络上引起轰动的可是这只脸上只有两条缝的兔子——兔斯基。今年,它更是迎来了自己的10岁生日。①关于德艺双馨“老艺...
-
2025-02-21 16:00 yuyutoo
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- mybatis plus (70)
- scheduledtask (71)
- css滚动条 (60)
- java学生成绩管理系统 (59)
- 结构体数组 (69)
- databasemetadata (64)
- javastatic (68)
- jsp实用教程 (53)
- fontawesome (57)
- widget开发 (57)
- vb net教程 (62)
- hibernate 教程 (63)
- case语句 (57)
- svn连接 (74)
- directoryindex (69)
- session timeout (58)
- textbox换行 (67)
- extension_dir (64)
- linearlayout (58)
- vba高级教程 (75)
- iframe用法 (58)
- sqlparameter (59)
- trim函数 (59)
- flex布局 (63)
- contextloaderlistener (56)