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.
将上面的代码框架复制后适当修改,实现业务逻辑即可。