使用Go语言创建WebSocket服务的实现示例

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

Go Web 编程系列的每篇文章的源代码都打了对应版本的软件包,供大家参考。公众号中回复 gohttp10 获取本文源代码

WebSocket介绍

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

  1. 轻量级报头减少了数据传输开销。
  2. 单个Web客户端仅需要一个TCP连接。
  3. 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
  • 调用连接的 WriteMessageReadMessage 方法发送和接收消息。上面的 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")
}

websocket.html 里我们需要用 JavaScript 连接 WebScoket 服务进行收发消息,篇幅原因我就只贴 JS 代码了

<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://yalantis.com/blog/how-to-build-websockets-in-go/

https://www.gorillatoolkit.org/pkg/websocket

到此这篇关于使用Go语言创建WebSocket服务的实现示例的文章就介绍到这了,更多相关Go语言创建WebSocket 内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Go 实现百万WebSocket连接的方法示例

    大家好!我是 Sergey Kamardin,是 Mail.Ru 的一名工程师. 本文主要介绍如何使用 Go 开发高负载的 WebSocket 服务. 如果你熟悉 WebSockets,但对 Go 了解不多,仍希望你对这篇文章的想法和性能优化方面感兴趣. 1. 简介 为了定义本文的讨论范围,有必要说明我们为什么需要这个服务. Mail.Ru 有很多有状态系统.用户的电子邮件存储就是其中之一.我们有几种方法可以跟踪该系统的状态变化以及系统事件,主要是通过定期系统轮询或者状态变化时的系统通知来实现.

  • go的websocket实现原理与用法详解

    本文实例讲述了go的websocket实现原理与用法.分享给大家供大家参考,具体如下: websocket分为握手和数据传输阶段,即进行了HTTP握手 + 双工的TCP连接 RFC协议文档在:http://tools.ietf.org/html/rfc6455 握手阶段 握手阶段就是普通的HTTP 客户端发送消息: GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebS

  • 利用Go语言搭建WebSocket服务端方法示例

    Go 搭建一个简单 WebSocket 服务端代码例子 test.go, 如下: package main import ( "fmt" "log" "net/http" "golang.org/x/net/websocket" ) func Echo(ws *websocket.Conn) { var err error for { var reply string if err = websocket.Message.Re

  • 利用 Go 语言编写一个简单的 WebSocket 推送服务

    本文中代码可以在 github.com/alfred-zhong/wserver获取. 背景 最近拿到需求要在网页上展示报警信息.以往报警信息都是通过短信,微信和 App 推送给用户的,现在要让登录用户在网页端也能实时接收到报警推送. 依稀记得以前工作的时候遇到过类似的需求.因为以前的浏览器标准比较陈旧,并且那时用 Java 较多,所以那时候解决这个问题就用了 Comet4J.具体的原理就是长轮询,长链接.但现在毕竟 html5 流行开来了,IE 都被 Edge 接替了,再用以前这种技术就显得过

  • 使用Go语言创建WebSocket服务的实现示例

    今天介绍如何用 Go 语言创建 WebSocket 服务,文章的前两部分简要介绍了 WebSocket 协议以及用 Go 标准库如何创建 WebSocket 服务.第三部分实践环节我们使用了 gorilla/websocket 库帮助我们快速构建 WebSocket 服务,它帮封装了使用 Go 标准库实现 WebSocket 服务相关的基础逻辑,让我们能从繁琐的底层代码中解脱出来,根据业务需求快速构建 WebSocket 服务. Go Web 编程系列的每篇文章的源代码都打了对应版本的软件包,供

  • node.js ws模块搭建websocket服务端的方法示例

    首先下载websocket模块,命令行输入 npm install ws node.js的 模块ws,可用于创建websocket服务,基本的express 和 http模块的使用 var express = require('express'); var http = require('http'); var WebSocket = require('ws'); var app = express(); var server = http.createServer(app); var wss

  • 使用Go语言创建静态文件服务器问题

    上篇关于Go模板库应用 的文章最后我们留下一个问题,页面模板是通过 CDN 引用的 BootStrap 的 css , js 文件.到目前位置我们的服务器还无法伺服客户端的静态文件请求把服务器磁盘上的文件响应给客户端.使用和配置过 Nginx 服务器的一定知道 Nginx 天然支持静态资源的访问,那么我们是不是也要借助 Nginx 才能实现处理静态文件请求呢?其实不是,在最开始的文章我们说过"Go语言不需要依赖任何第三方组件就能构建并启动一个高并发的 HTTP 服务器.",这篇文章就让

  • vs2019创建WebService服务的实现

    WebService是一种远程调用技术,也叫XML Web Service WebService,是一种可以接收从Internet或者Internet上的其他系统中传递过来的请求,轻量级的独立的通信技术.是通过SOAP在Web上提供的软件服务,使用WSDL文件进行说明,并通过UDDI进行注册. SOAP是什么? SOAP:全名为(Simple Object Access Protocol)简单对象存取协议.是XML Web Service的通信协议.当用户通过UDDI找到你的WSDL描述文档后,

  • C# 实现WebSocket服务端教程

    .net4.5中实现了对websocket的支持 在这里我使用的是.net4.0.因此需要对原本的socket发送的数据根据websocket的协议进行解析和打包. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net.Sockets; using System.Threading; using System.Net; namespace We

  • express中创建 websocket 接口及问题解答

    目录 整理 express-ws 源码 摒弃 express-ws 自己思考实现 express-ws 并解决它存在的问题 为什么要折腾这个? 大多数文章里的方法是直接安装 express-ws 然后进行使用: const express = require(`express`) const server = express() const expressWs = require(`express-ws`) expressWs(server) server.ws(`/aaa`, (ws, req

  • Go语言创建、初始化数组的常见方式汇总

    本文实例总结了Go语言创建.初始化数组的常见方式.分享给大家供大家参考.具体分析如下: Go语言的语法很灵活,以下展示了创建并初始化数组的多种方式: 复制代码 代码如下: //数组初始化的各种方式  func arraySliceTest0201() {      //创建数组(声明长度)      var array1 = [5]int{1, 2, 3}      fmt.Printf("array1--- type:%T \n", array1)      rangeIntPrin

  • AngularJS基于factory创建自定义服务的方法详解

    本文实例讲述了AngularJS基于factory创建自定义服务的方法.分享给大家供大家参考,具体如下: 为什么要创建自定义服务? 很简单,不想让控制器显得过于"臃肿",而且服务可复用.针对性强,每个服务对应不同的功能. 这里介绍如何使用factory创建自定义服务,并且使用他. 例子1: <!--HTML--> <div ng-controller="showTheName"> <h1 ng-bind="name"

  • 手把手教你在.NET中创建Web服务实现方法

    最近发现在.NET平台下使用Web服务还是很简单的.下面举个在.NET平台下创建Web服务的简单例子.首先用Visul Studio .Net创建一个C# 项目Asp.Net Web服务程序,源代码如下: 复制代码 代码如下: using System;using System.Collections;using System.ComponentModel;using System.Data;using System.Diagnostics;using System.Web;using Syst

随机推荐