0


Go实现WebSocket

Go实现WebSocket

在本文中,将会使用在 Go 中一个用得比较多的

WebSocket

实现

gorilla/websocket

1、WebSocket介绍

WebSocket

是一种应用层协议,

WebSocket

协议在 2008 年诞生,2011 年成为国际标准,现在最新版本浏览器

都已经支持了。

WebSocket

是一种在单个

TCP

连接上进行全双工通信的协议,

WebSocket

使得客户端和服务器之间的数据交换

变得更加简单,允许服务端主动向客户端推送数据。

Websocket

主要用在B/S架构的应用程序中,在 WebSocket

API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。

它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等

对话,属于服务器推送技术的一种。

1.1 WebSocket的典型特点

  • 基于 TCP 协议的应用层协议,实现相对简单
  • 单个 TCP 连接上进行全双工通信
  • 兼容 HTTP 协议,默认端口也是 80 和 443ws://host:port/path/querywss://host:port/path/query
  • 握手阶段采用 HTTP 协议,能通过各种 HTTP 代理服务器
  • 数据格式比较轻量,性能开销小,通信高效
  • 可以发送文本和二进制数据
  • 没有浏览器的同源限制

1.2 WebSocket的典型场景:

  • 即时通信
  • 协同编辑/编辑
  • 实时数据流的拉取与推送

1.3 WebSocket 推送和浏览器轮询

在 B/S 开发领域,若需要浏览器即时得到服务器的状态更新,常使用两个方案:

  • 浏览器端轮询
  • 服务器端推送

浏览器轮询:浏览器端,当需要获取最新数据状态时,利用脚本程序循环向服务端发送请求。

服务器推送:服务器端,当状态改变时,将数据发送到浏览器端。

HTTP/2 版本也支持服务器端推送,但实现上以推送静态资源为主,不能基于业务逻辑推送特定的消息,因此当前

的普及使用率 WebSocket 还是主流。

1.4 HTTP与WebSocket的关系

如果我们此前已经使用过

WebSocket

,比如在

nginx

配置过

WebSocket

,我们就会发现:

1、有个类似

upgrade

的关键字。这个关键字体现了 HTTP 与

WebSocket

的本质区别。

2、在

nginx

里配置,意味着

WebSocket

本质上也是通过 HTTP 协议来工作的。

我们知道,HTTP 的请求会在请求结束之后断开

TCP

连接,但

WebSocket

不一样,它在建立连接之后会一直维

持着连接状态, 这样客户端与服务端就可以一直维持通信状态了。

WebSocket 和 http 相同点:

  • 应用层协议
  • B/S 架构中使用
  • 基于 TCP 协议
  • 端口默认都是:80 和 443

WebSocket 和 http 不同点:
WebSocketHTTP通信模式双向单向握手双方协商浏览器发起服务器端推送支持不支持,H/2部分支持

1.5 WebSocket建立连接的过程

WebSocket

协议中,初始的握手阶段使用标准的

HTTP

请求和响应:

1、客户端先发送一个 HTTP 请求,请求升级到

WebSocket

协议。

2、服务器在收到这个请求后,如果同意升级到

WebSocket

,就会返回一个状态码为

101

的 HTTP 响应,指示升

级成功,然后不会断开 TCP 连接。

这个过程涉及到的 HTTP 头部字段是

Upgrade

Connection

,具体而言,HTTP 请求头部可能包含类似以下的

字段:

请求:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade

响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade

也就是说,我们所看到的

Upgrade

实际上是把一个

HTTP

连接升级为了

WebSocket

连接,这个连接可以实现双

向的通信。

这使得它非常适合实时通信的应用,例如聊天应用、在线游戏等。

2、WebSocket握手过程

通过 HTTP 请求响应,中的头信息,完成 WebSocket 握手,如图:

在这里插入图片描述

在请求头中添加如下信息:

# 升级为 websocket
Upgrade: websocket
Connection: Upgrade
# 一个Base64 encode的值,有于验证服务器端是否支持websocket
Sec-WebSocket-Key: x4JJHMbDL22zLk1GBhXDw==
# 用户协议,可以视为不同业务逻辑的频道
Sec-WebSocket-Protocol: chat
# 协议版本,13是当前通用版本,几乎不需要更改
Sec-WebSocket-Version: 13

