在开发TCP长连接服务时,定期发送心跳是保持连接不断开的常见做法。通过心跳,可以及时检测到连接异常并采取相应的处理措施。在Go语言中,我们可以方便地使用GoFrame框架的gtimer模块来优雅地实现TCP长连接心跳功能。
为什么要发送心跳?
在实际应用中,建立的TCP连接可能会因为各种原因被异常中断,例如:
- 网络故障或不稳定
- 防火墙或者NAT超时,将连接断开
- 服务器或客户端程序意外退出
- 连接被人为地关闭
如果连接中断,而我们的程序没有及时发现并处理,就可能导致数据丢失或者业务逻辑异常。通过定期发送心跳数据包,并在对方收到心跳后响应,我们就可以判断连接是否正常,是否需要重连。这样可以最大程度保证业务的连续性。
gtimer模块介绍
gtimer是GoFrame框架提供的定时任务管理模块。它支持多种定时任务类型,包括:
- 一次性定时任务: 只执行一次
- 循环定时任务: 每隔一段时间执行一次,到达指定次数后停止
- 永久定时任务: 每隔一段时间执行一次,永不停止
gtimer使用也非常简单,核心就是AddOnce、AddTimes和Add三个方法。感兴趣的读者可以去官网文档详细了解。这里我们主要使用Add方法来创建一个永久定时任务,用于TCP连接保活。
实现TCP心跳
基于TCP的网络应用通常分为客户端和服务端。虽然理论上讲,双方都可以发送心跳,但通常我们让客户端作为心跳的发起方,服务端进行心跳响应,这样可以尽量减轻服务端的压力。
服务端
我们先来看看如何实现服务端的心跳处理逻辑。假设我们已经建立了基于TCP的连接。
func (s *Server) handleConnection(conn gnet.Conn) {
// 在下一个心跳间隔超时前写入心跳响应数据包
gtimer.Add(s.HeartbeatInterval, func() {
j := gjson.New(g.Map{"pong": 11})
err := conn.AsyncWrite(j.MustToJson())
if err != nil {
return
}
})
}
客户端
再来看客户端如何发送心跳。假设我们也已建立连接,并将net.Conn保存为gnet.Conn类型的变量conn。
func (c *Client) startHeartbeat() {
// 启动定时器,每隔一个心跳间隔发送一次心跳
gtimer.Add(c.HeartbeatTimer, func() {
j := gjson.New(g.Map{"ping": 10})
if err := c.Conn.AsyncWrite(j.MustToJson()); err != nil {
err = c.Conn.Close()
if err != nil {
return
}
gtimer.Exit()
}
})
}