Tcp Socket 编程之Delphi与其他语言的字节码通信
yuyutoo 2024-12-31 15:36 3 浏览 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 运行效果
相关推荐
- 二十三种设计模式之-模板方法模式
-
这是我写二十三种设计模式第二篇文章。这个系列我将持续写下去,欢迎大家关注,点赞和收藏。模板方法模式1.模板方法模式(TemplateMethodPattern)又叫模板模式,在一个抽象的类中,公开...
- 从 Java 代码逆向工程生成 UML 类图和序列图
-
前言本文面向于那些软件架构师,设计师和开发人员,他们想使用IBM?Rational?SoftwareArchitect从Java?源代码来逆向工程生成UML类和序列图。逆向工程经常...
- 作为程序员,还在手动画流程图、类图?看看这个神器
-
老板看不懂你写的代码,要求你补充流程图。。。客户看不懂你的代码,要求画流程图。。。新同事看不懂你的代码,要求画流程图。。。此时此刻,你的内心是崩溃的。。。曾几何时,我也和你一样崩溃。。。...
- 使用 seaborn 绘制 12 类图
-
你好,我是zhenguo今晚分享一个很不错的seaborn可视化实战入门材料,这个实战教程来自于kaggle,使用的是美国警察开枪数据集,大小1M,一共5个csv文件使用seaborn作...
- 分享一个从源码快速生成UML类图的插件——PlantUML Parser
-
前言相信每一位程序员都分析过源码,在分析源码过程中,除了了解代码实现的功能(业务逻辑),还需要深入下去了解程序代码的执行过程以及结构,往往在了解代码执行过程(动态模型)前,先对代码的结构(静态模型)有...
- 需求分析-类图建模
-
...
- 还能这么玩?用VsCode画类图、流程图、时序图、...不要太爽
-
软件设计中,有好几种图需要画,比如流程图、类图、组件图等,我知道大部分人画流程图一般都会用微软的viso绘制,我之前也是这个习惯。viso画图有个不好的地方是需要时刻去调整线条和边框已达到简洁美观,今...
- UML:类图关系总结
-
UML类图几种关系的总结,泛化=实现>组合>聚合>关联>依赖在UML类图中,常见的有以下几种关系:泛化(Generalization),实现(Reali...
- 小白进阶之路:一文读懂UML-类图
-
UML类图(UnifiedModelingLanguageClassDiagram)是一种用于可视化和描述系统中类、属性、方法以及它们之间关系的图形化表示方法。我在大学时,学习这个知识总是容易...
- 餐饮系统大拆解:用类图拆解员工结构与工作职责(1)
-
编辑导语:利用类图这一方式,产品经理可以更清晰地梳理设计思路,进而推动后续方案的迭代优化,同时结合类图梳理,团队内也能降低沟通成本。具体应该如何拆解?本篇文章里,作者结合餐饮系统,对类图拆解和梳理做了...
- 软件开发设计文档之「类图」
-
对象是系统中用来描述客观事物的一个实体,它由对象标识(名称)、属性(状态、数据、成员变量)和服务(操作、行为、方法)三个要素组成,它们被封装为一个整体,以接口的形式对外提供服务。而类则是对具有相同属性...
- 如何绘制「UML类图」?附内容详解和优质实例分析!
-
下面这篇文章是笔者整理分析的关于如何绘制「UML类图」的相关内容,大家一起来看看吧!UML图有很多种,但是并非必须掌握所有的UML图,才能完整系统分析和设计工作。一般说来,在UML图中,只要掌握类图、...
- UML统一建模语言系列二:类图设计方法及最佳实践
-
一、前言...
- 类图(Class Diagram)
-
类图(ClassDiagram):类(Class)封装了数据和行为,是面向对象的重要组成部分,它是具有相同属性、操作、关系的对象集合的总称。类一般由三部分组成:类名(Class):每个类都必须有一个...
- 类图怎么画?简单快速绘制类图的软件
-
类图是显示模型中的类、类的内部结构和其他类的关系的图表,用来描述系统的结构化设计。类图是由类、包等元素和内容相互连接组成,是最常用的UML图。类图是描述系统中的类以及它们之间的关系的图表,它的主要作用...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)