在Delphi 2007 中用Indy10开发Socket应用程序
- 格式:doc
- 大小:279.50 KB
- 文档页数:11
为Delphi移动开发准备的跨平台异步Socket为Delphi移动开发准备的跨平台异步Socket 浏览:650加⼊我的收藏楼主:⼀、引⾔在D10.1 Berlin之前,Delphi移动开发⼀直缺少⼀个使⽤⽅便、跨平台的异步socket控件,除了Indy组件,连最基本的socket单元都缺少。
所以,移动开发时实现移动客户端和电脑服务器之间、⼿机之间的异步通信⽐较困难。
本⼈以前在实现⼿机端的消息传输时,还是通过HTTP轮询形式实现,响应速度慢、服务端压⼒⼤,不适合⼤并发即时通信开发。
D10.1 Berlin版本发布之后,这样的局⾯改观了,因为Berlin中提供了⼀个跨平台Socket单元,尽管不是异步的,但是,通过我们通过使⽤多线程等技术的再度封装,就可以实现异步通信——收到消息、连通、断开、出错等,都激发相应的事件。
本⼈近⽇就为QuickBurro中间件的移动开发组件增加了⼀个跨平台的异步Socket控件: TMBSocket。
下⾯的代码是类声明,后图⽰意其属性、事件:typeTConnectedEvent = procedure(Sender: TObject; Socket: TSocket) of object;TDisconnectedEvent = procedure(Sender: TObject; Socket: TSocket) of Object;TDataArrivedEvent = procedure(Sender: TObject; Socket: TSocket) of Object;TErrorEvent = procedure(Sender: TObject; Socket: TSocket; const Error: string; var CanClose: boolean) of Object; //TMBSocket=class(TComponent)privateRawSocket: TSocket;fHostName: string;fHostAddress: string;fPort: integer;fActive: boolean;fConnected: boolean;//fOnConnect: TConnectedEvent;fOnDisconnect: TDisconnectedEvent;fOnDataArrive: TDataArrivedEvent;fOnError: TErrorEvent;//procedure SetHostName(aHostName: string);procedure SetHostAddress(aHostAddress: string);procedure SetPort(aPort: integer);procedure SetActive(aActive: boolean);publicconstructor Create(AOwner: TComponent); override;destructor Destroy(); override;procedure Open;procedure Close;function SendBuff(const BufferPtr: pointer; const DataLength: integer): boolean;procedure ReceiveData(const BufferPtr: pointer; const Bytes: integer);function DataLength(): integer;publishedproperty HostName: string read fHostName write SetHostName;property HostAddress: string read fHostAddress write SetHostAddress;property Port: integer read fPort write SetPort;property Active: boolean read fActive write SetActive;property Connected: boolean read fConnected;//property OnConnect: TConnectedEvent read fOnConnect write fOnConnect;property OnDisconnect: TDisconnectedEvent read fOnDisconnect write fOnDisconnect; property OnDataArrive: TDataArrivedEvent read fOnDataArrive write fOnDataArrive;property OnError: TErrorEvent read fOnError write fOnError;End;此帖⼦包含附件:PNG 图像⼤⼩:21.1K----------------------------------------------樵夫的⼤马甲作者: jopher3 (樵夫的马六甲)▲▲△△△-注册会员2017-1-2212:46:11⼆、电脑端服务程序为了测试⼿机端TMBSocket控件的异步通信效果,我们先⽤TServerSocket快速搭建⼀个测试服务器,让它与⼿机端程序进⾏通信。
加载路径一般为source目录,每个控件安装完后都应加载。
安装skin控件,默认路径为C:\Program Files\ksdev\skinengine。
打开C:\ProgramFiles\ksdev\skinengine\sources\packages运行se_d7.dpk(编译)、se_db_d7.dpk(安装)、se_db_dsgn_d7.dpk(编译)、se_dsgn_d7.dpk(安装)。
Tools-Environment Options-Library-Library path:加载路径。
一、lst4000、IP4000的安装1stclass4000ProVcl7.exe序列号:IP4013487442密码:1ST4000982940NMW 打开安装路径的package目录编译fc4000v7.dpk安装fc4000dcl7.dpk。
InfoPower4000Pro_vcl7.exe密码:IP4013487442先编译ip4000v7.dpk其余试试。
二、DSPack2.3.4的安装1 增加搜索路径(此处为DSPack解压后的目录,如你的DSPack2.3.4放在E盘根目录:则为E)\src\DirectX9 和(此处为DSPack解压后的目录)\src\DSPack在delphi7中选择菜单【Tools】-【Enviroment Options】,在打开的窗口中选择Library页签,在Library Path一项中添加这两个目录C:\Borland\VCL\DSPACK234\src\Directx9 ;C:\Borland\VCL\DSPACK234\src\DSPack(将其直接粘贴在原有内容的后面,或者点击Library Path后面的...按钮添加)2 编译(DSPackDir)\packages\DirectX9_D7.dpk双击(DSPackDir)\packages\DirectX9_D7.dpk,在delphi7中会显示一个关于重新创建资源文件的对话框,点击OK 就可以了。
Delphi使用Indy、ICS组件读取网页Delphi使用Indy、ICS组件读取网页使用Indy 10中TIdHTTP的例子:代码usesIdHttp;...function HttpGet(const Url: string; var Html: string): Boolean;varHttpClient: TIdHTTP;beginResult := False;HttpClient := TIdHTTP.Create(nil);tryHtml := HttpClient.Get(Url);Result := True;excepton e: Exception dobeginend;end;HttpClient.Free;end;Indy采用的是同步I/O的方式,而且在连接超时控制方面存在bug,因此TIdHttp.Get()有时会发生陷入死锁无法返回的问题。
使用ICS中THttpCli的例子:代码usesHttpProt;...function HttpGet(const Url: string; var Html: string): Boolean; varHttpClient: THttpCli;DataLen: Int64;FailMsg: string;beginResult := False;HttpClient := THttpCli.Create(nil);HttpClient.URL := Url;HttpClient.NoCache := True;HttpClient.RcvdStream := TMemoryStream.Create;trytryHttpClient.Get;DataLen := HttpClient.RcvdStream.Size;SetLength(Html, DataLen);HttpClient.RcvdStream.Position := 0;HttpClient.RcvdStream.Read(PChar(Html)^, DataLen); Result := True;excepton E: EHttpException dobeginFailMsg := Format('Failed : %d %s',[HttpClient.StatusCode, HttpClient.ReasonPhrase]);end elseraise;end;finallyHttpClient.RcvdStream.Free;HttpClient.RcvdStream := nil;HttpClient.Free;end;end;ICS使用的是异步I/O,其TFtpClient组件有Timout属性可以对连接超时进行控制,而THttpCli组件没有。
unit UnitTCPUDP;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs, StdCtrls,WinSock, ExtCtrls, ComCtrls,inif iles,StrUtils;constWM_SOCK = WM_USER + 82; {自定义windows消息}//在tcp 服务器方式下,WM_SOCK为监听消息// WM_SOCK+1到 WM_SOCK+MAX_ACCEPT 为与连接客户端进行通讯时的消息MAX_ACCEPT=100;FD_SET= MAX_ACCEPT;typeTFormTCPUDP = class(TForm)BtnSend: TButton;MemoReceive: TMemo;EditSend: TEdit;Label2: TLabel;Label3: TLabel;Bevel2: TBevel;STOpCode: TStaticText;STIndex: TStatic Text;STCommand: TStatic Text;GroupBox1: TGroupBox;GroupBox2: TGroupBox;GroupBox3: TGroupBox;RBTCP: TRadioButton;RBUDP: TRadioButton;Panel1: TPanel;RBClient: TRadioButton;RBServer: TRadioButton;GroupBox4: TGroupBox;BtnConnect: TButton;BtnClose: TButton;Bevel1: TBevel;StatusBar1: TStatusBar;PanelDest: TPanel;Label4: TLabel;EditRemoteHost: TEdit;Label5: TLabel;EditRemotePort: TEdit;Label6: TLabel;CmbSendTo: TComboBox;Label7: TLabel;PanelLocal: TPanel;ChkBind: TCheckBox;EditHostPort: TEdit;Label1: TLabel;procedure BtnSendClick(Sender: TObject);procedure BtnConnectClick(Sender: TObject);procedure RBTCPClick(Sender: TObject);procedure RBUDPClick(Sender: TObject);procedure BtnCloseClick(Sender: TObject);procedure FormClose(Sender: TObject; var Action: TCloseAction);procedure RBClientClick(Sender: TObject);procedure RBServerClick(Sender: TObject);procedure ChkBindClick(Sender: TObject);procedure FormCreate(Sender: TObject);procedure EditHostPortChange(Sender: TObject);procedure EditRemoteHostChange(Sender: TObject);procedure EditRemotePortChange(Sender: TObject);procedure FormActivate(Sender: TObject);procedure CmbSendToKeyPress(Sender: TObject; var Key: Char); {消息接送}private{ Private declarations }FirstFlag:Boolean;INIPath:String;procedure R eadData(var Message: TMessage);function ReadTCPUDPIni():boolean; //读取配置信息procedure Wri t eIniStr(FileName:String;section:string;Ident:string;StringValue:string);//写系统信息 procedure Wri t eIniBool(FileName:String;section:string;Ident:string;BoolValue:Boolean);//写系统信息 protected{ Protected declarations }{ other fields and methods}procedure wndproc(var message:Tmessage);override;public{ Public declarations }end;constDATA_LENGTH =120; //数据长度typeTUDPaction = packed recordopcode:byte; //操作码index:word; //序列号Command:byte; //命令字data:array[0..(DATA_LENGTH-1)] of char; //数据end;varFormTCPUDP: TFormTCPUDP;AcceptSock:Array[0..MAX_ACCEPT] OF Tsocket;FSockAccept : Array[0..MAX_ACCEPT] OF TSockAddrIn;AcceptSockFlag: Array[0..MAX_ACCEPT] OF boolean;AcceptNum:integer=0;FSockLocal : TSockAddrIn;PackageID:integer=0; //包序号BindFlag:Boolean=true;TcpFlag:Boolean=false;ServerFlag:Boolean=false;function WinSockInital(Handle: HWnd):bool;Procedure WinSockClose();implementation{$R *.dfm}{始化SOCKET}function WinSockInital(Handle: HWnd):bool;var TempWSAData: TWSAData;i:integer;beginresult := false;{ 1 初始化SOCKET}if WSAStartup(2, TempWSAData)=1 then //2表示启用winsock2 exi t;{若是用UDP通信,则用}if TcpFlag thenAcceptSock[0]:=Socket(AF_INET,SOCK_STREAM,0)elseAcceptSock[0]:=Socket(AF_INET,SOCK_DGRAM,0);if AcceptSock[0]=SOCKET_ERROR thenexi t;if (BindFlag and not tcpflag) or (Serverflag and tcpflag) thenif bind(AcceptSock[0],FSockLocal,sizeof(FSockLocal))<>0 then beginWinSockClose();exit;end;if Tcpflag thenif Serverflag thenbeginif Listen(AcceptSock[0],1)<>0 then //等待连接队列的最大长度为1begin WinSockClose();exi t;end;endelseif connect(AcceptSock[0],FSockAccept[0],sizeof(FSockAccept[0]))<>0 thenbeginWinSockClose();exi t;end;{FD_READ 在读就绪的时候, 产生WM_SOCK 自定义消息号}if not TcpFlag thenWSAAsyncSelect(AcceptSock[0], Handle , WM_SOCK, FD_READ)else if Serverflag thenWSAAsyncSelect(AcceptSock[0], Handle , WM_SOCK, FD_READ or FD_ACCEPT or FD_CLOSE) elseWSAAsyncSelect(AcceptSock[0], Handle , WM_SOCK, FD_READ or FD_CLOSE);R esult:=true;end;{关闭SOCKET}Procedure WinSockClose();var i:integer;beginfor i:=1 to MAX_ACCEPT DOif AcceptSockFlag[i] thenbeginCloseSocket(AcceptSock[i]);AcceptSockFlag[i]:=false;end;CloseSocket(AcceptSock[0]); {closesocket函数用来关闭一个描述符为AcceptSock[0]套接字}WSACleanup;end;function TFormTCPUDP.ReadTCPUDPIni():boolean;var ti:TiniFile;beginti:=TIniFile.Create(INIPath+'TCPUDP.ini');EditHostPort.text:=ti.ReadString('Setting','LocalPort','');ChkBind.Checked:=ti.ReadBool('Setting','BindStatus',false);EditR emotePort.text:=ti.ReadString('Setting','RemotePort','');EditR emoteHost.text:=ti.ReadString('Setting','R emoteHost','');RBTCP.Checked:=ti.ReadBool('Setting','TCPStatus',false);RBUDP.Checked:=not RBTCP.Checked;RBServer.Checked:=ti.R eadBool('Setting','ServerStatus',false);RBClient.Checked:=not RBServer.Checked;end;procedure TFormTCPUDP.WriteIniStr(FileName:String;Section:string;Ident:string;StringValue:string); var ti:TiniFile;beginti:=TIniFile.Create(FileName);ti.writestring(section,Ident,StringValue);ti.Free;end;procedure TFormTCPUDP.WriteIniBool(FileName:String;Section:string;Ident:string;BoolValue:Boolean); var ti:TiniFile;beginti:=TIniFile.Create(FileName);ti.writebool(section,Ident,BoolValue);ti.Free;end;procedure TFormTCPUDP.BtnSendClick(Sender: TObject);var SEND_PACKAGE : TUDPaction; //数据发送i:integer;s:String;beginFillchar(SEND_PACKAGE.data,Data_Length,chr(0));SEND_PACKAGE.data[0]:='1';SEND_PACKAGE.data[1]:='2';SEND_PACKAGE.data[2]:='3';SEND_PACKAGE.opcode:=2;SEND_PACKAGE.index:=PackageID;SEND_mand:=3;s:=editsend.Text;for i:=0 to length(EditSend.Text)-1 doSEND_PACKAGE.data[i]:=s[i+1];PackageID:=PackageID+1;if not (Tcpflag and Serverflag) thensendto(AcceptSock[0], SEND_PACKAGE,sizeof(SEND_PACKAGE), 0, FSockAccept[0], sizeof(FSockAcce pt[0]))else if AcceptNum=0 thenApplication.MessageBox('没有一个客户端和您建立连接','信息提示',MB_OK)elsebegini:=pos(' ',CmbSendto.Text);if i>0 thenbegini:=strtoint(MidStr(CmbSendTo.Text,8,i-8));sendto(AcceptSock[i], SEND_PACKAGE,sizeof(SEND_PACKAGE), 0, FSockAccept[i], sizeof(FSockAcce pt[i]));endelseApplication.MessageBox('您没有选择发送方','错误提示',MB_OK);end;// sendto(AcceptSock[0], NbtstatPacket,50, 0, FSockAccept[0], sizeof(FSockAccept[0]));end;procedure TFormTCPUDP.BtnConnectClick(Sender: TObject);var s:String;i:integer;begins:='正在建立连接....';StatusBar1.Panels[0].Text:=s;Application.ProcessMessages;FSockLocal.sin_family:=AF_INET;FSockLocal.sin_port:=htons(strtoint(Edi t Hostport.Text));FSockAccept[0].sin_family:=AF_INET;FSockAccept[0].sin_port:=htons(strtoint(EditRemoteport.Text));FSockAccept[0].SIn_Addr.S_addr := inet_addr(PChar(EditR emoteHost.Text));//inet_addr(pchar(IP)); if WinSockInital(FormTCPUDP.Handle) thenbeginBtnConnect.Enabled:=false;BtnClose.Enabled:=true;BtnSend.Enabled:=true;s:='连接成功!';if ChkBind.Checked thens:=s+', ---绑定端口';if RBTcp.Checked thenbegins:=s+',---TCP方式';if RBServer.Checked thens:=s+',---服务端'elses:=s+',---客户端';endelses:=s+',---UDP方式';if tcpflag and Serverflag thenbeginAcceptNum:=0;CmbSendto.Clear;StatusBar1.Panels[2].Text:='共有:'+inttostr(AcceptNum)+'个连接';end;endelsebeginfor i:=0 to StatusBar1.Panels.count-1 doStatusBar1.Panels[i].Text:='';s:='创建套接字失败!!';end;StatusBar1.Panels[0].Text:=s;end;procedure TFormTCPUDP.wndproc(var Message: TMessage);beginif (Message.Msg>=WM_SOCK) and (Message.Msg<=WM_SOCK+MAX_ACCEPT) thenReadData(Message)elseinherited wndproc(message);end;procedure TFormTCPUDP.ReadData(var Message: TMessage);varReceive_PACKAGE : TUDPaction; //数据发送flen,len,i,index: integer;Event: word;beginIndex:=(Message.Msg-WM_SOCK);flen:=sizeof(FSockAccept[Index]);Event := WSAGetSelectEvent(Message.LParam);if Event = FD_READ thenbeginlen := recvfrom(AcceptSock[Index], Receive_PACKAGE, sizeof(R eceive_PACKAGE), 0, FSockAccept[In dex], Flen);if len> 0 thenbeginStatusBar1.Panels[0].Text:='收到来自ip地址:'+inet_ntoa(FSockAccept[Index].sin_addr)+' 端口:'+inttostr(ntohs(FSockAccept[Index].sin_port))+'的数据';StOpCode.Caption:= format('%.2d',[Receive_PACKAGE.opCode]);StIndex.Caption:= format('%d',[Receive_PACKAGE.Index]);StCommand.Caption:= format('%.2d',[R eceive_mand]);MemoR eceive.Lines.Add(StrPas(Receive_PACKAGE.data))end;endelse if Event=FD_ACCEPT thenbeginfor i:=1 to MAX_ACCEPT DOif not AcceptSockFlag[i] thenbeginflen:=Sizeof(FSockAccept[i]);AcceptSock[i]:=accept(AcceptSock[0],@FSockAccept[i],@flen);WSAAsyncSelect(AcceptSock[i], Handle , WM_SOCK+i, FD_READ or FD_CLOSE);AcceptSockFlag[i]:=true;AcceptNum:=AcceptNum+1;CmbSendto.I tems.Add('套接口:'+inttostr(i)+' 地址:'+inet_ntoa(FSockAccept[i].sin_addr)+' 端口:'+inttostr(ntohs(FSockAccept[i].sin_port)));break;end;StatusBar1.Panels[2].Text:='共有:'+inttostr(AcceptNum)+'个连接';endelse if Event=FD_CLOSE thenbeginWSAAsyncSelect(AcceptSock[index], FormTCPUDP.Handle, 0, 0);if index<>0 thenbeginfor i:=0 to CmbSendto.I tems.Count-1 doif CmbSendto.I tems.Strings[i]= '套接口:'+inttostr(index)+' 地址:'+inet_ntoa(FSockAccept[index].sin_addr)+' 端口:'+inttostr(ntohs(FSockAccept[index].sin_port)) thenbeginCmbSendto.Items.Delete(i);break;end;CloseSocket(AcceptSock[index]);AcceptSockFlag[index]:=false;AcceptNum:=AcceptNum-1;StatusBar1.Panels[2].Text:='共有:'+inttostr(AcceptNum)+'个连接';end;end;end;procedure TFormTCPUDP.RBTCPClick(Sender: TObject);beginwriteiniBool(INIPath+'TCPUDP.ini','Setting','TCPStatus',true);RBServer.Enabled:=true;RBClient.Enabled:=true;if RBServer.Checked thenbeginPanelDest.Visible:=false;CmbSendto.Enabled:=true;endelsebeginPanelDest.Visible:=true;PanelLocal.Visible:=false;end;ChkBind.Enabled:=false;TcpFlag:=true;end;procedure TFormTCPUDP.RBUDPClick(Sender: TObject);beginwriteiniBool(INIPath+'TCPUDP.ini','Setting','TCPStatus',false);RBServer.Enabled:=false;RBClient.Enabled:=false;PanelDest.Visible:=true;TcpFlag:=false;ChkBind.Enabled:=true;CmbSendto.Enabled:=false;PanelLocal.Visible:=true;end;procedure TFormTCPUDP.BtnCloseClick(Sender: TObject);var i:integer;beginWinSockClose();BtnConnect.Enabled:=true;BtnClose.Enabled:=false;BtnSend.Enabled:=false;CmbSendto.Clear;for i:=0 to StatusBar1.Panels.count-1 doStatusBar1.Panels[i].Text:='';Statusbar1.Panels[0].Text:='已关闭套接字!!';end;procedure TFormTCPUDP.FormClose(Sender: TObject; var Action: TCloseAction); beginif BtnClose.Enabled then WinSockClose();end;procedure TFormTCPUDP.RBClientClick(Sender: TObject);beginwriteiniBool(INIPath+'TCPUDP.ini','Setting','ServerStatus',false);ServerFlag:=false;PanelDest.Visible:=true;CmbSendto.Enabled:=false;if Tcpflag thenPanelLocal.Visible:=falseelsePanelLocal.Visible:=true;end;procedure TFormTCPUDP.RBServerClick(Sender: TObject);beginwriteiniBool(INIPath+'TCPUDP.ini','Setting','ServerStatus',true);ServerFlag:=true;if Tcpflag thenbeginPanelDest.Visible:=false;CmbSendto.Enabled:=true;ChkBind.Enabled:=false;ChkBind.Checked:=true;endelseChkBind.Enabled:=true;PanelLocal.Visible:=true;end;procedure TFormTCPUDP.ChkBindClick(Sender: TObject);beginwriteiniBool(INIPath+'TCPUDP.ini','Setting','BindStatus',ChkBind.Checked); BindFlag:=ChkBind.Checked;end;procedure TFormTCPUDP.FormCreate(Sender: TObject);var i:integer;beginFirstFlag:=true;for i:=1 to MAX_ACCEPT doAcceptSockFlag[i]:=false;INIPath:=extractFilePath(ParamStr(0));end;procedure TFormTCPUDP.EditHostPortChange(Sender: TObject);beginwriteiniStr(INIPath+'TCPUDP.ini','Setting','LocalPort',EditHostPort.Text);end;procedure TFormTCPUDP.EditRemoteHostChange(Sender: TObject);beginwriteiniStr(INIPath+'TCPUDP.ini','Setting','RemoteHost',EditR emoteHost.Text); end;procedure TFormTCPUDP.EditRemotePortChange(Sender: TObject);beginwriteiniStr(INIPath+'TCPUDP.ini','Setting','RemotePort',EditRemotePort.Text); end;procedure TFormTCPUDP.FormActivate(Sender: TObject);beginif FirstFlag thenbeginFirstFlag:=false;ReadTCPUDPIni();end;end;procedure TFormTCPUDP.CmbSendToKeyPress(Sender: TObject; var Key: Char); beginkey:=chr(0);end;end.。
delphi实现websocket的⼀款⾮常强势的控件----sgcWebsocket 最近⼿头有个项⽬需要⽤到websocket,⾃⼰写⼀个显然不⼤可能,时间与精⼒也不允许,别⼈我不知道,我是⼀个⽐较懒的⼈,选择delphi编程就是因为他能偷懒,编程是体⼒活,⼀个好的项⽬是基于⼀个好的创新idea上的,别⼈想不到的创意,你想到了并⽤程序实现了,这是⼀个很厉害的⾏为。
于是在⽹上搜下看有没有开源的第三⽅控件。
您别说还真搜到⼏个,经过筛选,最后锁定sgcWebsocket。
选择它的原因很简单,⽹络协议⼀直在发展,只有他⽐较新,可能满⾜我们的需求。
⾸先找到是sgcWebsocket 4.1.0源码版本,尝试编译失败,因为sgcWebsocket是基于indy上⾯实现的,由于delphi7是9.0版本太旧了,需要更新到indy10。
更新完成之后尝试编译发现有个inherit;⽆法编译,不管他注释掉再说,安装成功后尝试了部分ws://与wss://的websocket发现可以成功通信,但是我的项⽬使⽤的是wss://的⾼版本的协议来通信的,⽆法通信成功。
不想对⽼版本做修改,这太累了,不死⼼看看有没有新的版本,您别说还真找到了sgcWebsocket 4.4.4 源码版,这时我才知道这个第三⽅控件作者⼀直是在维护的,差不多⼀个⽉左右会更新⼀次,并且是收费的,试⽤版提供的是dcu⽂件,看了下收费版源码的价格,好像也不贵,以后真的要长期⽤这个控件的话,可以出钱买⼀份,毕竟算是⽤⾦钱换时间吧,⽽且作者也⼀直在更新修复BUG。
好,⾔归正传,直接编译这个版本很顺利,⾼版本就是不⼀样,这个版本多了协议版本可以选择,选择了1.2版本,简单的配置下连接,发现与服务器wss://xxxxxx/socket.io/?transport=websocket通信成功。
成功是成功了,可是发现中⽂返回的是问好????,不显⽰中⽂,⽐较苦恼,最后折腾了⼀天,⼜是OD调试,⼜是搜索,最后发⾏居然还是indy版本的原因。
利用Delphi编写Socket通信程序一、前言随着计算机技术的不断发展,网络应用得到了越来越广泛的应用,而Socket编程作为网络编程中最基础、最重要的部分,具有非常重要的意义。
Delphi是一种构建Windows应用程序的高级集成开发工具,支持对象化设计、事件驱动、可视化编程等多种编程方式。
Delphi具有非常强大的GUI设计、数据访问、组件开发等特点,可以方便地应用到Socket编程中,实现Socket通信程序的开发。
本文就介绍利用Delphi编写Socket通信程序相关的知识。
二、Socket编程基础Socket是一种用于网络通信的编程接口,它把网络通信抽象成发送和接收数据的过程。
Socket编程分为客户端和服务器两部分,客户端负责向服务器发起连接请求,服务器则负责接收客户端的连接请求,并根据请求提供相应的服务。
Socket编程中,通常使用TCP/IP协议进行数据传输。
TCP协议是一种可靠的、面向连接的协议,它通过三次握手建立连接,保证数据的可靠性、完整性和顺序性。
而UDP协议则是一种不可靠的、无连接的协议,它不保证数据的可靠性和完整性,但具有数据传递速度快等特点。
通常,TCP协议适用于对数据可靠性要求较高的应用场景,如文件传输、邮件系统、即时通信等;而UDP协议适用于对数据传输速度要求较高的应用场景,如视频直播、网络游戏等。
三、利用Delphi编写Socket通信程序1. 创建Socket使用Delphi进行Socket编程,可通过TServerSocket和TClientSocket组件来实现。
TServerSocket组件用于创建服务器Socket,TClientSocket组件用于创建客户端Socket。
在Delphi中,可以通过在组件面板中拖拽TServerSocket或TClientSocket组件来创建Socket。
使用TServerSocket组件创建服务器Socket的示例代码如下:```delphiprocedure TMyForm.FormCreate(Sender: TObject);begin ServerSocket1.Port := 8888;ServerSocket1.Active := True;end;procedure TMyForm.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket);beginMemo1.Lines.Add(Socket.RemoteAddress + ' connected.');end;```上述代码中,将ServerSocket1的Port属性设置为8888,表示该服务器监听8888端口的连接请求。
Delphi的Socket编程详解ClientSocket和ServerSocket几个重要的属性:1.client和server都有port属性,需要一致才能互相通信2.client有Address属性,使用时填写对方(server)的IP地址几个重要的事件:client:OnRead事件,当client收到消息时在OnRead事件中可以获得server发送过来消息。
Server:OnClientRead事件,与上述client的作用相同发送信息:clien使用SocketClient1.Socket.SendBuf(char类型的数组,信息长度);server使用SocketServer1.Socket.Connection[0].SendBuf(char类型的数组,信息长度);接收信息clien使用SocketClient1.Socket.ReceiveBuf(char类型的数组,信息长度);server使用SocketServer1.Socket.Connection[0].ReceiveBuf(char类型的数组,信息长度);使用sockets:Socket控件让你建立一个利用TCP/IP和有关的协议与其他系统进行通信的应用。
使用Sockets,你能够读和写通过它连接的其他机器,而不用担心实际的网络软件的相关细节。
Sockets提供基于TCP/IP协议的连接。
Delphi提供通过网络服务器或客户应用程序去读和写其他的系统的功能。
一个服务或客户程序通常专注于一个单一的服务,如超文本传送协议(HTTP)或文件传输协议(FTP)。
使用serversockets,一个应用程序可以通过这些服务中的一个去连接一个希望使用服务的客户程序。
Clientsockets允许一个应用使用这些服务中的一个去连接提供这个服务的服务应用。
使用sockets去写应用程序,你必须理解下面这些知识:一、服务工具当你需要写网络服务或客户应用时,Sockets提供一种接合。
indy10 开发通用tcpserver例子Indy是一个流行的Delphi/C++Builder 网络编程库,它提供了一组组件和类来简化各种网络应用的开发,包括TCP/IP、UDP、HTTP、FTP、SMTP、POP3 等协议。
以下是一个使用Indy 的TIdTCPServer组件来开发一个通用TCP 服务器的简单例子。
这个例子使用Delphi 语言,但Indy 也支持C++Builder。
首先,确保你已经安装了Indy 库。
在Delphi 中,你可以通过包管理器来安装Indy。
然后,你可以使用以下代码来创建一个基本的TCP 服务器:delphiunit Unit1;interfaceusesWindows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, IdTCPServer, IdContext, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPServerThreads;typeTForm1 = class(TForm)IdTCPServer1: TIdTCPServer;procedure IdTCPServer1Connect(AContext: TIdContext);procedure IdTCPServer1Disconnect(AContext: TIdContext);procedure IdTCPServer1Execute(AContext: TIdContext);private{ Private declarations }public{ Public declarations }end;varForm1: TForm1;implementation{$R *.dfm}procedure TForm1.IdTCPServer1Connect(AContext: TIdContext);begin// 当一个客户端连接到服务器时调用此方法ShowMessage('Client connected.');end;procedure TForm1.IdTCPServer1Disconnect(AContext: TIdContext);begin// 当一个客户端断开连接时调用此方法ShowMessage('Client disconnected.');end;procedure TForm1.IdTCPServer1Execute(AContext: TIdContext);varData: TBytes;Size: Integer;begin// 当从客户端接收到数据时调用此方法// 这里简单地将接收到的数据回发给客户端trySize := AContext.Connection.IOHandler.InputStream.Read(Data,SizeOf(Data));if Size > 0thenbeginAContext.Connection.IOHandler.Write(Data);end;excepton E: Exception dobegin// 处理异常ShowMessage(E.Message);end;end;end;end.在这个例子中,我们创建了一个TIdTCPServer实例,并设置了三个事件处理程序:•OnConnect:当一个客户端连接到服务器时调用。
IdTcpServer/IdTcpClientIdTcpServeruses IdContext //需要引用属性,方法:IdTCPServer.Active :=True; //开启服务器IdTCPServer1.Bindings.Add.IP := '127.0.0.1';//绑定IP IdTCPServer1.Bindings.Add.Port := 7956;//绑定端口事件:OnConnect : 客户端连接成功触发OnDisConnect : 客户端断开触发OnExeCute : 收到客户端数据触发例子//像所有客户断发送数据varI: Integer;Context: TIdContext;beginwith IdTCPServer1.Contexts.LockList dotryfor I := 0 to Count - 1 dobeginContext := TIdContext(Items[I]);Context.Connection.IOHandler.Write('Hello,everybody!'); end;finallyIdTCPServer1.Contexts.UnlockList;end;end;//向某个客户发送数据varI: Integer;Context: TIdContext;beginwith IdTCPServer1.Contexts.LockList dotryfor I := 0 to Count - 1 dobeginContext := TIdContext(Items[I]);if Context.Binding.PeerIP <> '192.168.10.90' thencontinue;Context.Connection.IOHandler.Write('Hello!');end;finallyIdTCPServer1.Contexts.UnlockList;end;end;IdTcpClient属性,方法:ConnectTimeOut:连接超时Host:主机IP或域名IPVersion:Ip版本 ipv4 or ipv6Name:控件名Port:主机端口ReadTimeOut:读取超时IdTCPClient1.Connect; //连接服务端IdTCPClient1.Disconnect;//端开连接IdTCPClient1.Connected;//是否连接成功返回true 连接成功IdTCPClient1.IOHandler.WriteLn('aa');// 向服务端发送数据OnWork事件AWorkMode=wmRead 表示有收到数据在DelPhi2007 中使用Indy 的TCP连接教程(一)首先先说明下为什么要用 INDY10最新的indy10可以基于win32上的程(Fiber) API.什么叫Fiber API呢,这里是解释:纤程(Fiber) —可以从 32 位版本的 Windows? 中使用的轻量级线程处理对象—在很多方案中都很有用。
笔者在前一段的工作中,需要开发一套简单的网络数据传输程序。
由于平时常用Delphi做点开发,故此次也不例外。
Delphi 7中带有两套TCP Socket组件:Indy Socket组件(IdTC PClient和IdTCPServer)和Delphi原生的TCP Socket组件(ClientSocket和ServerSocket)。
但是,Borland已宣称ClientSocket和ServerSocket组件即将被废弃,建议用相应的Indy组件来代替。
因此,笔者使用了Indy。
本文在对Indy进行简要介绍的基础上,创建了一组简单的TCP Socket数据传输应用来演示了Indy的使用方法。
开放源代码的Internet组件集——Internet Direct(Indy)Internet Direct(Indy)是一组开放源代码的Internet组件,涵盖了几乎所有流行的Internet协议。
Indy用Delphi编写,被包含在Delphi 6,Kylix 1和C++ Builder 6及以上各个版本的Borland开发环境中。
Indy曾经叫做WinShoes(双关于WinSock——Windows 的Socket库),是由Chad Z. Hower领导的一群开发者构建的,可以从Indy的站点/indy上找到更多的信息并下载其新版本。
到笔者撰写本文时为止,Indy 的最新稳定版是9.0.14,Indy 10也进入了Beta测试阶段。
Delphi 7中所带的是Indy 9。
在其的组件面板上,一共安装有100多个Indy组件。
使用这些组件你可以开发基于各种协议的TCP客户和服务器应用程序,并处理相关的编码和安全问题。
你可以通过前缀Id来识别Indy组件。
Indy是阻塞式(Blocking)的当你使用Winsock开发网络应用程序时,从Socket中读取数据或者向Socket写入数据都是异步发生的,这样就不会阻断程序中其它代码的执行。
在收到数据时,Winsock会向应用程序发送相应的消息。
这种访问方式被称作非阻塞式连接,它要求你对事件作出响应,设置状态机,并通常还需要一个等待循环。
与通常的Winsock编程方法不同的是,Indy使用了阻塞式Socket调用方式。
阻塞式访问更像是文件存取。
当你读取数据,或是写入数据时,读取和写入函数将一直等到相应的操作完成后才返回。
比如说,发起网络连接只需调用Connect方法并等待它返回,如果该方法执行成功,在结束时就直接返回,如果未能成功执行,则会抛出相应的异常。
同文件访问不同的是,Socket调用可能会需要更长的时间,因为要读写的数据可能不会立即就能准备好(在很大程度上依赖于网络带宽)。
阻塞式Socket并非恶魔(Evil)长期以来,阻塞式Socket都遭到了毫无理由的攻击。
其实阻塞式Socket并非如通常所说的那样可怕。
这还要从Winsock的发展说起。
当Socket被从Unix移植到Windows时,一个严重的问题立即就出现了。
Unix支持fork,客户程序和服务器都能够fork新的进程,并启动这些进程,从而能够很方便地使用阻塞式Socket。
而Windows 3.x既不支持fork也不支持多线程,当使用阻塞式Socket时,用户界面就会被“锁住”而无法响应用户输入。
为克服Windows 3.x的这一缺陷,微软在Winsock中加入了异步扩展,以使Winsock 不会“锁住”应用程序的主线程(也是唯一的线程)。
然而,这需要了一种完全不同的编程方式。
于是有些人为了掩饰这一弱点,就开始强烈地诽谤阻塞式Socket。
当Win32出现的时候,它能够很好地支持线程。
但是既成的观念已经很难更改,并且说出去的话也无法收回,因此对阻塞式Socket的诽谤继续存在着。
事实上,阻塞式Socket仍然是Unix实现Socket的唯一方式,并且它工作得很好。
阻塞式Socket的优点归结起来,在Windows上使用阻塞式Socket开发应用程序具有如下优点:○ 编程简单——阻塞式Socket应用程序很容易编写。
所有的用户代码都写在同一个地方,并且顺序执行。
○ 容易向Unix移植——由于Unix也使用阻塞式Socket,编写可移植的代码就变得比较容易。
Indy就是利用这一点来实现其多平台支持而又单一源代码的设计。
○ 很好地利用了线程技术——阻塞式Socket是顺序执行的,其固有的封装特性使得它能够很容易地使用到线程中。
阻塞式Socket的弱点事物都具有两面性,阻塞式Socket也不例外。
它的一个主要的缺点就是使客户程序的用户界面“冻结”。
当在程序的主线程中进行阻塞式Socket调用时,由于要等待Socket调用完成并返回,这段时间就不能处理用户界面消息,使得Update、Repaint以及其它消息得不到及时响应,从而导致用户界面被“冻结”。
使用TIdAntiFreeze对抗“冻结”Indy使用一个特殊的组件TIdAntiFreeze来透明地解决客户程序用户界面“冻结”的问题。
TIdAntiFreeze在Indy内部定时中断对栈的调用,并在中断期间调用Application.ProcessMessages方法处理消息,而外部的Indy调用继续保存阻塞状态,就好像TIdAntiFreeze对象不存在一样。
你只要在程序中的任意地方添加一个TIdAntiFreeze对象,就能在客户程序中利用到阻塞式Socket的所有优点而避开它的一些显著缺点。
Indy使用了线程技术阻塞式Socekt通常都采用线程技术,Indy也是如此。
从最底层开始,Indy的设计都是线程化的。
因此用Indy创建服务器和客户程序跟在Unix下十分相似,并且Delphi的快速开发环境和Indy对WinSock的良好封装使得应用程序创建更加容易。
Indy服务器模型一个典型的Unix服务器有一个或多个监听进程,它们不停地监听进入的客户连接请求。
对于每一个需要服务的客户,都fork一个新进程来处理该客户的所有事务。
这样一个进程只处理一个客户连接,编程就变得十分容易。
Indy服务器工作原理同Unix服务器十分类似,只是Windows不像Unix那样支持fork,而是支持线程,因此Indy服务器为每一个客户连接分配一个线程。
图1显示了Indy服务器的工作原理。
Indy服务器组件创建一个同应用程序主线程分离的监听线程来监听客户连接请求,对于接受的每一个客户,都创建一个新的线程来为该客户提供服务,所有与这一客户相关的事务都由该线程来处理。
使用组件TIdThreadMgrPool,Indy还支持线程池。
图1 Indy服务器工作原理线程与Indy客户程序Indy客户端组件并未使用线程。
但是在一些高级的客户程序中,程序员可以在自定义的线程中使用Indy客户端组件,以使用户界面更加友好。
简单的Indy应用示例下面将创建一个简单的TCP客户程序和一个简单的TCP服务器来演示Indy的基本使用方法。
客户程序使用TCP协议同服务器连接,并向服务器发送用户所输入数据。
服务器支持两条命令:DATA和QUIT。
在DATA命令后跟随要发送的数据,并用空格将命令字DATA和数据分隔开。
表单布局建立一个项目组,添加一个客户程序项目和一个服务器项目。
客户程序和服务器程序的表单布局如同2和图3所示。
客户程序表单上放置了TIdTCPClient组件,服务器程序表单上放置了TIdTCPServer组件。
为防止客户程序“冻结”,还在其表单上放置TIdAntiFreeze 组件。
客户程序和服务器程序的表单上都放置有TListBox组件,用来显示通信记录。
图2 简单的TCP客户程序表单图 3 简单的TCP服务器程序表单客户程序代码客户程序片断如代码列表1所示。
代码列表1unit Unit1;interfaceusesWindows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, IdAntiFreezeBase, IdAntiFreeze, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient, StdCtrls;typeTForm1 = class(TForm)IdTCPClient: TIdTCPClient;IdAntiFreeze1: TIdAntiFreeze;Label1: TLabel;Label2: TLabel;Label3: TLabel;Label4: TLabel;EdtPort: TEdit;EdtHost: TEdit;EdtData: TEdit;BtnConnect: TButton;BtnSend: TButton;BtnDisconnect: TButton;Button4: TButton;LbLog: TListBox;procedure BtnConnectClick(Sender: TObject);procedure BtnSendClick(Sender: TObject);procedure BtnDisconnectClick(Sender: TObject); private{ Private declarations }public{ Public declarations }end;varForm1: TForm1;implementation{$R *.dfm}procedure TForm1.BtnConnectClick(Sender: TObject); beginIdTCPClient.Host := EdtHost.Text;IdTCPClient.Port := StrToInt(EdtPort.Text);LbLog.Items.Add('正在连接' + EdtHost.Text + '...');with IdTCPClient dobegintryConnect;tryLbLog.Items.Add(IOHandler.ReadLn);BtnConnect.Enabled := False;BtnSend.Enabled := True;BtnDisconnect.Enabled := True;exceptLbLog.Items.Add('远程主机无响应!');IdTCPClient.Disconnect();end;//end tryexceptLbLog.Items.Add('无法建立到' + EdtHost.Text + '的连接!'); end;//end tryend;//end withend;procedure TForm1.BtnDisconnectClick(Sender: TObject);//var// Received: string;beginLbLog.Items.Add('QUIT');tryIdTCPClient.IOHandler.WriteLn('QUIT');finallyIdTCPClient.Disconnect();LbLog.Items.Add('同主机' + EdtHost.Text + ' 的连接已断开!');BtnConnect.Enabled := True;BtnSend.Enabled := False;BtnDisconnect.Enabled := False;end;//end tryend;procedure TForm1.BtnSendClick(Sender: TObject);beginLbLog.Items.Add('DATA ' + EdtData.Text);with IdTCPClient dobegintryIOHandler.WriteLn('DATA ' + EdtData.Text);LbLog.Items.Add(IOHandler.ReadLn)exceptLbLog.Items.Add('发送数据失败!');IdTCPClient.Disconnect();LbLog.Items.Add('同主机' + EdtHost.Text + ' 的连接已断开!');BtnConnect.Enabled := True;BtnSend.Enabled := False;BtnDisconnect.Enabled := False;end;//end tryend;//end withend;end.在“连接”按钮事件响应过程中,首先根据用户输入设置IdTCPClient的主机和端口,并调用IdTCPClient的Connect方法向服务器发出连接请求。