什么是心跳检测:

心跳检测(Heartbeat Detection)是一种用于监控网络连接、系统状态或服务可用性的技术。其主要目的是确保设备、应用程序或服务在正常运行,并能够及时发现故障或连接问题。以下是心跳检测的主要概念和应用场景:

1. 基本概念

  • 定期发送信号:心跳检测通常涉及定期发送小的数据包(心跳包)以确认系统或服务的可用性。发送方会在特定的时间间隔内向接收方发送心跳信号。

  • 响应机制:接收方会对心跳信号进行响应(例如,返回确认消息)。如果发送方在规定的时间内未收到响应,则可以认为连接可能存在问题。

2. 主要用途

  • 检测连接状态:心跳检测可以用于检测客户端和服务器之间的连接状态,确保在网络通信中保持连接的活性。

  • 故障检测:通过心跳检测,可以及时发现故障和中断。例如,服务器可以监控其组件或其他服务,确保它们在正常运行。如果某个组件未能按时发送心跳信号,系统可以触发警报或执行恢复操作。

  • 负载均衡:在负载均衡的环境中,心跳检测可用于监控后端服务器的健康状态。负载均衡器可以在服务器出现故障时自动将流量转移到其他可用的服务器。

3. 实现方式

  • 心跳包:心跳包通常包含少量数据,以减小带宽消耗。包的内容可能包括发送者的ID、时间戳、状态码等信息。

  • 超时机制:如果发送方在特定时间内未收到响应,通常会认为连接已断开,并采取相应的错误处理措施,比如重连或切换到备用系统。

  • 时间间隔:心跳检测的发送频率可以根据具体应用场景的需求进行调整。频率过高可能导致网络负担,而频率过低可能导致无法及时发现问题。

4. 使用场景

  • 分布式系统:在微服务架构中,各个服务之间可以使用心跳检测来确认彼此的状态。

  • 实时应用:例如,在线游戏、聊天应用和视频会议等需要保持实时连接的应用,通常会实现心跳机制以确保连接活跃。

  • 服务器监控:数据中心和云服务提供商会使用心跳检测来监控服务器和虚拟机的运行状态。

5. 注意事项

  • 网络负载:心跳检测的设计需要权衡信号频率和带宽消耗,以避免对网络造成过大的负担。

  • 故障处理:在实现心跳检测时,需要合理设计错误处理机制,以便在连接丢失时能够快速恢复。

心跳检测是一种简单而有效的方式,用于确保系统或服务的正常运行,通过定期的健康检查来及时发现和处理问题。

实现步骤总结:

服务端:

1.编写服务端启动逻辑

2.定义对应的心跳包结构

3.定义对应的定时发送心跳包机制

4.定义接收客户端反馈心跳包机制

客户端:

1.编写客户端访问逻辑

2.定义接收服务端的逻辑

3.定义发送反馈服务端心跳逻辑

运用到的特殊函数解析:

1.time.NewTicker()函数:

time.NewTicker() 是 Go 标准库中 time 包的一个函数,用于创建一个定时器,它会以指定的时间间隔重复触发一个事件。它返回一个指向 Ticker 的指针,该 Ticker 结构体包含一个 C 字段(一个通道),每当时间间隔到达时,当前时间会被发送到这个通道。

ticker.C 是一个通道,每当时间间隔到达时,会从这个通道中收到一个时间值。

time.NewTicker 的作用

time.NewTicker 函数用于创建一个新的 Ticker 实例。它返回一个 Ticker 结构体和一个通道,用于接收时间信号。调用者可以在 goroutine 中通过读取通道来执行定期任务。

time.NewTicker 的函数签名

func NewTicker(d Duration) *Ticker
  • d Duration:指定时间间隔,表示每隔多长时间发送一次信号。可以使用 time.Secondtime.Millisecond 等来设置。
  • 返回值:返回一个指向 Ticker 的指针,该指针包含一个接收时间信号的通道 C

内部定义:

// NewTicker returns a new Ticker containing a channel that will send
// the current time on the channel after each tick. The period of the
// ticks is specified by the duration argument. The ticker will adjust
// the time interval or drop ticks to make up for slow receivers.
// The duration d must be greater than zero; if not, NewTicker will
// panic. Stop the ticker to release associated resources.
func NewTicker(d Duration) *Ticker {
	if d <= 0 {
		panic("non-positive interval for NewTicker")
	}
	// Give the channel a 1-element time buffer.
	// If the client falls behind while reading, we drop ticks
	// on the floor until the client catches up.
	c := make(chan Time, 1)
	t := &Ticker{
		C: c,
		r: runtimeTimer{
			when:   when(d),
			period: int64(d),
			f:      sendTime,
			arg:    c,
		},
	}
	startTimer(&t.r)
	return t
}
// A Ticker holds a channel that delivers “ticks” of a clock
// at intervals.
type Ticker struct {
	C <-chan Time // The channel on which the ticks are delivered.
	r runtimeTimer
}

我们可以很清晰的看到返回值Ticker是一个结构体,而其中的字段C是一个只读管道。

2.context.WithCancel()函数:

在 Go 语言中,context.WithCancel 是用于生成可以取消的 Context 对象的函数。它属于 context 包,常用于在并发操作中进行控制,以便父协程可以通过取消 Context 来通知子协程结束或提前退出。

context.Context 的作用

