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

}

相关推荐
古城小栈1 天前
SSE 流式传输技术:后端 说话 前端 听着
websocket·http·信息与通信
前端_yu小白1 天前
websocket在vue项目和nginx中的代理配置
vue.js·websocket·nginx·vue3·服务端推送
爱吃烤鸡翅的酸菜鱼1 天前
【RabbitMQ】发布订阅架构深度实践:构建高可用异步消息处理系统
java·spring boot·分布式·后端·websocket·架构·rabbitmq
梵尔纳多1 天前
基于 libwebsockets 实现 websocket 服务
网络·websocket·网络协议
小信啊啊1 天前
Go语言结构体
golang·go
moxiaoran57532 天前
Go语言的常量
go
武大打工仔2 天前
如何理解 Golang 中的 Context?
go
心随雨下2 天前
WebSocket使用注意事项与优化策略
网络·websocket·网络协议
闲人编程2 天前
WebSocket实时通信协议深度解析
网络·websocket·网络协议·安全·通信·codecapsule
卓码软件测评2 天前
第三方CNAS软件测试评测机构:【软件测试工具Apifox中的WebSocket接口测试从入门到精通】
websocket·网络协议·测试工具·单元测试·测试用例