大家好,欢迎来到IT知识分享网。
6.Linux环境下Udp Socket 编程
对于 Linux 环境下的 UDP Socket 编程,与 TCP Scoket 编程是一样的,多数情况下是 Shell 命令行方式运行的应用程序,所以日志和配置相对比较重要,在本章第4节《4.Linux环境下Tcp Socket 编程》中我们介绍了 Lazarus 的日志系统,这一节我们先来学习一下在 Lazarus 下使用 ini 文件,目的是我们在 ini 文件中设置 UDP Socket Server 的端口号,然后在该端口上启动应用程序。
6.1 使用 ini 文件
INI 文件是存储按部分分组的键/值对的文本文件。INI 文件可用于轻松保存用户设置。使用 IniFiles unit 和 TINIFile class,可以轻松地使用其中的数据。IniFiles unit 是 FCL 的一部分。目前仅支持符合更严格的 Windows ini 文件格式的 INI 文件。即使用 sections 是强制性的,只有分号可以用于注释。
INI 文件使用方括号来创建和标记包含sections、keys 和 values,Key 与其对应的 Value 用等号分隔(Key=Value)。
节名称放在方括号 ([Section]) 内。
注释是允许的,并在行首用分号 (;) 标记。
示例 ini 文件:
; Comment. Beginning of INI file ; empty lines are ignored [General] ; This starts a General section Compiler=FreePascal ; Key Compiler and value FreePascal
6.1.1 读写 ini 文件
使用 ini 文件首先需要通过 uses 语句声明使用 IniFiles 单元,然后使用该单元中的 TINIFile 类来操作 ini 文件。
TIniFile 类的主要主要方法如下表:
方法 |
说明 |
constructor Create(); |
创建一个新的TINiFile实例 |
destructor Destroy; override; |
从内存中删除TINiFile实例 |
function ReadString(); override; |
读取一个字符串 |
procedure WriteString(); override; |
将字符串写入文件 |
procedure ReadSection(); override; |
读取 Section 中的键 |
procedure ReadSectionRaw(); |
读取原始的 Section |
procedure ReadSections(); override; |
读取 Section 名称 |
procedure ReadSectionValues(); override; overload; |
读取 Section 中的数据 |
procedure EraseSection(); override; |
删除 Section |
procedure DeleteKey(); override; |
删除键 |
procedure UpdateFile; override; |
更新磁盘上的文件 |
property Stream: TStream; [r] |
获取 ini 文件流 |
property CacheUpdates: Boolean; [rw] |
是否应该将更改保存在内存中 |
6.1.2 ini 文件读写示例
示例:通过 ini 文件保存连接到 PostgreSQL 数据库的参数,通过应用程序界面来读写 ini 文件。ini 文件为:
pgdb.ini
[PostgreSQL] host=127.0.0.1 port=9432 db=demodb user=postgres password=admin@163.com
应用程序界面如下图:
代码如下:
uses Classes, SysUtils, Forms, Controls, Graphics, Dialogs, StdCtrls, Spin, IniFiles; type { TForm1 } TForm1 = class(TForm) DbEdit: TEdit; PasswordEdit: TEdit; UserEdit: TEdit; HostEdit: TEdit; SaveButton: TButton; Label1: TLabel; Label2: TLabel; Label3: TLabel; Label4: TLabel; Label5: TLabel; PortSpinEdit: TSpinEdit; procedure FormCreate(Sender: TObject); procedure SaveButtonClick(Sender: TObject); private public end; var Form1: TForm1; IniFile: TIniFile; implementation {$R *.frm} { TForm1 } procedure TForm1.FormCreate(Sender: TObject); begin // 打开 ini 文件,读取数据 IniFile:=TIniFile.Create('pgdb.ini'); HostEdit.Text:=IniFile.ReadString('PostgreSQL','host',''); PortSpinEdit.Value:=IniFile.ReadInteger('PostgreSQL','port',5432); DbEdit.Text:=IniFile.ReadString('PostgreSQL','db',''); UserEdit.Text:=IniFile.ReadString('PostgreSQL','user',''); PasswordEdit.Text:=IniFile.ReadString('PostgreSQL','password',''); end; procedure TForm1.SaveButtonClick(Sender: TObject); begin // 将数据写入 ini 文件 IniFile.WriteString('PostgreSQL','host',HostEdit.Text); IniFile.WriteInteger('PostgreSQL','port',PortSpinEdit.Value); IniFile.WriteString('PostgreSQL','db',DbEdit.Text); IniFile.WriteString('PostgreSQL','user',UserEdit.Text); IniFile.WriteString('PostgreSQL','password',PasswordEdit.Text); end;
6.2 UDP Socket 服务端
在 Linux shell 下开发 UdpSocket 服务端,需要创建一个 Console Application,然后新建的应用程序类中编写代码。仍然采用上一节的示例来说明。
示例:客户端定时实时检测所在机器的屏幕分辨率上行到服务端,服务端接收到数据后,根据其屏幕分辨率随机生成一个坐标并下发给客户端,客户端将应用程序的窗体位置放置到相应的坐标上。
客户端代码仍然是上一节的代码,在此不再赘述。
首先,打开 CodeTyphon,选择 Project -> New Project,然后选择 Console Application,设置 Application class name 和 Title,单击 Ok。
声明日志、ini文件、TIdUDPServer:
TUDPApplication = class(TCustomApplication) private IdUDPServe: TIdUDPServer; Logger: TEventLog; W, H, X, Y: Integer; // 用于上下行的数据 protected procedure DoRun; override; // UDP 异常处理 procedure UDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage: String; const AExceptionClass: TClass); // UDP 读取数据 procedure UDPReadData(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); // 输出日志 procedure OutputInfoLog(Info: String); procedure OutputErrorLog(Error: String); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure WriteHelp; virtual; end; var IniFile: TIniFile; // 配置文件
通信数据结构:
TCommBlock = Record // 客户端上传: W-屏幕宽度, H-屏幕高度, E-结束; // 服务端下发: X-水平坐标, Y-垂直坐标, E-结束; Part: String[1]; Desc: String[16]; // 描述 Value: Integer; // 数据值 end;
日志输出实现代码:
procedure TUDPApplication.OutputInfoLog(Info: String); begin Writeln(Info); Logger.Info(Info); end; procedure TUDPApplication.OutputErrorLog(Error: String); begin Writeln(Error); Logger.Error(Error); end;
UDP 异常代码:
procedure TUDPApplication.UDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage: String; const AExceptionClass: TClass); begin OutputErrorLog(AMessage); end;
UDP 读取代码:
procedure TUDPApplication.UDPReadData(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); var Ip: String; Port: Integer; CommBlock: TCommBlock; begin OutputInfoLog('----- Read -----'); Ip:=AThread.Binding.PeerIP; Port:=AThread.Binding.PeerPort; BytesToRaw(AData, CommBlock, SizeOf(CommBlock)); OutputInfoLog(Ip + ':' + inttostr(Port) + ' - ' + 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(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); AThread.Server.Sendbuffer(Ip, Port, RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送垂直坐标 CommBlock.Part:='Y'; CommBlock.Desc:='垂直坐标'; CommBlock.Value:=Y; OutputInfoLog(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); AThread.Server.Sendbuffer(Ip, Port, RawToBytes(CommBlock, SizeOf(CommBlock))); // 发送结束标志 CommBlock.Part:='E'; CommBlock.Desc:='结束'; CommBlock.Value:=0; OutputInfoLog(Ip + ':' + inttostr(Port) + ' - ' + CommBlock.Desc + ': ' + inttostr(CommBlock.Value)); AThread.Server.Sendbuffer(Ip, Port, RawToBytes(CommBlock, SizeOf(CommBlock))); end; end;
应用程序启动:
procedure TUDPApplication.DoRun; var Port: Integer; begin { add your program here } // 读取配置文件 IniFile:=TIniFile.Create('server.ini'); Port:=IniFile.ReadInteger('SERVER', 'port', 8080); // 日志文件相关设置 Logger:=TEventLog.Create(Self); Logger.FileName:='udp_serve.log'; Logger.LogType:=ltFile; Logger.DefaultEventType:=etDebug; Logger.AppendContent:=True; Logger.TimeStampFormat:='yyyy-mm-dd hh:nn:ss:zzz'; Logger.Active:=True; // UDP服务端启动 IdUDPServe:=TIdUDPServer.Create(Self); IdUDPServe.DefaultPort:=Port; IdUDPServe.OnUDPException:=@UDPException; IdUDPServe.OnUDPRead:=@UDPReadData; IdUDPServe.ThreadedEvent:=True; IdUDPServe.Active:=True; OutputInfoLog('Start at ' + inttostr(Port)); while True do readln(); Logger.Destroy; IniFile.Destroy; IdUDPServe.Destroy; // stop program loop Terminate; end;
在控制台应用程序中,需要设置 ThreadedEvent 为 True:
IdUDPServe.ThreadedEvent:=True;
该属性指示 UDP 服务器是否使用单独的线程来执行 UDP 连接的读取,或者在调用 OnUDPRead 事件处理程序时,读取是否必须与服务器的主执行线程同步。
当检测到传入数据报或其中一个服务器绑定遇到异常时,在为服务器执行 TIdUDPListenerThread 期间使用ThreadedEvent。
TIdUDPListenerThread.UDPRead方法用于处理读取通知,并在ThreadedEvent为False时使用Synchronize包装对该方法的调用。
使用用于活动套接字绑定的 OnUDPRead 事件处理程序从内部 Indy 缓冲区提取数据报数据包。
TIdUDPListenerThread.UDPException 方法用于处理异常通知,并在 ThreadedEvent 为False时使用Synchronize 包装对该方法的调用。
ThreadedEvent的默认值为False。
6.3 UDP Socket Server 代码框架
program 程序名; {$mode objfpc}{$H+} uses {$IFDEF UNIX} cthreads, {$ENDIF} Classes, SysUtils, CustApp, IniFiles, IdUDPServer, IdGlobal, IdSocketHandle, EventLog { you can add units after this }; type { TUDPApplication } TUDPApplication = class(TCustomApplication) private IdUDPServe: TIdUDPServer; Logger: TEventLog; protected procedure DoRun; override; procedure UDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage: String; const AExceptionClass: TClass); procedure UDPReadData(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); procedure OutputInfoLog(Info: String); procedure OutputErrorLog(Error: String); public constructor Create(TheOwner: TComponent); override; destructor Destroy; override; procedure WriteHelp; virtual; end; var IniFile: TIniFile; { TUDPApplication } procedure TUDPApplication.OutputInfoLog(Info: String); begin Writeln(Info); Logger.Info(Info); end; procedure TUDPApplication.OutputErrorLog(Error: String); begin Writeln(Error); Logger.Error(Error); end; procedure TUDPApplication.UDPException(AThread: TIdUDPListenerThread; ABinding: TIdSocketHandle; const AMessage: String; const AExceptionClass: TClass); begin OutputErrorLog(AMessage); end; procedure TUDPApplication.UDPReadData(AThread: TIdUDPListenerThread; const AData: TIdBytes; ABinding: TIdSocketHandle); begin end; procedure TUDPApplication.DoRun; var Port: Integer; begin { add your program here } IniFile:=TIniFile.Create('server.ini'); Port:=IniFile.ReadInteger('SERVER', 'port', 8080); Logger:=TEventLog.Create(Self); Logger.FileName:='udp_serve.log'; Logger.LogType:=ltFile; Logger.DefaultEventType:=etDebug; Logger.AppendContent:=True; Logger.TimeStampFormat:='yyyy-mm-dd hh:nn:ss:zzz'; Logger.Active:=True; IdUDPServe:=TIdUDPServer.Create(Self); IdUDPServe.Bindings.Clear; IdUDPServe.DefaultPort:=Port; IdUDPServe.OnUDPException:=@UDPException; IdUDPServe.OnUDPRead:=@UDPReadData; IdUDPServe.ThreadedEvent:=True; IdUDPServe.Active:=True; OutputInfoLog('Start at ' + inttostr(Port)); while True do readln(); Logger.Destroy; IniFile.Destroy; IdUDPServe.Destroy; // stop program loop Terminate; end; constructor TUDPApplication.Create(TheOwner: TComponent); begin inherited Create(TheOwner); StopOnException:=True; end; destructor TUDPApplication.Destroy; begin inherited Destroy; end; procedure TUDPApplication.WriteHelp; begin { add your help code here } writeln('Usage: ', ExeName, ' -h'); end; var Application: TUDPApplication; begin Application:=TUDPApplication.Create(nil); Application.Title:='UDPApplication'; Application.Run; Application.Free; end.
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/53864.html