最近項目中需要判定客戶端是否還在線,需要用到心跳檢測機制。這里做個筆記總結一下。
心跳檢測機制:
網絡中接收和發送數據都是通過操作系統的socket實現的。但是如果套接字已經斷開,那發送和接收數據就會出問題。
但如何判斷套接字是否斷開了呢?這就需要建立一種機制,能夠檢測通信對方是否還存活。如果已經斷開,就要釋放資源。這種機制通常采用心跳檢測實現。
所謂的“心跳”就是定時發送一個自定義的結構體(心跳包或心跳幀),讓對方知道自己“在線”,以確保鏈接的有效性。心跳檢測規定定時發送心跳檢測數據包,接收方接心跳包后回復,否則認為連接斷開。
一、Netty心跳檢測方式
1、pipeline加入IdleStateHandler
Netty提供了心跳檢測類IdleStateHandler,它有三個參數,分別是讀超時時間、寫超時時間、讀寫超時時間。
1)readerIdleTime:讀超時時間;
2)writerIdleTime:寫超時時間;
3)allIdleTime:所有類型超時時間;
這里最重要是的readerIdleTime,當設置了readerIdleTime以后,服務端server會每隔readerIdleTime時間去檢查一次channelRead方法被調用的情況,如果在readerIdleTime時間內該channel上的channelRead()方法沒有被觸發,就會調用userEventTriggered方法。
//讀超時時間設置為10s,0表示不監控 ch.pipeline().addLast(new IdleStateHandler(10, 0, 0, TimeUnit.SECONDS)); //加入處理事件 ch.pipeline().addLast(new ServerHeartBeat());
2、重寫userEventTriggered方法
重寫ChannelInboundHandlerAdapter處理類的userEventTriggered方法,在方法中處理idleEvent.state() == IdleState.READER_IDLE情況。
public class ServerHeartBeat extends ChannelInboundHandlerAdapter { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { if (evt instanceof IdleStateEvent) {//超時事件 IdleStateEvent idleEvent = (IdleStateEvent) evt; if (idleEvent.state() == IdleState.READER_IDLE) {//讀 ctx.channel().close(); } else if (idleEvent.state() == IdleState.WRITER_IDLE) {//寫 } else if (idleEvent.state() == IdleState.ALL_IDLE) {//全部 } } super.userEventTriggered(ctx, evt); } }
二、TCP連接心跳檢測方式
1、TCP心跳機制
上面采用的是Netty的心跳檢測機制,屬於應用層自定義的心跳檢測機制。
應用層實現心跳機制好處:
一是靈活, 應用層心跳可以自由定義,可實現各時間間隔(秒級、毫秒級)的檢測,包里還可以攜帶額外的信息。
二是通用, 應用層的心跳不依賴於底層協議。如果后期想把TCP改為UDP,協議層不提供心跳機制了,但應用層的心跳依舊是通用的。
應用層實現心跳機制壞處:
一是增加開發量, 由於使用特定的網絡框架, 還可能很增加代碼結構的復雜度。
二是額外增加了網絡通信數據包,流量消耗更大。
TCP協議本身也實現了心跳檢測機制。我們也可以使用TCP的心跳機制檢測連接是否斷開。
如果設置了心跳,TCP會在一定時間(比如設置的是3秒鍾)內發送設置的次數的心跳(比如說2次),並且該心跳信息不會影響自己定義的協議。
2、TCP心跳設置
TCP協議自帶的保活功能,使用起來很簡單。Linux環境下,修改/etc/sysctl.conf文件
添加以下內容:
#表示當keepalive起用的時候,TCP發送keepalive消息的頻度。缺省是7200秒(2小時),改為5秒鍾。 net.ipv4.tcp_keepalive_time = 5 #如果對方不予應答,探測包的發送次數 net.ipv4.tcp_keepalive_probes = 5 #探測消息發送的頻率 net.ipv4.tcp_keepalive_intvl = 1
這個是修改的linux tcp發送探測包配置的。修改完成后輸入以下命令生效
/sbin/sysctl -p /sbin/sysctl -w net.ipv4.route.flush=1
還可以使用echo的方式修改,命令如下:
#echo 5 > /proc/sys/net/ipv4/tcp_keepalive_time #echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes #echo 1 > /proc/sys/net/ipv4/tcp_keepalive_intvl
修改后查看下參數,驗證設置是否已經生效。
#cat /proc/sys/net/ipv4/tcp_keepalive_time #cat /proc/sys/net/ipv4/tcp_keepalive_probes #cat /proc/sys/net/ipv4/tcp_keepalive_intvl
這樣設置完成以后,每次客戶端斷開連接后5秒后會執行以下方法。
3、使用Netty,重寫handlerRemoved方法
public class TcpServerHandler extends SimpleChannelInboundHandler<ByteBuf>{ @Override public void handlerRemoved(ChannelHandlerContext ctx) throw Exception{ //執行客戶端斷開連接后的業務操作 } }
這里的時間設置不一定適合各業務場景,需要根據具體的業務設置合適的時間間隔。有時候網絡不穩定,一個探測包沒有探測到,系統就自動斷開連接了。
三、使用redis保存連接
服務端收到客戶端發送的心跳數據包后,使用redis保存,同時設置超時時間,一旦超時,觸發超時事件,表示客戶端連接出問題了。
比如:心跳時間為60s,那么服務器端收到客戶端心跳數據包后,保存到redis,並設置超時事件為70s(根據實際情況,需要大於心跳時間),一旦超時觸發超時事件,進行連接斷開邏輯處理。