百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 编程网 > 正文

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图。类图是描述系统中的类以及它们之间的关系的图表,它的主要作用...

取消回复欢迎 发表评论: