go语言websocket连接,重连,发心跳示例

package main

import (

"context"

"fmt"

"log"

"net/http"

"sync"

"time"

"github.com/gorilla/websocket"

)

type WebSocketClient struct {

url string

conn *websocket.Conn

sendChan chan []byte

reconnectChan chan bool

closeChan chan struct{}

mutex sync.RWMutex

connected bool

pingInterval time.Duration

pongWait time.Duration

}

func NewWebSocketClient(url string) *WebSocketClient {

return &WebSocketClient{

url: url,

sendChan: make(chan []byte, 100),

reconnectChan: make(chan bool, 1),

closeChan: make(chan struct{}),

pingInterval: 30 * time.Second,

pongWait: 60 * time.Second,

}

}

func (wsc *WebSocketClient) Connect() error {

conn, _, err := websocket.DefaultDialer.Dial(wsc.url, nil)

if err != nil {

return fmt.Errorf("failed to connect: %v", err)

}

wsc.mutex.Lock()

wsc.conn = conn

wsc.connected = true

wsc.mutex.Unlock()

// 启动读取、写入和心跳协程

go wsc.readPump()

go wsc.writePump()

go wsc.heartbeat()

log.Println("WebSocket connected successfully")

return nil

}

func (wsc *WebSocketClient) readPump() {

defer func() {

wsc.mutex.Lock()

wsc.connected = false

wsc.conn.Close()

wsc.mutex.Unlock()

log.Println("Read pump stopped")

}()

// 设置读取超时

wsc.conn.SetReadDeadline(time.Now().Add(wsc.pongWait))

wsc.conn.SetPongHandler(func(string) error {

wsc.conn.SetReadDeadline(time.Now().Add(wsc.pongWait))

return nil

})

for {

select {

case <-wsc.closeChan:

return

default:

messageType, message, err := wsc.conn.ReadMessage()

if err != nil {

if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {

log.Printf("WebSocket error: %v", err)

}

wsc.triggerReconnect()

return

}

switch messageType {

case websocket.TextMessage:

log.Printf("Received text message: %s", string(message))

case websocket.BinaryMessage:

log.Printf("Received binary message: %d bytes", len(message))

case websocket.PingMessage:

wsc.conn.WriteMessage(websocket.PongMessage, []byte{})

}

}

}

}

func (wsc *WebSocketClient) writePump() {

ticker := time.NewTicker(wsc.pingInterval)

defer func() {

ticker.Stop()

wsc.conn.Close()

}()

for {

select {

case message, ok := <-wsc.sendChan:

wsc.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))

if !ok {

wsc.conn.WriteMessage(websocket.CloseMessage, []byte{})

return

}

err := wsc.conn.WriteMessage(websocket.TextMessage, message)

if err != nil {

log.Printf("Write error: %v", err)

wsc.triggerReconnect()

return

}

case <-ticker.C:

wsc.conn.SetWriteDeadline(time.Now().Add(10 * time.Second))

if err := wsc.conn.WriteMessage(websocket.PingMessage, []byte{}); err != nil {

log.Printf("Ping error: %v", err)

wsc.triggerReconnect()

return

}

case <-wsc.closeChan:

wsc.conn.WriteMessage(websocket.CloseMessage, []byte{})

return

}

}

}

func (wsc *WebSocketClient) heartbeat() {

ticker := time.NewTicker(30 * time.Second)

defer ticker.Stop()

for {

select {

case <-ticker.C:

wsc.SendPing()

case <-wsc.closeChan:

return

}

}

}

func (wsc *WebSocketClient) SendPing() {

wsc.mutex.RLock()

connected := wsc.connected

conn := wsc.conn

wsc.mutex.RUnlock()

if connected && conn != nil {

conn.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"ping\",\"timestamp\":"+fmt.Sprintf("%d", time.Now().Unix())+"}"))

}

}

func (wsc *WebSocketClient) SendText(message string) error {

wsc.mutex.RLock()

connected := wsc.connected

wsc.mutex.RUnlock()

if !connected {

return fmt.Errorf("WebSocket is not connected")

}

select {

case wsc.sendChan <- []byte(message):

return nil

case <-time.After(5 * time.Second):

return fmt.Errorf("send timeout")

}

}

