Tcp Socket 编程之Delphi与其他语言的字节码通信
yuyutoo 2024-12-31 15:36 9 浏览 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 运行效果
相关推荐
- 当 Linux 根分区 (/) 已满时如何释放空间?
-
根分区(/)是Linux文件系统的核心,包含操作系统核心文件、配置文件、日志文件、缓存和用户数据等。当根分区满载时,系统可能出现无法写入新文件、应用程序崩溃甚至无法启动的情况。常见原因包括:...
- 玩转 Linux 之:磁盘分区、挂载知多少?
-
今天来聊聊linux下磁盘分区、挂载的问题,篇幅所限,不会聊的太底层,纯当科普!!1、Linux分区简介1.1主分区vs扩展分区硬盘分区表中最多能存储四个分区,但我们实际使用时一般只分为两...
- Linux 文件搜索神器 find 实战详解,建议收藏
-
在Linux系统使用中,作为一个管理员,我希望能查找系统中所有的大小超过200M文件,查看近7天系统中哪些文件被修改过,找出所有子目录中的可执行文件,这些任务需求...
- Linux 操作系统磁盘操作(linux 磁盘命令)
-
一、文档介绍本文档描述Linux操作系统下多种场景下的磁盘操作情况。二、名词解释...
- Win10新版19603推送:一键清理磁盘空间、首次集成Linux文件管理器
-
继上周四的Build19592后,微软今晨面向快速通道的Insider会员推送Windows10新预览版,操作系统版本号Build19603。除了一些常规修复,本次更新还带了不少新功能,一起来了...
- Android 16允许Linux终端使用手机全部存储空间
-
IT之家4月20日消息,谷歌Pixel手机正朝着成为强大便携式计算设备的目标迈进。2025年3月的更新中,Linux终端应用的推出为这一转变奠定了重要基础。该应用允许兼容的安卓设备...
- Linux 系统管理大容量磁盘(2TB+)操作指南
-
对于容量超过2TB的磁盘,传统MBR分区表的32位寻址机制存在限制(最大支持2.2TB)。需采用GPT(GUIDPartitionTable)分区方案,其支持64位寻址,理论上限为9.4ZB(9....
- Linux 服务器上查看磁盘类型的方法
-
方法1:使用lsblk命令lsblk输出说明:TYPE列显示设备类型,如disk(物理磁盘)、part(分区)、rom(只读存储)等。...
- ESXI7虚机上的Ubuntu Linux 22.04 LVM空间扩容操作记录
-
本人在实际的使用中经常遇到Vmware上安装的Linux虚机的LVM扩容情况,最终实现lv的扩容,大多数情况因为虚机都是有备用或者可停机的情况,一般情况下通过添加一块物理盘再加入vg,然后扩容lv来实...
- 5.4K Star很容易!Windows读取Linux磁盘格式工具
-
[开源日记],分享10k+Star的优质开源项目...
- Linux 文件系统监控:用脚本自动化磁盘空间管理
-
在Linux系统中,文件系统监控是一项非常重要的任务,它可以帮助我们及时发现磁盘空间不足的问题,避免因磁盘满而导致的系统服务不可用。通过编写脚本自动化磁盘空间管理,我们可以更加高效地处理这一问题。下面...
- Linux磁盘管理LVM实战(linux实验磁盘管理)
-
LVM(逻辑卷管理器,LogicalVolumeManager)是一种在Linux系统中用于灵活管理磁盘空间的技术,通过将物理磁盘抽象为逻辑卷,实现动态调整存储容量、跨磁盘扩展等功能。本章节...
- Linux查看文件大小:`ls`和`du`为何结果不同?一文讲透原理!
-
Linux查看文件大小:ls和du为何结果不同?一文讲透原理!在Linux运维中,查看文件大小是日常高频操作。但你是否遇到过以下困惑?...
- 使用 df 命令检查服务器磁盘满了,但用 du 命令发现实际小于磁盘容量
-
在Linux系统中,管理员或开发者经常会遇到一个令人困惑的问题:使用...
- Linux磁盘爆满紧急救援指南:5步清理释放50GB+小白也能轻松搞定
-
“服务器卡死?网站崩溃?当Linux系统弹出‘Nospaceleft’的红色警报,别慌!本文手把手教你从‘删库到跑路’进阶为‘磁盘清理大师’,5个关键步骤+30条救命命令,快速释放磁盘空间,拯救你...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- 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)