「12.网络编程」4.Linux环境下Tcp Socket 编程

4.Linux环境下Tcp Socket 编程

在 Linux 环境下使用 Indy 组件进行 TCP Socket 编程也非常的简单,一般情况下,Linux 下编写 TCP Socket 应用程序是不需要有 UI 的,所以,今天我们一起来在 Linux 环境下实现一个 shell 命令行的 TCP Socket 应用程序。

在 Linux 环境下编写 Object Pascal 程序,就需要使用 Lazarus 或者 CodeTyphon 了,我们在本节使用 CodeTyphon 来编写,同时在 Linux 下编写 shell 命令行程序,日志系统是不可缺少的,所以,我先介绍 Lazarus 下的日志系统。

4.1 Lazarus Logger

在 Lazarus 中, TEventLog 是一个可用于向系统日志发送消息的组件。如果不存在系统日志(例如在 Windows 95/98 或 DOS 上),则将消息写入文件。消息可以使用一般的 Log 调用,或专门的 Warning、Error、Info或Debug调用来记录,这些调用具有预定义的事件类型。

TEventLog 的主要属性:

  • LogType – 日志类型,取值: ltSystem | ltFile | ItStdErr | ItStdOut
  • FileName – 日志文件名
  • DefaultEventType – 默认事件类型,取值:etCustom | etInfo | etWarning |etError | etDebug
  • AppendContent – 控制是否将输出附加到现有文件
  • Active – 激活日志机制,取值为布尔型
  • TimeStampFormat – 时间戳字符串的格式

TEventLog 的主要方法:

方法

说明

Log()

将消息记录到系统日志中

Warning()

记录警告消息

Error()

将错误消息记录

Debug()

记录调试消息

Info()

记录信息性消息

4.2 Tcp Socket 服务端

在 Linux shell 下开发 Tcp Socket 服务端,需要创建一个 Console Application,然后新建的应用程序类中编写代码。仍然采用上一节的示例来说明。

示例:客户端定时实时检测所在机器的屏幕分辨率上行到服务端,服务端接收到数据后,根据其屏幕分辨率随机生成一个坐标并下发给客户端,客户端将应用程序的窗体位置放置到相应的坐标上。

客户端代码仍然是上一节的代码,在此不再赘述。

首先,打开 CodeTyphon,选择 Project -> New Project,然后选择 Console Application,设置 Application class name 和 Title,单击 Ok,此时生成的代码如下:

program Project1;{$mode objfpc}{$H+}uses {$IFDEF UNIX} cthreads, {$ENDIF} Classes, SysUtils, CustApp { you can add units after this };type { TTcpApplication } TTcpApplication = class(TCustomApplication) protected procedure DoRun; override; public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure WriteHelp; virtual; end;{ TTcpApplication }procedure TTcpApplication.DoRun;var ErrorMsg: String;begin // quick check parameters ErrorMsg:=CheckOptions(‘h’, ‘help’); if ErrorMsg” then begin ShowException(Exception.Create(ErrorMsg)); Terminate; Exit; end; // parse parameters if HasOption(‘h’, ‘help’) then begin WriteHelp; Terminate; Exit; end; { add your program here } // stop program loop Terminate;end;constructor TTcpApplication.Create(TheOwner: TComponent);begin inherited Create(TheOwner); StopOnException:=True;end;destructor TTcpApplication.Destroy;begin inherited Destroy;end;procedure TTcpApplication.WriteHelp;begin { add your help code here } writeln(‘Usage: ‘, ExeName, ‘ -h’);end;var Application: TTcpApplication;begin Application:=TTcpApplication.Create(nil); Application.Title:=’Tcp Application’; Application.Run; Application.Free;end.

生成的代码为设置的类名的类代码,其中:

  • WriteHelp – 输出帮助信息
  • DoRun – 该程序的主要执行代码

下面,我们根据需求来实现 Tcp Server 的功能,由于需要在程序执行过程中记录日志,所以先在该类的声明部分添加日志和 TIdTCPServer 变量,如下代码:

private TcpServer: TIdTCPServer; Logger: TEventLog;

接下来,声明日志输出功能和 Tcp Server 需要实现的事件处理功能,对于日志,我们需要输出消息类数据和错误类信息,代码如下:

