go 系列实现websocket

一、简介

websocket是个二进制协议,需要先通过Http协议进行握手,从而协商完成从Http协议向websocket协议的转换。一旦握手结束,当前的TCP连接后续将采用二进制websocket协议进行双向双工交互,自此与Http协议无关。

二、websocket demo

ws.go
Go 复制代码
package main

import (
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
	"time"
)

type wsMessage struct {
	mType int
	data  []byte
}

type wsConnection struct {
	wsSocket *websocket.Conn // 底层websocket
	inChan   chan *wsMessage // 读队列
	outChan  chan *wsMessage // 写队列
	state    int             // 连接状态
}

func (wsCon *wsConnection) wsReadLoop() {
	for {
		if wsCon.state == 1 {
			return
		}
		mType, data, err := wsCon.wsSocket.ReadMessage() // 阻塞
		if err != nil {
			fmt.Println("read err: %v", err)
			continue
		}
		msg := &wsMessage{
			mType,
			data,
		}
		wsCon.inChan <- msg
	}

}

func (wsCon *wsConnection) wsWriteLoop() {
	for {
		if wsCon.state == 1 {
			return
		}
		msg := <-wsCon.outChan // 阻塞
		err := wsCon.wsSocket.WriteMessage(msg.mType, msg.data)
		if err != nil {
			fmt.Printf("write err: %v", err)
			continue
		}
	}
}

func (wsCon *wsConnection) bizLoop() {
	for {
		if wsCon.state == 1 {
			return
		}
		// 处理消息
		msg := <-wsCon.inChan
		fmt.Println("process msg", string(msg.data))
		// 返回响应
		wsCon.outChan <- &wsMessage{
			mType: 2,
			data:  []byte(fmt.Sprintf("%s done", string(msg.data))),
		}
	}
}

func (wsCon *wsConnection) healthLoop() {
	defer wsCon.wsSocket.Close()
	for {
		time.Sleep(3 * time.Second)
		// 返回响应
		if err := wsCon.wsSocket.WriteMessage(2,
			[]byte("心跳检查")); err != nil {
			fmt.Println("write err: %v", err)
			wsCon.state = 1
			return
		}

	}
}

func wsHandler(resp http.ResponseWriter, req *http.Request) {
	// 升级websocket协议
	wsSocket, err := websocket.Upgrade(resp, req, nil, 1024, 1024)
	if err != nil {
		fmt.Println("upgrade err: %v", err)
	}
	// 一个请求,一个websocket 连接
	wsCon := &wsConnection{
		wsSocket: wsSocket,
		inChan:   make(chan *wsMessage, 1000),
		outChan:  make(chan *wsMessage, 1000),
	}

	go wsCon.wsReadLoop()
	go wsCon.wsWriteLoop()
	go wsCon.healthLoop()
	go wsCon.bizLoop()

}

func main() {
	http.HandleFunc("/ws", wsHandler)
	http.ListenAndServe(":8080", nil)
}
ws.html
html 复制代码
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <script>
        window.addEventListener("load", function (evt) {
            var output = document.getElementById("output");
            var input = document.getElementById("input");
            var ws;
            var print = function (message) {
                var d = document.createElement("div");
                d.innerHTML = message;
                output.appendChild(d);
            };
            document.getElementById("open").onclick = function (evt) {
                if (ws) {
                    return false;
                }
                ws = new WebSocket("ws://localhost:8080/ws");
                ws.onopen = function (evt) {
                    print("OPEN");
                }
                ws.onclose = function (evt) {
                    print("CLOSE");
                    ws = null;
                }
                ws.onmessage = function (evt) {
                    const reader = new FileReader()
                    reader.onload = function (event) {
                        print("RESPONSE: " + event.target.result)
                    }
                    reader.readAsText(evt.data)
                }
                ws.onerror = function (evt) {
                    print("ERROR: " + evt.data);
                }
                return false;
            };
            document.getElementById("send").onclick = function (evt) {
                if (!ws) {
                    return false;
                }
                print("SEND: " + input.value);
                ws.send(input.value);
                return false;
            };
            document.getElementById("close").onclick = function (evt) {
                if (!ws) {
                    return false;
                }
                ws.close();
                return false;
            };
        });
    </script>
</head>
<body>
<table>
    <tr>
        <td valign="top" width="50%">
            <p>Click "Open" to create a connection to the server,
                "Send" to send a message to the server and "Close" to close the connection.
                You can change the message and send multiple times.
            </p>
            <form>
                <button id="open">Open</button>
                <button id="close">Close</button>
                <input id="input" type="text" value="Hello!">
                <button id="send">Send</button>
            </form>
        </td>
        <td valign="top" width="50%">
            <div id="output"></div>
        </td>
    </tr>
</table>
</body>
</html>
实现效果
相关推荐
昔时扬尘处1 天前
【C2000系列DSP的不掉电升级】C2000 不掉电升级(LFU)方案详解(含流程、代码与官方方案适配)
网络·dsp·c2000·德州仪器·实时控制mcu·lfu不掉电升级·后台升级
ZHHHHHJ661 天前
LL层-PAST
运维·服务器·网络
老蒋新思维1 天前
创客匠人启示:破解知识交付的“认知摩擦”——IP、AI与数据的三角解耦模型
大数据·人工智能·网络协议·tcp/ip·重构·创客匠人·知识变现
百***07451 天前
GPT-5.2 极速接入指南:流程详解与主流模型对比
网络·人工智能·gpt
REDcker1 天前
TCP/IP 协议栈详解:协议栈是什么意思?为什么叫“协议栈”?
网络·网络协议·tcp/ip
老蒋新思维1 天前
反脆弱性设计:创始人IP与AI智能体如何构建愈动荡愈强大的知识商业|创客匠人
人工智能·网络协议·tcp/ip·算法·机器学习·创始人ip·创客匠人
凯子坚持 c1 天前
Docker网络架构深度解析:从原理到实战
网络·docker·架构
工控小楠1 天前
Profinet从站转EtherNet IP主站协议网关应用于自动化生产线
网络协议·tcp/ip·自动化
cdprinter1 天前
信刻光盘数据自动回读系统,多重保障数据安全及调阅便捷性!
网络·安全·自动化
发光小北1 天前
SG-CAN (FD) NET-210(双通道 CAN (FD) 转以太网网关)特点与功能介绍
开发语言·网络·php