基于以上请求头,服务器端,就知道需要将协议升级为

WebSocket

协议,并提供一些验证信息。

服务端的响应头:

HTTP/1.1 101 Switching Protocols
# 协议升级
Upgrade: websocket
# 连接状态
Connection: Upgrade
# WebSocket服务端根据Sec-WebSocket-Key生成
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
# WebSocket协议用户协议
Sec-WebSocket-Protocol: chat

基于以上响应头,浏览器端就知道服务器端升级成功,并通过了验证。

至此,B/S 端可以基于该连接,完成 websocket 双向通信了。

websocket 只能发送 GET 请求。

3、WebSocket状态码和消息类型

3.1 WebSocket协议状态码解析

WebSocket协议是一种基于TCP的全双工通信协议,它允许客户端和服务器之间进行实时的双向通信。在

WebSocket通信过程中,服务器和客户端会通过状态码来表示当前通信的状态或错误信息。

WebSocket 协议状态码是一个 16 位的整数,用于表示 WebSocket 连接的状态。状态码的第一个数字表示状态的

分类,后三个数字表示具体的状态。根据 WebSocket 协议的规范,状态码可以分为以下几类:

  • 1xxx:表示信息性状态码,用于传递一些非错误信息。
  • 2xxx:表示成功状态码,用于表示连接成功或操作成功。
  • 3xxx:表示重定向状态码,用于表示需要进一步操作以完成请求。
  • 4xxx:表示客户端错误状态码,用于表示客户端发送的请求有误。
  • 5xxx:表示服务器错误状态码,用于表示服务器无法完成请求。

常见的 WebSocket 协议状态码有:

  • 1000:正常关闭
  • 1001:终端离开
  • 1002:协议错误
  • 1003:数据类型错误
  • 1005:无法接收
  • 1006:连接关闭异常
  • 1011:服务器遇到异常

WebSocket协议状态码详解:

  • WebSocket协议状态码1000:正常关闭状态码1000表示WebSocket连接正常关闭。当服务器或客户端决定关闭连接时,会发送状态码1000给对方,表示连接关闭的原因是正常的。
  • WebSocket协议状态码1001:终端离开状态码1001表示客户端离开。当客户端主动关闭连接时,会发送状态码1001给服务器,表示客户端离开。
  • WebSocket协议状态码1002:协议错误状态码1002表示协议错误。当服务器或客户端收到的数据不符合WebSocket协议的规范时,会发送状态码1002给对方,表示协议错误。
  • WebSocket协议状态码1003:数据类型错误状态码1003表示数据类型错误。当服务器或客户端收到的数据类型不符合预期时,会发送状态码1003给对方,表示数据类型错误。
  • WebSocket协议状态码1005:无法接收状态码1005表示无法接收数据。当服务器或客户端由于某些原因无法接收数据时,会发送状态码1005给对方,表示无法接收。
  • WebSocket协议状态码1006:连接关闭异常状态码1006表示连接关闭异常。当服务器或客户端在关闭连接时遇到异常情况时,会发送状态码1006给对方,表示连接关闭异常。
  • WebSocket协议状态码1011:服务器遇到异常状态码1011表示服务器遇到异常。当服务器在处理WebSocket请求时遇到异常情况时,会发送状态码1011给客户端,表示服务器遇到异常。

3.2 消息类型

  • TextMessageBinaryMessage 分别表示发送文本消息和二级制消息
  • CloseMessage 关闭帧,接收方收到这个消息就关闭连接
  • PingMessagePongMessage:是保持心跳的帧

发送方 -> 接收方是

PingMessage

接收方 -> 发送方是

PongMessage

由服务器发

ping

给浏览器,浏览器返回

pong

消息。

4、gorilla/websocket中的基本概念

4.1 WebSocket 连接-Conn

在 gorilla/websocket 中使用

Conn

来表示一个

WebSocket

连接,它主要有如下作用:

  • 发送消息给客户端:Write* 方法,如 WriteJSON 发送 JSON 类型消息,又或者 WriteMessage 可以发送普通的文本消息。
  • 接收客户端发送的消息:Read* 方法,如 ReadJSONReadMessage
  • 其他功能:关闭连接、获取客户端 IP 地址等