procedure OutputInfoLog(Info: String); // 消息日志procedure OutputErrorLog(Error: String); // 错误日志procedure ServeExecute(AContext: TIdContext); // 与客户端交互数据procedure ServeConnecte(AContext: TIdContext); // 客户端建立连接时触发procedure ServeDisconnect(AContext: TIdContext); // 客户端断开连接时触发procedure ServeException(AContext: TIdContext; AException: Exception); // 与客户端交互发生异常时触发

实现日志输出功能:

procedure TTcpApplication.OutputInfoLog(Info: String);var LogContent: String;begin LogContent:=FormatDateTime(‘yyyy-mm-dd hh:nn:ss:zzz’, Now) + ‘ -info- ‘ + Info; Writeln(LogContent); Logger.Info(LogContent);end;procedure TTcpApplication.OutputErrorLog(Error: String);var LogContent: String;begin LogContent:=FormatDateTime(‘yyyy-mm-dd hh:nn:ss:zzz’, Now) + ‘ -error- ‘ + Error; Writeln(LogContent); Logger.Info(LogContent);end;

实现客户端建立连接、断开连接、发生异常时触发的事件:

procedure TTcpApplication.ServeConnecte(AContext: TIdContext);var Info: String;begin // 连接 Info := AContext.Connection.Socket.Binding.PeerIP + ‘:’ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – has created connection.’; OutputInfoLog(Info);end;procedure TTcpApplication.ServeDisconnect(AContext: TIdContext);var Info: String;begin // 断开连接 Info := AContext.Connection.Socket.Binding.PeerIP + ‘:’ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – has closed connection.’; OutputInfoLog(Info);end;procedure TTcpApplication.ServeException(AContext: TIdContext; AException: Exception);var Info: String;begin // 异常 Info := AContext.Connection.Socket.Binding.PeerIP + ‘ ‘ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – Error: ‘ + AException.Message; OutputErrorLog(Info);end;

定义交互数据结构:

// 通信数据结构 TCommBlock = Record Part: String[1]; // 客户端上传: W-屏幕宽度, H-屏幕高度, E-结束; 服务端下发: X-水平坐标, Y-垂直坐标, E-结束 Desc: String[16]; // 描述 Value: Integer; // 数据值 end;

实现数据交互功能:

procedure TTcpApplication.ServeExecute(AContext: TIdContext);var CommBlock: TCommBlock; bytes: TIdBytes; W, H, X, Y: Integer; Host: String;begin // 执行 if AContext.Connection.Connected then begin try OutputInfoLog(‘—– Start —–‘); Host:=AContext.Connection.Socket.Binding.PeerIP; AContext.Connection.IOHandler.ReadBytes(bytes, SizeOf(CommBlock), False); BytesToRaw(bytes, CommBlock, SizeOf(CommBlock)); OutputInfoLog(‘Recv – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); if CommBlock.Part = ‘W’ then W:=CommBlock.Value; if CommBlock.Part = ‘H’ then H:=CommBlock.Value; if CommBlock.Part = ‘E’ then begin Randomize; X:=Random(W); Y:=Random(H); // 发送水平坐标 CommBlock.Part:=’X’; CommBlock.Desc:=’水平坐标’; CommBlock.Value:=X; OutputInfoLog(‘Send – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); AContext.Connection.IOHandler.Write(RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送垂直坐标 CommBlock.Part:=’Y’; CommBlock.Desc:=’垂直坐标’; CommBlock.Value:=Y; OutputInfoLog(‘Send – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); AContext.Connection.IOHandler.Write(RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送结束标志 CommBlock.Part:=’E’; CommBlock.Desc:=’结束’; CommBlock.Value:=0; OutputInfoLog(‘Send – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); AContext.Connection.IOHandler.Write(RawToBytes(CommBlock, SizeOf(CommBlock))); end; except ON E: Exception do OutputErrorLog(‘ERROR: ‘ + Host + ‘ – ‘ + E.Message); end; end;end;

实现主程序:

