最近有個(gè)項(xiàng)目需要用到socket通信,對(duì)于socket的網(wǎng)絡(luò)異常處理(程序異常退出或者網(wǎng)絡(luò)掉了)及重連糾結(jié)了好久,網(wǎng)上雖然有很多資料,但是都是從一個(gè)地方轉(zhuǎn)過(guò)來(lái)的,不夠詳細(xì),查了很久的資料才弄出來(lái)的,原來(lái)的出處給忘了。
環(huán)境:delphi7+indy控件(dephi7自帶) 工作需要才用delphi7,建議使用delphi2007及以上版本,delphi2007里面帶的indy控件版本是10.0,修復(fù)了很多以前的bug
使用心跳包的理由:(ps:以下理由是抄的,感覺意思和我當(dāng)初的初衷是一樣的)
有開發(fā)網(wǎng)絡(luò)應(yīng)用經(jīng)歷的人都知道,網(wǎng)絡(luò)中的接收和發(fā)送數(shù)據(jù)都是使用WINDOWS中的SOCKET進(jìn)行實(shí)現(xiàn)。但是如果此套接字已經(jīng)斷開,那發(fā)送數(shù)據(jù)和接收數(shù)據(jù)的時(shí)候就一定會(huì)有問題??墒侨绾闻袛噙@個(gè)套接字是否還可以使用呢?
有人一定想到使用Send函數(shù)中的返回結(jié)果來(lái)進(jìn)行判斷。如果返回的長(zhǎng)度和自己發(fā)送出去的長(zhǎng)度一致,那就說(shuō)明這個(gè)套接字是可用的,否則此套接字一定出現(xiàn)了問題。但是我們并不是無(wú)時(shí)無(wú)刻的發(fā)送數(shù)據(jù)呀。如何解決呢?
其實(shí)TCP中已經(jīng)為我們實(shí)現(xiàn)了一個(gè)叫做心跳的機(jī)制。如果你設(shè)置了心跳,那TCP就會(huì)在一定的時(shí)間(比如你設(shè)置的是3秒鐘)內(nèi)發(fā)送你設(shè)置的次數(shù)的心跳(比如說(shuō)2次),并且此信息不會(huì)影響你自己定義的協(xié)議。
原理網(wǎng)上都有,就是使用setsockopt函數(shù)和WSAIoctl函數(shù),網(wǎng)上光介紹了怎么使用心跳機(jī)制,沒有說(shuō)處理辦法。設(shè)置好心跳后,TCP就會(huì)在一定的時(shí)間(比如你設(shè)置的是3秒鐘)內(nèi)發(fā)送你設(shè)置的次數(shù)的心跳(比如說(shuō)2次),如果網(wǎng)絡(luò)異常(程序異常退出或者網(wǎng)絡(luò)掉了),心跳就會(huì)拋出一個(gè)錯(cuò)誤異常,我們只要知道在哪兒捕獲異常,就能知道網(wǎng)絡(luò)情況以及何時(shí)進(jìn)行重連了。
服務(wù)端的心跳處理(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 數(shù)據(jù)結(jié)構(gòu)
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事件里面就能捕獲到這個(gè)異常,并進(jìn)行異常處理了
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;
客戶端的異常捕捉,我是放在客戶端讀取服務(wù)端發(fā)送過(guò)來(lái)數(shù)據(jù)的線程里面
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;//這兒?jiǎn)?dòng)定時(shí)器重連服務(wù)端
end;
end;
end;
ThreadDestroy;
end;
聯(lián)系客服