4.2 消息

在 gorilla/websocket 中,消息被分为以下几种:

  • 数据消息:
    • TextMessage 文本消息:文本消息被解析为 UTF-8 编码的文本。需要应用程序来确保文本消息是有效的UTF-8 编码文本。- BinaryMessage 二进制消息:二进制消息的解析留给应用程序。
  • 控制消息:可以调用 Conn 中的 WriteControlWriteMessageNextWriter 方法,将控制消息发送给对方。
    • CloseMessage 关闭连接的消息- PingMessage ping 消息- PongMessage pong 消息

注意:应用程序需要先读取连接中的消息才能处理从对等方发送的

close

ping

pong

消息。如果应用程序

对来自对等方的消息不感兴趣, 则应用程序应启动一个

goroutine

来读取和丢弃来自对等方的消息。

4.3 并发

虽然 Golang 中有

goroutine

可以支持我们做并发操作,但是在 gorilla/websocket 中, 一个

WebSocket

连接

只支持一个并发

reader

和一个并发

writer

我们的应用程序应该确保不超过一个

goroutine

同时调用写入方法(

WriteMessage

WriteJSON

)或者读取方

法(

ReadMessage

ReadJSON

)。

Close

WriteControl

方法可以与其他所有方法同时调用。

4.4 安全性

我们知道,在一般的 web 应用中,经常需要处理跨域的问题,同样的,在 gorilla/websocket 中也需要做一定的

配置。

我们可以在

Upgrader

中的

CheckOrigin

字段中指定函数的

Origin

检查策略,如果

CheckOrigin

函数返回

false

,则

Upgrader

方法将拒绝建立

WebSocket

连接,如果允许所有来源的连接,我们可以直接返回

true

即可。

var upgrader = websocket.Upgrader{
    ReadBufferSize:1024,
    WriteBufferSize:1024,
    CheckOrigin:func(r *http.Request)bool{returntrue},}

4.5 缓冲

缓冲在 io 类操作中是一个很常见的术语,在 gorilla/websocket 中我们可以通过上面那段代码的

ReadBufferSize

WriteBufferSize

来指定连接的缓冲大小,以减少读取或写入消息时的系统调用次数。

默认大小为

4096

,建议限制为最大预期消息的大小,大于最大消息最大大小的缓冲区不会带来任何好处。

5、WebSocket例子

5.1 Hello World

让我们通过一个简单的

Hello World

程序来结束本文:

package main

import("log""net/http""github.com/gorilla/websocket")var upgrader = websocket.Upgrader{
    ReadBufferSize:1024,
    WriteBufferSize:1024,
    CheckOrigin:func(r *http.Request)bool{returntrue},}funchandler(w http.ResponseWriter, r *http.Request){
    conn, err := upgrader.Upgrade(w, r,nil)if err !=nil{
        log.Fatal(err)}

    conn.WriteMessage(websocket.TextMessage,[]byte("Hello, World!"))
    conn.Close()}funcmain(){
    http.HandleFunc("/ws", handler)
    http.ListenAndServe(":8181",nil)}

启动

WebSocket

服务端,在

http://www.websocket-test.com/

访问:

ws://127.0.0.1:8181/ws

在这里插入图片描述

5.2 消息发送和接收

package main

import("fmt""log""net/http""github.com/gorilla/websocket")funcWebSocketServer(){
    addr :="localhost:8002"
    http.HandleFunc("/wshandler", WebSocketUpgrade)
    log.Println("Starting websocket server at "+ addr)gofunc(){
        err := http.ListenAndServe(addr,nil)if err !=nil{
            log.Fatal(err)}}()

    log.Println("WebSocket 服务器正在运行。按Ctrl+C退出")select{}}funcWebSocketUpgrade(resp http.ResponseWriter, req *http.Request){// 初始化 Upgrader
    upgrader := websocket.Upgrader{
        ReadBufferSize:1024,
        WriteBufferSize:1024,
        CheckOrigin:func(r *http.Request)bool{returntrue},}// 使用默认的选项// 第三个参数是响应头,默认会初始化
    conn, err := upgrader.Upgrade(resp, req,nil)if err !=nil{
        log.Println(err)return}defer conn.Close()// 读取客户端的发送额消息,并返回goReadMessage(conn)select{}}// 读取客户端发送的消息,并返回funcReadMessage(conn *websocket.Conn){for{// 消息类型:文本消息和二进制消息
        messageType, msg, err := conn.ReadMessage()if err !=nil{
            log.Println(err)return}
        fmt.Println("receive msg:",string(msg))

        err = conn.WriteMessage(messageType, msg)if err !=nil{
            log.Println("write error:", err)return}}}funcmain(){WebSocketServer()}