context.Context 是 Go 中用来处理多个协程间共享的上下文信息的机制,它支持:

  • 取消信号:通过取消 Context 结束协程的执行。
  • 截止时间:设定某个时间点来自动取消 Context
  • 传递数据:通过上下文传递一些跨协程的值。

context.WithCancel 函数用于生成一个可以取消的上下文 (Context)。它返回两个值:

  1. ctx:派生出来的 Context,当调用取消函数时,这个上下文会被取消。
  2. cancel():用于取消此 Context 的函数,调用 cancel() 后,所有派生自该 Context 的操作都会停止或提前结束。

context.WithCancel 的函数签名

func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
  • parent Context:表示传入的父上下文,可以是 context.Background() 或派生自其他 Context
  • ctx:返回新的 Context
  • cancel():一个取消函数,用于取消 Context

内部定义:

// WithCancel returns a copy of parent with a new Done channel. The returned
// context's Done channel is closed when the returned cancel function is called
// or when the parent context's Done channel is closed, whichever happens first.
//
// Canceling this context releases resources associated with it, so code should
// call cancel as soon as the operations running in this Context complete.
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) {
	c := withCancel(parent)
	return c, func() { c.cancel(true, Canceled, nil) }
}

具体实现

服务端

心跳包定义

// 心跳包定义
type message struct {
	Type string 
	Content string 	
	Time time.Time 
}
//设置消息类型
func (m *message) SetType(t string) {
	m.Type = t
}
//设置消息内容
func (m *message) SetContent(c string){
	m.Content = c
}
//设置消息打印格式
func (m *message) String() string {
	return fmt.Sprintf("Message{type:%s,content:%s,time:%s}", m.Type, m.Content, m.Time)
}

服务端启动

//服务端心跳检测机制
func StartServerHeartBeat(){
	//监听端口
	listener,err := net.Listen("tcp","127.0.0.1:1234")
	if err != nil {
		log.Println("Error starting server")
		return
	}
	//打印监听地址
	log.Println("TCP Server started listening:",listener.Addr())
	defer listener.Close()
	for {
		//接受客户端连接
		conn,err := listener.Accept()
		if err != nil {
			log.Println("Error accepting connection")
			continue
		}
		//处理客户端请求
		go handleClientRequest(conn)
	}
}

处理客户端请求

//处理客户端请求
func handleClientRequest(con net.Conn){
	defer con.Close()
	//创建一个WaitGroup,用于等待心跳检测结束
	wg := sync.WaitGroup{}
	wg.Add(1)
	//启动心跳检测
	go sendHeartBeat(con,&wg)
	wg.Wait()
}

处理服务端发送心跳包

func sendHeartBeat(con net.Conn,wg *sync.WaitGroup){
	const Timeout = 3
	currentTime := 0
	defer wg.Done()
	//启动Pong心跳检测
	//创建一个上下文,便于终止监听客户端信息的goroutine
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go readHeartBeat(con,ctx)
	//定时器
	ticker := time.NewTicker(time.Second*5)
	//定时发送心跳包
	defer ticker.Stop()
	for t := range ticker.C {
		//发送心跳包
		mes := message{"HEART_PING",fmt.Sprintf("Hi,I am Server,alive ! From:%s",con.LocalAddr()),t}
		//编码
		encoder := gob.NewEncoder(con)
		err := encoder.Encode(mes)
		if err != nil {
			log.Println("Error encoding message! connection closed")
			currentTime ++ 
			if currentTime > Timeout {
				//尝试关闭连接
				cancel()
				return
			}
		}
	}
}

处理服务端监听心跳包

//定义监听客户端心跳包的函数
func readHeartBeat(con net.Conn,ctx context.Context){
	for {
		select{
			//返回一个channel,当连接关闭时,会发送一个信号
		case <- ctx.Done():
			fmt.Println("Connection closed")
			return
		default:
			mes := message{}
			//解码
			decoder := gob.NewDecoder(con)
			decoder.Decode(&mes)
			//如果解码失败或者EOF连接断开,打印错误信息
			//暂时不处理错误,由case分支来处理退出机制
			//如果是心跳包
			if mes.Type == "HEART_PONG" {
				log.Println("Received HEART_PONG",mes)
			}//如果不是心跳包后的处理
		}
	}
}

客户端

客户端较简单所以直接定义在同一函数中便于观察逻辑

//客户端发送心跳包
func StartClientHeartBeat(){
	conn,err := net.Dial("tcp","127.0.0.1:1234")
	if err != nil {
		log.Println("Error connecting to server")
		return
	}
	defer conn.Close()
	for {
		//初始化一个心跳包
		mes := &message{}
		//通过json解码后读取服务器发送的消息
		err = gob.NewDecoder(conn).Decode(mes)
		if err != nil && err.Error() == "EOF" {
			log.Println("Error decoding message or EOF")
			break
		}
		if mes.Type == "HEART_PING" {
			log.Println("Received HEART_PING",mes)
			//回复心跳包
			//设置心跳包类型
			mes.SetType("HEART_PONG")
			//设置心跳包内容
			mes.SetContent(fmt.Sprintf("Hi,I am Client,alive ! From :%s",conn.LocalAddr()))
			err = gob.NewEncoder(conn).Encode(mes)
			//发送失败后的处理
			if err != nil {
				log.Println("Error encoding message")
				continue
			}
		}
		//否则是服务器发送的其他消息处理
	}
}

实验呈现结果

Server:

Client:

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