很久没有专门用过delphi 了,大概有二三年了吧,最近接手一与银行有关的项目,对方银行使用的是unix操作系统,用socket与项目程序进行通信。而这边项目肯定是要在windows下跑的,由于考虑到unix下socket采用的是阻塞式通信,由于项目紧张,思索再三,决定采用delphi的indy组件来编写服务器端的通信部分。其中通信的格式如下定义:
信息段 |
类型 |
长度(单位:字节) |
说明 |
包长 |
字符 |
6 |
左对齐,右补空格,包长指报文体长度(不包含交易码长度及目标保险公司代码长度)。 |
交易码 |
字符 |
7 |
左对齐,右补空格,参见下表 |
目标保险公司代码 |
字符 |
6 |
默认为006 |
报文体 |
字符 |
|
XML报文,不定长 |
首先,简单用indy做了一个对端(客户端)模拟程序,按照指定的通信协议,用socket发送数据包,
procedure TForm1.Button1Click(Sender: TObject); var msSend,msRecv:string; amp :TStream; iRet:integer; a:STring; ia:String; begin msSend:= mmoinput.Text; //发送文本框中的内容 //计算XML包文的长度,并按照协议保持六位数 ia:=IntToStr(Length(msSend)); if Length(ia) =5 then ia:=ia+' ' else if Length(ia) =4 then ia:=ia+' ' else if Length(ia) =3 then ia:=ia+' ' else if Length(ia) =2 then ia:=ia+' ' else if Length(ia) =1 then ia:=ia+' ' else if Length(ia) >6 then ia:=leftstr(ia,6); mssend:= ia + edt1.Text +'006 '+mssend; //按通信协议拼接需要通过socket发送的内容 iRet:=SendStream(IdTCPClient1,trim(edit1.Text),strtoint(trim(edit2.Text)),msSend,msRecv); //调用socket通信函数 mmoOutput.Text:=''; mmoOutput.Lines.Add(msRecv); //把服务器返回的内容显示出来 end; function TForm1.SendStream(const AIdTCPClient:TIdTCPClient ;const Aip:string;const Aport:integer ;var AsendStream,ARecvStream:string):integer; begin result:=-1; if AIdTCPClient=nil then exit; if not AIdTCPClient.Connected then begin try AIdTCPClient.Host:=Aip ; AIdTCPClient.Port:=Aport; AIdTCPClient.Connect(200); //TCP建立连接 except result:=-2; AIdTCPClient.Disconnect; exit; end; end; try aidtcpclient.Write(AsendStream); //直接把准备好发送的内容,写入缓冲区即可 except result:=-3; AIdTCPClient.Disconnect; exit; end; try //接收 ARecvStream:=aidtcpclient.CurrentReadBuffer(); //把服务器端返回的内容接收到。此处是直接把接收缓冲区的内容全部收完,没有按通信协议进行指定接收 result:=1; except result:=-4; AIdTCPClient.Disconnect; exit; end; end;
服务器端的接收程序如下。
procedure TdmMain.IdTCPServer1Execute(AThread: TIdPeerThread); var strlog,strcode:string; ileng :integer; tmpException: Exception; begin if Athread=nil then exit; if Not AThread.Connection.Connected then exit; if Athread.Terminated then exit; try try //接收流 try strlog:=AThread.Connection.ReadString(19);//首先接收指定的19个字符,如前面协议中定义的 if strlog <> '' then begin ileng:=StrToIntDef(trim(LeftStr(strlog,6)),0); //取出前6个字符,即正式包文体的内容的长度 strcode:=MidStr(strlog,7,7); //取出后7位,分析此数据包的含义 end; (AThread.Data as TThreadData).FStrRevc:= athread.Connection.ReadString(ileng);//接收指定长度的报文体 except //返回,解包错误 MakeErrorXmlStream((AThread.Data as TThreadData).FStrSend); end; if ((AThread.Data as TThreadData).FStrRevc = '') or (Length((AThread.Data as TThreadData).FStrRevc) <> ileng) then //检查收到的数据包是否完整 begin MakeErrorXmlStream((AThread.Data as TThreadData).FStrSend); end else begin //处理接收到的流,并返回要发送的流 KentDealStream(strcode,(AThread.Data as TThreadData).FStrRevc ,(AThread.Data as TThreadData).FStrSend); end; //按指定的通信格式,拼接包文 strlog:=inttostr(Length((AThread.Data as TThreadData).FStrSend)); if Length(strlog) =5 then strlog:=strlog+' ' else if Length(strlog) =4 then strlog:=strlog+' ' else if Length(strlog) =3 then strlog:=strlog+' ' else if Length(strlog) =2 then strlog:=strlog+' ' else if Length(strlog) =1 then strlog:=strlog+' ' else if Length(strlog) >6 then strlog:=leftstr(strlog,6); (AThread.Data as TThreadData).FStrSend:= strlog+ strcode+'006 '+ (AThread.Data as TThreadData).FStrSend; AThread.Connection.Write((AThread.Data as TThreadData).FStrSend);//发送数据 finally end; except AThread.Connection.Disconnect; tmpException := ExceptObject as Exception; oLogfile.addtolog('IdTCPServer1Execute 异常!' +' 原因:'+TmpException.Message); end; end;
由于indy采取的是由unix相同的阻塞式通信,采用多线程实现,所以使用起来比较简单。indy把socket已经封装得比较完善,包括收发字符串、二进制流等。
附上indy 的 TIdTCPClient 的常用方法函数:
比方说这个读取缓冲区的数据,就有很多种方法。相对于TTcpClient的几种方法来说,TIdTCPClient确实提供了多种选择,不仔细研究真的容易糊涂(其实我比较喜欢用CurrentReadBuffer):
1、ReadFromStack
原型:function ReadFromStack(const ARaiseExceptionIfDisconnected: boolean; const ATimeout: integer; const AUseBuffer: boolean; ADestStream: TIdBuffer): integer; virtual;
用于判断缓冲区里是否还有数据可读,返回值:Integer - Number of bytes read.
2、CurrentReadBuffer
原型:function CurrentReadBuffer: string;
用于读取Socket数据到缓冲区,注意返回为String类型,如果直接显示该String的数据,对于/0之后的数据可能看不到,因此要读取所有的数据,还必须利用CurrentReadBufferSize()判断该String的长度。
返回值:String - Contents of the Indy buffer.
3、GetResponse
原型:function GetResponse(const AAllowedResponses: Array of SmallInt): SmallInt; virtual;
对于简单的命令应答可以使用这个方法获取应答消息,返回值:SmallInt - The numeric response number.
4、ReadBuffer
原型:procedure ReadBuffer(var ABuffer; const AByteCount: Longint);
读取指定数目的字节到缓冲区ABuffer,注意它会调用 ReadFromStack 以检查缓冲区里的数据是否少于AByteCount
5、ReadInteger
原型:function ReadInteger(const AConvert: boolean): Integer;
从缓冲区中读取整型数据,它会调用ReadBuffer
6、ReadLn
原型:function ReadLn(const ATerminator: string; const ATimeout: integer): string; virtual;
读取移行记录,带有一个TimeOut属性,以防止在读不到新行时死循环。返回值:String - Line read from the buffer.
注意行分隔符可能是以下几种:
#0 - Default Line Feed (#10)
LF - Line Feed (#10)
CR - Carriage Return (#13)
EOL - End-of-line (Carriage Return Line Feed)
7、ReadLnWait:
原型:function ReadLnWait: string;
很像ReadLn,但它会一直傻傻的等待
8、ReadSmallInt
原型:function ReadSmallInt(const AConvert: boolean): SmallInt;
9、ReadStream
原型:procedure ReadStream(AStream: TStream; AByteCount: LongInt; const AReadUntilDisconnect: boolean);
10、ReadString
原型:function ReadString(const ABytes: integer): string;
与CurrentReadBuffer的不同在于它读取指定长度的字符串
IdTCPClient和IdTCPServer主要属性
2008-10-13 12:40
IdTCPClient属性
1 : IOHandler 如果有相应的输入/输出操作,那么IOHandler相对应的组件或
接口将提供一个虚拟/抽象的输入/输出接口给相应的网络连接
2 : Intercept 如果有一个网络连接正在使用,那么Intercept 提供的组件或接
口将可以拦截相应的网络数据流中的数据
3 : BoundIP 指定使用IdTCPClient组件的计算机系统的IP地址,也就是说,其
中是空白那么什么样的计算机都可以使用,但如果指定的IP地址为239.126.12.2,那么
就只有IP地址为239.126.12.2的计算机可以使用它.
4 : BoundPor 网络端口的概念,指定使用IdTCPClient组件的计算机系统网络
端口,也就是说,如果BoundPort中指定了以整数为端口的网络端口,那么通讯时就只能
使用这个商品进行通讯
5 : Host 如果不是使用BoundIP中的地址起先通讯,那么Host就是诣将
要通讯的计算机系统的名称或它的IP地址,可以是计算机名也可以是IP地址.
6 : Port 与BoundPort的概念是基本一样的,只是它与HOst相配合来决
定IdTCPClient组件要与哪一个计算机系统中的什么样的网络通讯端口进行通讯
方法
IdTCPClient 主要使
用Write,WriteBuffer,WriteCardinal,WriteFile,WriteHeader,WriteInteger,WriteL
n,WriteRFCReply,WriteRFCStrings,WriteSmallInt,WriteStream,WriteStrings.
通过它们 IdTCPClient 可以发送非常多的类型的数据到相应的服务端,而这些都是非
常的简单
如: Write的函数说明是:
procedure Write(Const Aout : String) ; Virtual;
它的目的很简单,就是发送一个字符串到相应的服务端.其中Aout就是被发送的字符串.
而 WriteLn的函数说明是:
procedure WriteLn(Const Aout : String = ''); Virtual;
它的目的就是在Write的基础上,在被发送的字符串后加入相应的回车控件符.
注意 : 虽然IdTCPClient可以直接使用,它不可以被单独的使用,它必须与相应
的IdTCPServer组件相配合才能发挥特定的作用,当然,如果相应的网络系统中具有这
样的TCP服务器应用,那么IdTCPClient也可以直接与它们进行通讯.
IdTCPServer
属性
1 : Bindings 包括了相应的服务端所允许的所有的Socket(套接字)的相关信
息,比如它们的IP地址和端口号
2 : ListenQueue 是规定在服务端失效之前最多可以允许的监听网络连接的纯种
数目.默认情况下它的值是15,但这个值可以根据自己的需要和系统的最大承受能力来
进行改变
3 : MaxConnects 它表示的内容就是服务端可以承载的最大的网络连接数目,如果
它的值是0,那么就表明它没有最大的限制值,可以使用任意数目的连接.
4 : MaxConnectionReply 与MaxConnects相配合,当连接数目大
于MaxConnects的数目时,它将返回一个错误的信息,而这个信息是由TIdRFCReply
类所定义的
5 : ThreadMgr 就是使用相应的 ThreadMgr 组件来对于网络连接线程进行管
理.