delphi idtcpclient和idtcpserver的心跳包
最近有个项目需要用到socket通信,对于socket的网络异常处理(程序异常退出或者网络掉了)及重连纠结了好久,网上虽然有很多资料,但是都是从一个地方转过来的,不够详细,查了很久的资料才弄出来的,原来的出处给忘了。
环境:delphi7+indy控件(dephi7自带) 工作需要才用delphi7,建议使用delphi2007及以上版本,delphi2007里面带的indy控件版本是10.0,修复了很多以前的bug
使用心跳包的理由:(ps:以下理由是抄的,感觉意思和我当初的初衷是一样的)
有开发网络应用经历的人都知道,网络中的接收和发送数据都是使用WINDOWS中的SOCKET进行实现。但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题。可是如何判断这个套接字是否还可以使用呢?
有人一定想到使用Send函数中的返回结果来进行判断。如果返回的长度和自己发送出去的长度一致,那就说明这个套接字是可用的,否则此套接字一定出现了问题。但是我们并不是无时无刻的发送数据呀。如何解决呢?
其实TCP中已经为我们实现了一个叫做心跳的机制。如果你设置了心跳,那TCP就会在一定的时间(比如你设置的是3秒钟)内发送你设置的次数的心跳(比如说2次),并且此信息不会影响你自己定义的协议。
原理网上都有,就是使用setsockopt函数和WSAIoctl函数,网上光介绍了怎么使用心跳机制,没有说处理办法。设置好心跳后,TCP就会在一定的时间(比如你设置的是3秒钟)内发送你设置的次数的心跳(比如说2次),如果网络异常(程序异常退出或者网络掉了),心跳就会抛出一个错误异常,我们只要知道在哪儿捕获异常,就能知道网络情况以及何时进行重连了。
服务端的心跳处理(idtcpserver)
//定义心跳常量
Const
IOC_IN = $80000000;
IOC_VENDOR =
$18000000;
IOC_out = $40000000;
SIO_KEEPALIVE_VALS = IOC_IN
or IOC_VENDOR or 4;
DATA_BUFSIZE = 8192;
//定义 KeepAlive
数据结构
Type
TTCP_KEEPALIVE = packed record
onoff: integer;
keepalivetime:
integer;
keepaliveinterval: integer;
end;
在idtcpserver的OnConnect事件里面加入以下代码:
var
opt:Integer;
klive, outKlive: TTCP_KEEPALIVE;
begin
opt
:= 1;
if
setsockopt(AThread.Connection.Socket.Binding.Handle,SOL_SOCKET, SO_KEEPALIVE,
@opt, SizeOf(opt)) <> 0 then
begin
Showmessage('setsockopt KeepAlive
Error!');
end;
klive.onoff :=
1;
klive.keepalivetime := 3000;
klive.keepaliveinterval := 1;
if
WSAIoctl(AThread.Connection.Socket.Binding.Handle, SIO_KEEPALIVE_VALS,
@klive,
SizeOf(TTCP_KEEPALIVE),
@outKlive,
SizeOf(TTCP_KEEPALIVE), @opt,0,nil) = SOCKET_ERROR then
begin
Showmessage('WSAIoctl KeepAlive
Error!');
end
end
在idtcpserver的OnException事件里面就能捕获到这个异常,并进行异常处理了
procedure
Tfrmmain.IdTCPServer1Exception(AThread: TIdPeerThread;
AException:
Exception);
begin
suiMemo1.Lines.Add('客户端'+inttostr(athread.handle)+'异常断开');
if
AThread.Connection.Connected then
AThread.Connection.Disconnect;
end;
客户端的心跳处理(idtcpclient)
procedure
Tfrmmain.IdTCPClient1Connected(Sender: TObject);
var
opt:Integer;
klive, outKlive:
TTCP_KEEPALIVE;
begin
opt := 1;
if setsockopt(IdTCPClient1.Socket.Binding.Handle,SOL_SOCKET, SO_KEEPALIVE, @opt,
SizeOf(opt)) <> 0 then
begin
Showmessage('setsockopt KeepAlive
Error!');
end;
klive.onoff :=
1;
klive.keepalivetime := 3000;
klive.keepaliveinterval := 1;
if
WSAIoctl(IdTCPClient1.Socket.Binding.Handle, SIO_KEEPALIVE_VALS,
@klive,
SizeOf(TTCP_KEEPALIVE),
@outKlive,
SizeOf(TTCP_KEEPALIVE), @opt,0,nil) = SOCKET_ERROR then
begin
Showmessage('WSAIoctl KeepAlive
Error!');
end;
end;
客户端的异常捕捉,我是放在客户端读取服务端发送过来数据的线程里面
procedure
TReadThread.Execute;
var
TestBuffer:TSendDataNet;
begin
{ Place thread code here }
frmmain.log('Client Begin reading...');
while permit do
begin
try
frmmain.IdTCPClient1.ReadBuffer(TestBuffer,SizeOf(TestBuffer));
sleep(100);
except
on
e:Exception do
begin
permit:=False;
if
frmmain.IdTCPClient1.Connected then
frmmain.IdTCPClient1.Disconnect;
frmmain.RzStatusPane1.Caption:='断开;
frmmain.LinkTimer.Enabled:=True;//这儿启动定时器重连服务端
end;
end;
end;
ThreadDestroy;
end;