// 日志初始化Logger:=TEventLog.Create(Self);Logger.FileName:=’tcp_serve.log’;Logger.LogType:=ltFile;Logger.DefaultEventType:=etDebug;Logger.AppendContent:=True;Logger.Active:=True;// Server 端初始化TcpServer:=TIdTCPServer.Create(Self);TcpServer.DefaultPort:=8081;// 设置 Server 端事件处理TcpServer.OnConnect:=@ServeConnecte;TcpServer.OnDisconnect:=@ServeDisconnect;TcpServer.OnExecute:=@ServeExecute;TcpServer.OnException:=@ServeException;// 启动 ServerTcpServer.StartListening;TcpServer.Active:=True;OutputInfoLog(‘Started at 8081’); while True do readln();// 释放资源 Logger.Destroy;TcpServer.StopListening;TcpServer.Destroy; // stop program loopTerminate;

完整代码如下:

program ct1203;{$mode objfpc}{$H+}uses {$IFDEF UNIX} cthreads, {$ENDIF} Classes, SysUtils, CustApp, IdTCPServer, IdContext, IdGlobal, EventLog { you can add units after this };type { TTcpApplication } TTcpApplication = class(TCustomApplication) private TcpServer: TIdTCPServer; Logger: TEventLog; protected procedure DoRun; override; procedure OutputInfoLog(Info: String); procedure OutputErrorLog(Error: String); procedure ServeExecute(AContext: TIdContext); procedure ServeConnecte(AContext: TIdContext); procedure ServeDisconnect(AContext: TIdContext); procedure ServeException(AContext: TIdContext; AException: Exception); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; // procedure WriteHelp; virtual; end; // 通信数据结构 TCommBlock = Record Part: String[1]; // 客户端上传: W-屏幕宽度, H-屏幕高度, E-结束; 服务端下发: X-水平坐标, Y-垂直坐标, E-结束 Desc: String[16]; // 描述 Value: Integer; // 数据值 end;{ TTcpApplication }procedure TTcpApplication.OutputInfoLog(Info: String);var LogContent: String;begin LogContent:=FormatDateTime(‘yyyy-mm-dd hh:nn:ss:zzz’, Now) + ‘ -info- ‘ + Info; Writeln(LogContent); Logger.Info(LogContent);end;procedure TTcpApplication.OutputErrorLog(Error: String);var LogContent: String;begin LogContent:=FormatDateTime(‘yyyy-mm-dd hh:nn:ss:zzz’, Now) + ‘ -error- ‘ + Error; Writeln(LogContent); Logger.Info(LogContent);end;procedure TTcpApplication.ServeConnecte(AContext: TIdContext);var Info: String;begin // 连接 Info := AContext.Connection.Socket.Binding.PeerIP + ‘:’ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – has created connection.’; OutputInfoLog(Info);end;procedure TTcpApplication.ServeDisconnect(AContext: TIdContext);var Info: String;begin // 断开连接 Info := AContext.Connection.Socket.Binding.PeerIP + ‘:’ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – has closed connection.’; OutputInfoLog(Info);end;procedure TTcpApplication.ServeException(AContext: TIdContext; AException: Exception);var Info: String;begin // 异常 Info := AContext.Connection.Socket.Binding.PeerIP + ‘ ‘ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – Error: ‘ + AException.Message; OutputErrorLog(Info);end;procedure TTcpApplication.ServeExecute(AContext: TIdContext);var CommBlock: TCommBlock; bytes: TIdBytes; W, H, X, Y: Integer; Host: String;begin // 执行 if AContext.Connection.Connected then begin try OutputInfoLog(‘—– Start —–‘); Host:=AContext.Connection.Socket.Binding.PeerIP; AContext.Connection.IOHandler.ReadBytes(bytes, SizeOf(CommBlock), False); BytesToRaw(bytes, CommBlock, SizeOf(CommBlock)); OutputInfoLog(‘Recv – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); if CommBlock.Part = ‘W’ then W:=CommBlock.Value; if CommBlock.Part = ‘H’ then H:=CommBlock.Value; if CommBlock.Part = ‘E’ then begin Randomize; X:=Random(W); Y:=Random(H); // 发送水平坐标 CommBlock.Part:=’X’; CommBlock.Desc:=’水平坐标’; CommBlock.Value:=X; OutputInfoLog(‘Send – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); AContext.Connection.IOHandler.Write(RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送垂直坐标 CommBlock.Part:=’Y’; CommBlock.Desc:=’垂直坐标’; CommBlock.Value:=Y; OutputInfoLog(‘Send – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); AContext.Connection.IOHandler.Write(RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送结束标志 CommBlock.Part:=’E’; CommBlock.Desc:=’结束’; CommBlock.Value:=0; OutputInfoLog(‘Send – ‘ + Host + ‘ ‘ + CommBlock.Desc + ‘: ‘ + inttostr(CommBlock.Value)); AContext.Connection.IOHandler.Write(RawToBytes(CommBlock, SizeOf(CommBlock))); end; except ON E: Exception do OutputErrorLog(‘ERROR: ‘ + Host + ‘ – ‘ + E.Message); end; end;end;procedure TTcpApplication.DoRun;// var // ErrorMsg: String;begin // quick check parameters (*ErrorMsg:=CheckOptions(‘h’, ‘help’); if ErrorMsg” then begin ShowException(Exception.Create(ErrorMsg)); Terminate; Exit; end;*) // parse parameters (*if HasOption(‘h’, ‘help’) then begin WriteHelp; Terminate; Exit; end;*) { add your program here } Logger:=TEventLog.Create(Self); Logger.FileName:=’tcp_serve.log’; Logger.LogType:=ltFile; Logger.DefaultEventType:=etDebug; Logger.AppendContent:=True; // Logger.TimeStampFormat:=’yyyy-mm-dd hh:nn:ss:zzz’; Logger.Active:=True; TcpServer:=TIdTCPServer.Create(Self); TcpServer.DefaultPort:=8081; TcpServer.OnConnect:=@ServeConnecte; TcpServer.OnDisconnect:=@ServeDisconnect; TcpServer.OnExecute:=@ServeExecute; TcpServer.OnException:=@ServeException; TcpServer.StartListening; TcpServer.Active:=True; OutputInfoLog(‘Started at 8081’); while True do readln(); Logger.Destroy; TcpServer.StopListening; TcpServer.Destroy; // stop program loop Terminate;end;constructor TTcpApplication.Create(TheOwner: TComponent);begin inherited Create(TheOwner); StopOnException:=True;end;destructor TTcpApplication.Destroy;begin inherited Destroy;end;(*procedure TTcpApplication.WriteHelp;begin { add your help code here } writeln(‘Usage: ‘, ExeName, ‘ -h’);end;*)var Application: TTcpApplication;begin Application:=TTcpApplication.Create(nil); Application.Title:=’Tcp Application’; Application.Run; Application.Free;end.

