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

}

相关推荐
GitLqr17 小时前
从 TCP 底层看实时通信:为什么 SSE 往往比 WebSocket 更香?
websocket·http·openai
ylj_dev18 小时前
从 0 构建 AI Workload Platform(七):可观测性、故障注入与性能验证
go·prometheus·可观测性·opentelemetry·故障注入
脉动数据行情121 小时前
Python WebSocket 国内商品期货|螺纹钢 & 铁矿石实时行情监听
开发语言·python·websocket
我的div丢了肿么办21 小时前
go语言中基本数据类型的转换
后端·go
宠友信息2 天前
内容社区源码开发实践解析,用Spring Boot打造1:1仿小红书源码平台
java·spring boot·redis·websocket·mysql·spring·uni-app
newerp2 天前
语法分析与 AST:Parser 与 go/ast
后端·程序员·go
newerp2 天前
Go 编译过程全景
后端·程序员·go
stark张宇2 天前
Go语言runtime全景图:从编译到GC,带你彻底吃透Go的底层血脉
后端·go
名字还没想好☜3 天前
Go 用 slices/maps 标准库泛型函数:告别手写 Contains、Sort、去重(Go 1.21)
开发语言·后端·算法·golang·go
limingade3 天前
智能拨号器APP-外部WebSocket话单推送系统接口文档
网络·websocket·智能拨号器app·远程推送号码到手机拨打电话·手机app稳定长连接·手机拨打电话后推送话单和录音