访问:

ws://localhost:8002/wshandler

在这里插入图片描述

在这里插入图片描述

5.3 WebSocket代理实现

package main

import("log""net/http""net/http/httputil""net/url")var(// 代理服务器地址
    proxyServer ="127.0.0.1:8082"// 真实websocket服务器地址
    websocketServer ="http://127.0.0.1:8002")funcWebSocketProxy(){
    url, err := url.Parse(websocketServer)if err !=nil{
        log.Println(err)}
    proxy := httputil.NewSingleHostReverseProxy(url)
    log.Println("WebSocket 代理启动, 按CTRL+C退出")
    http.ListenAndServe(proxyServer, proxy)}funcmain(){WebSocketProxy()}

访问:

ws://localhost:8082/wshandler

5.4 WebSocket 服务端主动推送功能的实现

package main

import("fmt""log""net/http""time""github.com/gorilla/websocket")// websocket服务器每隔3秒会主动向服务器推送消息"Heart Beat"funcWebSocketServer(){
    addr :="localhost:8002"
    http.HandleFunc("/wshandler", WebSocketUpgrade)
    log.Println("Starting websocket server at "+ addr)gofunc(){
        err := http.ListenAndServe(addr,nil)if err !=nil{
            log.Fatal(err)}}()

    log.Println("WebSocket 服务器正在运行。按Ctrl+C退出")select{}}funcWebSocketUpgrade(resp http.ResponseWriter, req *http.Request){// 初始化 Upgrader
    upgrader := websocket.Upgrader{
        ReadBufferSize:1024,
        WriteBufferSize:1024,
        CheckOrigin:func(r *http.Request)bool{returntrue},}// 使用默认的选项// 第三个参数是响应头,默认会初始化
    conn, err := upgrader.Upgrade(resp, req,nil)if err !=nil{
        log.Println(err)return}defer conn.Close()// 主动向服务端推送消息goPushMessage(conn)// 读取客户端的发送额消息,并返回goReadMessage(conn)select{}}// websocket 服务器主动服务器推送消息funcPushMessage(conn *websocket.Conn){for{
        err := conn.WriteMessage(websocket.TextMessage,[]byte("heart beat"))if err !=nil{
            log.Println(err)return}
        time.Sleep(time.Second *3)}}// 读取客户端发送的消息,并返回funcReadMessage(conn *websocket.Conn){for{// 消息类型:文本消息和二进制消息
        messageType, msg, err := conn.ReadMessage()if err !=nil{
            log.Println(err)return}
        fmt.Println("receive msg:",string(msg))

        err = conn.WriteMessage(messageType, msg)if err !=nil{
            log.Println("write error:", err)return}}}funcmain(){WebSocketServer()}

在这里插入图片描述

5.5 其他库

package main

import("log""net/http""golang.org/x/net/websocket")funcEchoWebSocket(ws *websocket.Conn){var err errorfor{var reply stringif err = websocket.Message.Receive(ws,&reply); err !=nil{break}if err = websocket.Message.Send(ws, reply); err !=nil{break}}
    ws.Close()}funcmain(){
    http.Handle("/echo", websocket.Handler(EchoWebSocket))if err := http.ListenAndServe(":5000",nil); err !=nil{
        log.Fatal("ListenAndServe:", err)}}
标签: golang

本文转载自: https://blog.csdn.net/qq_30614345/article/details/140083133
版权归原作者 242030 所有, 如有侵权,请联系我们删除。

“Go实现WebSocket”的评论:

还没有评论