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;