使用 Go 语言创建 WebSocket 服务

感谢参考原文-http://bjbsair.com/2020-04-01/tech-info/18504.html

今天介绍如何用 Go 语言创建 WebSocket 服务,文章的前两部分简要介绍了 WebSocket 协议以及用 Go 标准库如何创建 WebSocket 服务。第三部分实践环节我们使用了 gorilla/websocket 库帮助我们快速构建 WebSocket 服务,它帮封装了使用 Go 标准库实现 WebSocket 服务相关的基础逻辑,让我们能从繁琐的底层代码中解脱出来,根据业务需求快速构建 WebSocket 服务。

WebSocket介绍

WebSocket 通信协议通过单个 TCP 连接提供全双工通信通道。与 HTTP 相比, WebSocket 不需要你为了获得响应而发送请求。它允许双向数据流,因此您只需等待服务器发送的消息即可。当 Websocket 可用时,它将向您发送一条消息。对于需要连续数据交换的服务(例如即时通讯程序,在线游戏和实时交易系统), WebSocket 是一个很好的解决方案。 WebSocket 连接由浏览器请求,并由服务器响应,然后建立连接,此过程通常称为握手。 WebSocket 中的特殊标头仅需要浏览器与服务器之间的一次握手即可建立连接,该连接将在其整个生命周期内保持活动状态。 WebSocket 解决了许多实时 Web 开发的难题,并且与传统的 HTTP 相比,具有许多优点:

  • 轻量级报头减少了数据传输开销。
  • 单个 Web 客户端仅需要一个 TCP 连接。
  • WebSocket 服务器可以将数据推送到 Web 客户端。

WebSocket协议实现起来相对简单。它使用 HTTP 协议进行初始握手。握手成功后即建立连接, WebSocket 实质上使用原始 TCP 读取/写入数据。

客户端请求如下所示:

GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
    Sec-WebSocket-Protocol: chat, superchat
    Sec-WebSocket-Version: 13
    Origin: http://example.com

这是服务器响应:

HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
    Sec-WebSocket-Protocol: chat

如何在Go中创建WebSocket应用

要基于Go 语言内置的 net/http 库编写 WebSocket 服务器,你需要:

  • 发起握手
  • 从客户端接收数据帧
  • 发送数据帧给客户端
  • 关闭握手

发起握手

首先,让我们创建一个带有 WebSocket 端点的 HTTP 处理程序:

// HTTP server with WebSocket endpoint
func Server() {
        http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
            ws, err := NewHandler(w, r)
            if err != nil {
                 // handle error
            }
            if err = ws.Handshake(); err != nil {
                // handle error
            }
        …

然后初始化 WebSocket 结构。

初始握手请求始终来自客户端。服务器确定了 WebSocket 请求后,需要使用握手响应进行回复。

请记住,你无法使用 http.ResponseWriter 编写响应,因为一旦开始发送响应,它将关闭其基础的 TCP 连接(这是 HTTP 协议的运行机制决定的,发送响应后即关闭连接)。

因此,您需要使用 HTTP 劫持( hijack )。通过劫持,可以接管基础的 TCP 连接处理程序和 bufio.Writer 。这使可以在不关闭 TCP 连接的情况下读取和写入数据。

// NewHandler initializes a new handler
func NewHandler(w http.ResponseWriter, req *http.Request) (*WS, error) {
        hj, ok := w.(http.Hijacker)
        if !ok {
            // handle error
        }                  .....
}

要完成握手,服务器必须使用适当的头进行响应。

// Handshake creates a handshake header
    func (ws *WS) Handshake() error {  

        hash := func(key string) string {
            h := sha1.New()
            h.Write([]byte(key))
            h.Write([]byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))  

        return base64.StdEncoding.EncodeToString(h.Sum(nil))
        }(ws.header.Get("Sec-WebSocket-Key"))
      .....
}

客户端发起 WebSocket 连接请求时用的 Sec-WebSocket-key 是随机生成的,并且是Base64编码的。接受请求后,服务器需要将此密钥附加到固定字符串。假设秘钥是 x3JJHMbDL1EzLkh9GBhXDw== 。在这个例子中,可以使用 SHA-1 计算二进制值,并使用 Base64 对其进行编码。得到 HSmrc0sMlYUkAGmm5OPpG2HaGWk= 。然后使用它作为 Sec-WebSocket-Accept 响应头的值。

传输数据帧

握手成功完成后,您的应用程序可以从客户端读取数据或向客户端写入数据。WebSocket规范定义了一个客户机和服务器之间使用的特定帧格式。这是框架的位模式:

图:传输数据帧的位模式

使用以下代码对客户端有效负载进行解码:

