使用GoFrame的gtimer优雅地实现TCP长连接心跳

165 阅读2分钟

在开发TCP长连接服务时,定期发送心跳是保持连接不断开的常见做法。通过心跳,可以及时检测到连接异常并采取相应的处理措施。在Go语言中,我们可以方便地使用GoFrame框架的gtimer模块来优雅地实现TCP长连接心跳功能。

为什么要发送心跳?

在实际应用中,建立的TCP连接可能会因为各种原因被异常中断,例如:

  1. 网络故障或不稳定
  2. 防火墙或者NAT超时,将连接断开
  3. 服务器或客户端程序意外退出
  4. 连接被人为地关闭

如果连接中断,而我们的程序没有及时发现并处理,就可能导致数据丢失或者业务逻辑异常。通过定期发送心跳数据包,并在对方收到心跳后响应,我们就可以判断连接是否正常,是否需要重连。这样可以最大程度保证业务的连续性。

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()
       }
    })
}