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

}

相关推荐
子玖11 分钟前
go实现通过ip解析城市
后端·go
Das1_1 小时前
【Golang 数据结构】Slice 底层机制
后端·go
太凉21 小时前
select 语句详解
go
想用offer打牌1 天前
一站式了解四种限流算法
java·后端·go
怕浪猫1 天前
第20章:Web服务实战——构建RESTful API
后端·go·编程语言
Coding君2 天前
每日一Go-28、Go语言进阶-深入Go运行时:内存管理与GC
go
echo本尊472182 天前
如何设计一个简单易用的定时任务模块
go
Bigger3 天前
告别版本焦虑:如何为 Hugo 项目定制专属构建环境
前端·架构·go
刀法如飞4 天前
一款Go语言Gin框架MVC脚手架,满足大部分场景
go·mvc·gin