// Recv receives data and returns a Frame
    func (ws *WS) Recv() (frame Frame, _ error) {
        frame = Frame{}
        head, err := ws.read(2)
        if err != nil {
            // handle error
        }

反过来,这些代码行允许对数据进行编码:

// Send sends a Frame
    func (ws *WS) Send(fr Frame) error {
        // make a slice of bytes of length 2
        data := make([]byte, 2)  

        // Save fragmentation & opcode information in the first byte
        data[0] = 0x80 | fr.Opcode
        if fr.IsFragment {
            data[0] &= 0x7F
        }
        .....

关闭握手

当各方之一发送状态为关闭的关闭帧作为有效负载时,握手将关闭。可选 的 ,发送关闭帧的一方可以在有效载荷中发送关闭原因。 如果关闭是由客户端发起的,则服务器应发送相应的关闭帧作为响应。

// Close sends a close frame and closes the TCP connection
func (ws *Ws) Close() error {
    f := Frame{}
    f.Opcode = 8
    f.Length = 2
    f.Payload = make([]byte, 2)
    binary.BigEndian.PutUint16(f.Payload, ws.status)
    if err := ws.Send(f); err != nil {
        return err
    }
    return ws.conn.Close()
}

使用第三方库快速构建WebSocket服务

通过上面的章节可以看到用 Go 自带的 net/http 库实现 WebSocket 服务还是太复杂了。好在有很多对 WebSocket 支持良好的第三方库,能减少我们很多底层的编码工作。这里我们使用 gorilla web toolkit 家族的另外一个库 gorilla/websocket 来实现我们的 WebSocket 服务,构建一个简单的 Echo 服务( echo 意思是回音,就是客户端发什么,服务端再把消息发回给客户端)。

我们在 http_demo 项目的 handler 目录下新建一个 ws 子目录用来存放 WebSocket 服务相关的路由对应的请求处理程序。

增加两个路由:

  • /ws/echo echo 应用的 WebSocket 服务的路由。
  • /ws/echo_display echo 应用的客户端页面的路由。

创建WebSocket服务端

// handler/ws/echo.go
package ws  

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

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
}  

func EchoMessage(w http.ResponseWriter, r *http.Request) {
    conn, _ := upgrader.Upgrade(w, r, nil) // 实际应用时记得做错误处理  

    for {
        // 读取客户端的消息
        msgType, msg, err := conn.ReadMessage()
        if err != nil {
            return
        }  

        // 把消息打印到标准输出
        fmt.Printf("%s sent: %s\n", conn.RemoteAddr(), string(msg))  

        // 把消息写回客户端,完成回音
        if err = conn.WriteMessage(msgType, msg); err != nil {
            return
        }
    }
}
  • conn 变量的类型是 websocket.Conn , websocket.Conn 类型用来表示 WebSocket 连接。服务器应用程序从 HTTP 请求处理程序调用 Upgrader.Upgrade 方法以获取 websocket.Conn
  • 调用连接的 WriteMessage 和 ReadMessage 方法发送和接收消息。上面的 msg 接收到后在下面又回传给了客户端。 msg 的类型是 []byte 。

创建WebSocket客户端

前端页面路由对应的请求处理程序如下,直接返回 views/websockets.html 给到浏览器渲染页面即可。

// handler/ws/echo_display.go
package ws  

import "net/http"  

func DisplayEcho(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "views/websockets.html")
}
<form>
    <input id="input" type="text" />
    <button onclick="send()">Send</button>
    <pre id="output"></pre>
</form>
...
<script>    var input = document.getElementById("input");
    var output = document.getElementById("output");
    var socket = new WebSocket("ws://localhost:8000/ws/echo");  

    socket.onopen = function () {
        output.innerHTML += "Status: Connected\n";
    };  

    socket.onmessage = function (e) {
        output.innerHTML += "Server: " + e.data + "\n";
    };  

    function send() {
        socket.send(input.value);
        input.value = "";
    }</script>
...

注册路由

服务端和客户端的程序都准备好后,我们按照之前约定好的路径为他们注册路由和对应的请求处理程序:

// router/router.go
func RegisterRoutes(r *mux.Router) {
    ...
    wsRouter := r.PathPrefix("/ws").Subrouter()
    wsRouter.HandleFunc("/echo", ws.EchoMessage)
    wsRouter.HandleFunc("/echo_display", ws.DisplayEcho)
}

测试验证

重启服务后访问 http://localhost:8000/ws/echo_display ,在输入框中输入任何消息都能再次回显到浏览器中。

图片

服务端则是把收到的消息打印到终端中然后把调用 writeMessage 把消息再回传给客户端,可以在终端中查看到记录。

总结

