websocket的协议设计

WebSocket连接在建立阶段需要基于HTTP协议,但建立后会有独立的TCP通信通道。具体来说:

  1. 握手阶段(基于HTTP) WebSocket连接通过HTTP协议发起握手请求,请求头包含Upgrade: websocket等关键信息,服务端响应101状态码完成协议切换

  2. 通信阶段(独立协议) 握手成功后,客户端和服务端会保持TCP长连接,使用WebSocket协议帧进行双向通信,此时不再依赖HTTP协议

这种设计的好处:

  • 复用现有HTTP端口(80/443)
  • 利用成熟的HTTP基础设施(身份验证、负载均衡等)
  • 更容易通过防火墙和代理

如果希望完全脱离HTTP协议,可以使用更底层的TCP Socket+自定义协议,但会失去上述优势,且需要自行处理协议解析、心跳维护等复杂逻辑。

go 复制代码
var upgrader = websocket.Upgrader{
	CheckOrigin: func(r *http.Request) bool { 
		return true // 允许跨域请求
	},
}

func wsHandler(w http.ResponseWriter, r *http.Request) {
	// 升级HTTP连接到WebSocket
	conn, err := upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Error("WebSocket升级失败:", err)
		return
	}
	defer conn.Close()

	// 读取客户端消息
	for {
	}
}

区别

特性 websocket socket
协议层级 应用层协议 (基于HTTP握手) 传输层协议
连接建立 通过HTTP协议升级建立 直接三次握手建立
数据格式 自带帧结构(支持文本/二进制) 原始字节流
通信模式 全双工通信 全双工通信
消息边界 自动处理消息边界 需应用层处理分包
头部开销 每个帧有2-14字节头部 无额外头部
典型应用 实时网页应用(聊天、推送) 传统C/S应用(文件传输、数据库连接)

WebSocket是一种应用层协议,建立在TCP之上,提供全双工通信。它通过HTTP握手建立连接,之后保持长连接,适合实时应用。而TCP Socket是传输层的接口,更底层,需要自己处理数据格式和协议。

总结两者的适用场景,比如需要实时性且不想处理底层协议时用WebSocket,需要完全控制通信细节时用TCP Socket。

实现

websocket处理消息

go 复制代码
// 自动处理协议升级(HTTP -> WebSocket)
conn, _, err := websocket.DefaultDialer.Dial(url, header)

// 支持消息类型标识(TextMessage表示文本帧)
err = conn.WriteMessage(websocket.TextMessage, []byte("Hello..."))

// 内置消息边界处理(ReadMessage自动处理帧分割)
_, message, err := conn.ReadMessage()

原生TCP处理方式

go 复制代码
// 需要手动处理消息边界(假设头部4字节标识消息长度)
func readTCPMessage(conn net.Conn) ([]byte, error) {
    header := make([]byte, 4)
    if _, err := io.ReadFull(conn, header); err != nil {
        return nil, err
    }
    length := binary.BigEndian.Uint32(header)
    data := make([]byte, length)
    _, err := io.ReadFull(conn, data)
    return data, err
}

如果使用原生TCP Socket实现相同功能,需要:

  1. 手动实现HTTP握手协议
  2. 自行设计消息格式(如添加长度前缀)
  3. 处理TCP粘包/拆包问题
  4. 实现心跳保持机制
  5. 自行处理连接重连逻辑

通信协议

设计结构化的websocket通信协议。

通常,一个良好的协议需要定义消息格式、类型、错误处理、心跳机制等。用户可能希望消息包含元数据(如时间戳、消息ID)和有效载荷,同时支持不同的操作类型。

消息类型的设计。可能需要定义如Request、Response、Notification、Heartbeat等类型,每种类型有不同的处理逻辑。例如,客户端发送请求,服务器响应,或者服务器主动推送通知。

错误处理的设计。协议应包含错误码和错误信息,这样客户端可以识别和处理不同错误。例如,在用户代码中,处理速率限制时返回特定错误,可以将其纳入协议的错误体系中。

