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 小时前
边界检查消除(BCE,Bound Check Elimination)
go
梁梁梁梁较瘦3 小时前
指针
go
梁梁梁梁较瘦3 小时前
内存申请
go
半枫荷3 小时前
七、Go语法基础(数组和切片)
go
梁梁梁梁较瘦20 小时前
Go工具链
go
半枫荷1 天前
六、Go语法基础(条件控制和循环控制)
go
YUELEI1181 天前
Springboot WebSocket
spring boot·后端·websocket
半枫荷2 天前
五、Go语法基础(输入和输出)
go
小王在努力看博客2 天前
CMS配合闲时同步队列,这……
go
Anthony_49263 天前
逻辑清晰地梳理Golang Context
后端·go