程序运行效果:

[2022-06-25 10:30:07.442 Info] 2022-06-25 10:30:07:442 -info- Started at 8081 [2022-06-25 10:30:21.783 Info] 2022-06-25 10:30:21:783 -info- 127.0.0.1:52612 – has created connection. [2022-06-25 10:30:21.783 Info] 2022-06-25 10:30:21:783 -info- —– Start —– [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- Recv – 127.0.0.1 宽度: 1920 [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- —– Start —– [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- Recv – 127.0.0.1 高度: 1080 [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- —– Start —– [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- Recv – 127.0.0.1 结束: 0 [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- Send – 127.0.0.1 水平坐标: 550 [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- Send – 127.0.0.1 垂直坐标: 877 [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- Send – 127.0.0.1 结束: 0 [2022-06-25 10:30:31.797 Info] 2022-06-25 10:30:31:797 -info- —– Start —– [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- Recv – 127.0.0.1 宽度: 1920 [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- —– Start —– [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- Recv – 127.0.0.1 高度: 1080 [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- —– Start —– [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- Recv – 127.0.0.1 结束: 0 [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- Send – 127.0.0.1 水平坐标: 777 [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- Send – 127.0.0.1 垂直坐标: 950 [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- Send – 127.0.0.1 结束: 0 [2022-06-25 10:30:41.794 Info] 2022-06-25 10:30:41:794 -info- —– Start —– [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- Recv – 127.0.0.1 宽度: 1920 [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- —– Start —– [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- Recv – 127.0.0.1 高度: 1080 [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- —– Start —– [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- Recv – 127.0.0.1 结束: 0 [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- Send – 127.0.0.1 水平坐标: 271 [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- Send – 127.0.0.1 垂直坐标: 294 [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- Send – 127.0.0.1 结束: 0 [2022-06-25 10:30:51.792 Info] 2022-06-25 10:30:51:792 -info- —– Start —– [2022-06-25 10:30:53.792 Info] 2022-06-25 10:30:53:792 -error- ERROR: 127.0.0.1 – Connection Closed Gracefully. [2022-06-25 10:30:53.792 Info] 2022-06-25 10:30:53:792 -info- 127.0.0.1:52612 – has closed connection.

4.3 Tcp Socket Server 代码框架

program 程序名;{$mode objfpc}{$H+}uses {$IFDEF UNIX} cthreads, {$ENDIF} Classes, SysUtils, CustApp, IdTCPServer, IdContext, IdGlobal, EventLog { you can add units after this };type { TTcpApplication } TTcpApplication = class(TCustomApplication) private TcpServer: TIdTCPServer; Logger: TEventLog; protected procedure DoRun; override; procedure OutputInfoLog(Info: String); procedure OutputErrorLog(Error: String); procedure ServeExecute(AContext: TIdContext); procedure ServeConnecte(AContext: TIdContext); procedure ServeDisconnect(AContext: TIdContext); procedure ServeException(AContext: TIdContext; AException: Exception); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; end;{ TTcpApplication }procedure TTcpApplication.OutputInfoLog(Info: String);var LogContent: String;begin LogContent:=FormatDateTime(‘yyyy-mm-dd hh:nn:ss:zzz’, Now) + ‘ -info- ‘ + Info; Writeln(LogContent); Logger.Info(LogContent);end;procedure TTcpApplication.OutputErrorLog(Error: String);var LogContent: String;begin LogContent:=FormatDateTime(‘yyyy-mm-dd hh:nn:ss:zzz’, Now) + ‘ -error- ‘ + Error; Writeln(LogContent); Logger.Info(LogContent);end;procedure TTcpApplication.ServeConnecte(AContext: TIdContext);var Info: String;begin // 连接 Info := AContext.Connection.Socket.Binding.PeerIP + ‘:’ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – has created connection.’; OutputInfoLog(Info);end;procedure TTcpApplication.ServeDisconnect(AContext: TIdContext);var Info: String;begin // 断开连接 Info := AContext.Connection.Socket.Binding.PeerIP + ‘:’ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – has closed connection.’; OutputInfoLog(Info);end;procedure TTcpApplication.ServeException(AContext: TIdContext; AException: Exception);var Info: String;begin // 异常 Info := AContext.Connection.Socket.Binding.PeerIP + ‘ ‘ + inttostr(AContext.Connection.Socket.Binding.PeerPort) + ‘ – Error: ‘ + AException.Message; OutputErrorLog(Info);end;procedure TTcpApplication.ServeExecute(AContext: TIdContext);begin // 执行 – 业务逻辑 end;procedure TTcpApplication.DoRun;begin { add your program here } Logger:=TEventLog.Create(Self); Logger.FileName:=’tcp_serve.log’; Logger.LogType:=ltFile; Logger.DefaultEventType:=etDebug; Logger.AppendContent:=True; Logger.Active:=True; TcpServer:=TIdTCPServer.Create(Self); TcpServer.DefaultPort:=8081; TcpServer.OnConnect:=@ServeConnecte; TcpServer.OnDisconnect:=@ServeDisconnect; TcpServer.OnExecute:=@ServeExecute; TcpServer.OnException:=@ServeException; TcpServer.StartListening; TcpServer.Active:=True; Writeln(‘Started at 8081’); Logger.Info(‘Started at 8081′); while True do readln(); // stop program loop Terminate;end;constructor TTcpApplication.Create(TheOwner: TComponent);begin inherited Create(TheOwner); StopOnException:=True;end;destructor TTcpApplication.Destroy;begin inherited Destroy;end;var Application: TTcpApplication;begin Application:=TTcpApplication.Create(nil); Application.Title:=’Tcp Application’; Application.Run; Application.Free;end.

将上面的代码框架复制后适当修改,实现业务逻辑即可。

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月27日
下一篇 2022年6月27日

相关推荐

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息