心跳机制的设计。心跳机制对于维持连接很重要,特别是在长时间空闲时防止连接被关闭。用户当前的代码中没有心跳,需要建议添加定期发送心跳消息的逻辑。

消息头部的设计。可以考虑包含通用字段,如消息ID、时间戳、类型、状态码等。有效载荷部分则根据具体类型携带不同数据。

版本控制和兼容性设计。需要考虑版本控制和兼容性,为协议添加版本字段,以便未来升级。

go 复制代码
// 新增协议结构体(添加到文件顶部)
type Message struct {
    ID        string          `json:"id"`     // 消息唯一标识
    Type      string          `json:"type"`   // 消息类型:request/response/notification
    Timestamp int64           `json:"ts"`     // 毫秒时间戳
    Status    int             `json:"status"` // 状态码(0=成功)
    Payload   json.RawMessage `json:"data"`   // 实际业务数据
}

// 消息类型常量(添加到包级别)
const (
    MsgTypeRequest      = "request"
    MsgTypeResponse     = "response"
    MsgTypeNotification = "notification"
    MsgTypeHeartbeat    = "heartbeat"
)

// 在wsconn.ReadLoop中处理消息(修改消息处理逻辑)
func (c *Conn) ReadLoop(ctx context.Context) error {
    for {
        _, data, err := c.conn.ReadMessage()
        if err != nil {
            return err
        }
        
        var msg Message
        if err := json.Unmarshal(data, &msg); err != nil {
            // 处理协议错误
            _ = c.SendError(ctx, "invalid_message_format", err)
            continue
        }
        
        switch msg.Type {
        case MsgTypeHeartbeat:
            // 更新心跳时间
            c.lastHeartbeat = time.Now().Unix()
        case MsgTypeRequest:
            go c.handleRequest(ctx, msg)
        default:
            // 返回未知类型错误
            _ = c.SendError(ctx, "unsupported_message_type", nil)
        }
    }
}

// 新增发送方法(添加到Conn结构体)
func (c *Conn) SendResponse(ctx context.Context, reqID string, data interface{}) error {
    payload, _ := json.Marshal(data)
    msg := Message{
        ID:        uuid.New().String(),
        Type:      MsgTypeResponse,
        Timestamp: time.Now().UnixMilli(),
        Status:    0,
        Payload:   payload,
    }
    return c.conn.WriteJSON(msg)
}

// 新增错误处理方法(添加到Conn结构体)
func (c *Conn) SendError(ctx context.Context, reqID string, err error) error {
    errorMsg := map[string]interface{}{
        "code":    1001, // 自定义错误码
        "message": err.Error(),
    }
    payload, _ := json.Marshal(errorMsg)
    
    msg := Message{
        ID:        reqID,
        Type:      MsgTypeResponse,
        Timestamp: time.Now().UnixMilli(),
        Status:    1,
        Payload:   payload,
    }
    return c.conn.WriteJSON(msg)
}
相关推荐
Kale又菜又爱玩17 小时前
WebSocket 使用教程
网络·websocket·网络协议
liuniansilence2 天前
WebSocket 连接前后端实现
前端·websocket
白总Server2 天前
Bash和Zsh在处理大文件时差异
网络·websocket·网络协议·udp·ssh·ssl·shell
GoldenaArcher2 天前
[MERN] 使用 socket.io 实现即时通信功能
websocket·mongodb·node.js·reactjs·express
黑风风2 天前
详解了解websocket协议
网络·websocket·网络协议
小杨4043 天前
springboot框架项目应用实践五(websocket实践)
spring boot·后端·websocket
jbjhzstsl3 天前
libwebsockets实现异步websocket客户端,服务端异常断开可重连
websocket
高志小鹏鹏3 天前
掘金是不懂技术吗,为什么一直用轮询调接口?
前端·websocket·rocketmq
pitt19973 天前
NexLM 开源系列】让 AI 聊天更丝滑:WebSocket 实现流式对话!
websocket·chatgpt·deepseek·see·大模型集成·流式对话