WebSocket 在现在更新频繁的应用中使用非常广泛,进行 WebSocket 编程也是我们需要掌握的一项必备技能。文章的实践练习稍微简单了一些,也没有做错误和安全性检查。主要是为了讲清楚大概的流程。关于 gorilla/websocket 更多的细节在使用时还需要查看官方文档才行。

原文地址:https://blog.51cto.com/14744108/2484180

时间: 2024-08-07 17:29:53

使用 Go 语言创建 WebSocket 服务的相关文章

基于Socket创建Web服务

基于Socket创建Web服务 为什么要使用Socket呢,我们来看下图 Socket原理图回顾: -------------------编写SocketService,完成字母小写转大写功能----------------------------- ServerSocket服务器端代码如下: public static void main(String[] args) throws IOException { // 1:建立服务器端的tcp socket服务,必须监听一个端口 ServerSo

高效简易websocket服务开发包beetle

websocket的主要是为了解决在web上应用长连接进行灵活的通讯应用而产生,但websocket本身只是一个基础协议,对于消息上还不算灵活,毕竟websocket只提供文本和二进制流这种基础数据格式.在实际应用则更偏向于对象消息的处理,而在这基础上更希望集成一系列的消息路由机制来解决消息处理上的问题.为了解决以上问题beetle针对websocket进行了一些高层次的封装,让服务端处理消息变得更简单灵活.以下通过不同的示例介绍Beetle websocket开发包的简易性. hello wo

Thrift总结(二)创建RPC服务

前面介绍了thrift 基础的东西,怎么写thrift 语法规范编写脚本,如何生成相关的语言的接口.不清楚的可以看这个<Thrift总结(一)介绍>.做好之前的准备工作以后,下面就开始如何用Thrift写RPC接口. 如何用Thrift写RPC接口 1. 打开之前下载的thrift 源码,thrift-0.10.0\lib\csharp\src ,编译生成Thrift.dll 文件. 2. 新建一个空白解决方案命名为HelloThrift.在解决方案根目录下创建一个lib文件夹,将刚刚生成的T

用C语言实现websocket服务器

Websocket Echo Server Demo 背景 嵌入式设备的应用开发大都依靠C语言来完成,我去研究如何用c语言实现websocket服务器也是为了在嵌入式设备中实现一个ip camera的功能,用户通过网页访问到嵌入式设备的摄像头以及音频,在学习的过程中先实现echo server是最基本的. 主要参考资源 编写 WebSocket 服务器--MDN Linux下用C编写WebSocet服务以响应HTML5的WebSocket请求 具体实现 整个websocket从握手到数据传输帧头

杂记2:VS2013创建Windows服务实现自动发送邮件

这篇随笔里,我将介绍如何用VS2013开发Windows服务项目,实现的功能是定时发送电子邮件. 开发环境:VS2013,SQL Server2008,采用C#语言开发 步骤一:创建Windows服务项目 首先,有人提问VS2013找不到创建Windows服务项目的选项,答案是在“Windows 桌面”目录下: 步骤二:重命名服务,添加Timer组件 重命名默认创建的Service1服务,比如MyMailService:然后在设计界面添加Timer组件. 这里要注意,VS工具箱默认提供的是Sys

JHipster创建微服务及相关微服务架构组件介绍

参考链接: jhipster官网 jdl官方语法文档 JHipster中文文档-CSND博客 在线使用jhipster创建应用 在线使用jdl生成器创建数据表和相应服务端代码 一.创建微服务 微服务是一种JHipster应用程序,它没有前端(必须在网关)上生成Angular前端),并且可以与JHipster Registry一起配置,发现和管理. 创建微服务应用 安装: 安装Java 8 from the Oracle website. 安装Node.js from the Node.js we

Tomcat7 搭建 websocket服务

pom.xml (虽然配置了maven tomcat7插件,但是直接从maven中启动web服务,websocket服务启动不了.要将web服务部署到tomcat7 server上.可以直接打包war,也可以在开发工具中引入tomcat7) <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"       

Topshelf创建Windows服务

使用Topshelf创建Windows服务 概述 Topshelf是创建Windows服务的另一种方法,老外的一篇文章Create a .NET Windows Service in 5 steps with Topshelf通过5个步骤详细的介绍使用使用Topshelf创建Windows 服务.Topshelf是一个开源的跨平台的宿主服务框架,支持Windows和Mono,只需要几行代码就可以构建一个很方便使用的服务宿主. 引用安装 1.官网:http://topshelf-project.c

C#创建Window服务图解

一.首先打开VS2013,创建Windows服务项目 二.创建完成后对"Service1.cs"重命名位"ServiceDemo":然后切换到代码视图,写个服务运行日志. using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.IO; using Syst