func (wsc *WebSocketClient) SendBinary(data []byte) error {

wsc.mutex.RLock()

connected := wsc.connected

wsc.mutex.RUnlock()

if !connected {

return fmt.Errorf("WebSocket is not connected")

}

select {

case wsc.sendChan <- data:

return nil

case <-time.After(5 * time.Second):

return fmt.Errorf("send timeout")

}

}

func (wsc *WebSocketClient) triggerReconnect() {

select {

case wsc.reconnectChan <- true:

default:

// 避免阻塞

}

}

func (wsc *WebSocketClient) reconnectLoop() {

maxReconnectAttempts := 10

reconnectDelay := 5 * time.Second

for attempt := 1; attempt <= maxReconnectAttempts; attempt++ {

log.Printf("Attempting to reconnect... (attempt %d/%d)", attempt, maxReconnectAttempts)

err := wsc.Connect()

if err == nil {

log.Println("Reconnected successfully")

return

}

log.Printf("Reconnect failed: %v, waiting %v before next attempt", err, reconnectDelay)

time.Sleep(reconnectDelay)

reconnectDelay = time.Duration(float64(reconnectDelay) * 1.5) // 指数退避

}

log.Printf("Max reconnect attempts reached (%d), giving up", maxReconnectAttempts)

}

func (wsc *WebSocketClient) Start() {

// 初始连接

if err := wsc.Connect(); err != nil {

log.Printf("Initial connection failed: %v, will attempt to reconnect", err)

wsc.reconnectLoop()

}

// 监听重连信号

go func() {

for {

select {

case <-wsc.reconnectChan:

wsc.reconnectLoop()

case <-wsc.closeChan:

return

}

}

}()

}

func (wsc *WebSocketClient) Close() {

close(wsc.closeChan)

wsc.mutex.Lock()

if wsc.conn != nil {

wsc.conn.Close()

}

wsc.connected = false

wsc.mutex.Unlock()

}

func (wsc *WebSocketClient) IsConnected() bool {

wsc.mutex.RLock()

connected := wsc.connected

wsc.mutex.RUnlock()

return connected

}

// 使用示例

func main() {

// 创建WebSocket客户端

client := NewWebSocketClient("ws://localhost:8080/ws")

// 启动客户端

go client.Start()

// 等待连接建立

time.Sleep(2 * time.Second)

// 发送消息示例

go func() {

ticker := time.NewTicker(10 * time.Second)

defer ticker.Stop()

counter := 0

for {

select {

case <-ticker.C:

counter++

message := fmt.Sprintf("{\"type\":\"message\",\"id\":%d,\"content\":\"Hello from client\",\"timestamp\":%d}",

counter, time.Now().Unix())

if err := client.SendText(message); err != nil {

log.Printf("Failed to send message: %v", err)

} else {

log.Printf("Message sent: %s", message)

}

case <-client.closeChan:

return

}

}

}()

// 模拟程序运行

time.Sleep(60 * time.Second)

// 关闭客户端

client.Close()

}

相关推荐
俞凡2 小时前
Golang 构建网络漏洞扫描器
go
百锦再3 小时前
第14章 智能指针
android·java·开发语言·git·rust·go·错误
xrkhy4 小时前
websocket(即时通讯)
网络·websocket·网络协议
Mgx6 小时前
用 Go 写个“端口扫描器”,100 行代码扫描你家路由器?(别慌,只是看看谁在开门!)
go
mao毛6 小时前
go项目适配DTM,gozero已经适配dtm了,goframe项目要怎么适配?
微服务·go
ZKshun9 小时前
WebSocket指南:从原理到生产环境实战
前端·websocket
Mgx10 小时前
一文讲透 Go 的 defer:你的“善后管家“,别让他变成“背锅侠“!
go
Mgx10 小时前
剪贴板监控记:用 Go 写一个 Windows 剪贴板监控器
go
百锦再17 小时前
第11章 泛型、trait与生命周期
android·网络·人工智能·python·golang·rust·go
wuk99817 小时前
实现ROS系统的Websocket传输,向Web应用推送sensor_msgs::Image数据
前端·websocket·网络协议