• [Go WebSocket] 你的第一个Go WebSocket服务: echo server


    我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

    背景

    上篇文章:《为什么我选用Go重构Python版本的WebSocket服务?》,介绍了我的目标。

    从这篇文章开始,我们进入实战,正式介绍Go WebSocket框架。

    还没学过Go,要先看什么?

    建议你花1天时间,看一下Go的原理简介、基础语法。什么教程都可以,知名的教程就行。

    至少要明白:各种数据类型,控制流(for、if等)写法,弄懂channel和goroutine,如何加锁。

    一定要自己写写goroutine和channel试一下,了解一下基础语法。

    此外,还要了解常用包的用法,包括fmt、net/http。

    技术选型

    面对自己不熟悉的语言和不熟悉的框架,该怎么做技术选型呢?

    我告诉你个小技巧,直接在Github上搜索,看Star最多的那个仓库,就可以啦~

    1.png

    看吧,我们搜到了gorilla/websocket,star数以显著差异甩开了后面几名。这就没有什么好纠结的了,果断使用它。

    新建项目

    在使用GoLand时,新建Go Project会有2个选项:

    2.png

    我们选用第一个即可。

    如果你没有GoLand,也可以手动创建文件夹,在里面新建文件go.mod(我是使用的目前最新稳定版1.18)

    module echo
    
    go 1.18
    
    • 1
    • 2
    • 3

    安装依赖

    go get github.com/gorilla/websocket
    
    • 1

    拷贝echo代码

    gorilla/websocket的官方demo拷贝过来即可,我们慢慢分析:

    • https://github.com/gorilla/websocket/blob/master/examples/echo/server.go

    只需要拷贝这一个文件,命名为server.go即可。

    先尝试运行

    go run server.go
    
    • 1

    然后浏览器打开 localhost:8080就可以了~

    3.png

    • 点击「Open」建立WebSocket连接
    • 编辑好文本,按Send发送一个消息给服务器
    • 服务器立马回复一个一模一样的消息,这就是echo
    • 点击「Close」关闭连接,之后无法Send

    你的所有操作都会记录在页面上:

    4.png

    当然,也可以打开开发者工具,查看WebSocket连接,就像你查看Http请求那样。这篇文章教了你怎样使用Chrome的开发者面板抓包:《遇到表格,手动翻页太麻烦?我教你写脚本,一页展示所有数据》。

    5.png

    6.png

    代码解读

    引入依赖

    package main
    
    import (
       "flag"
       "html/template"
       "log"
       "net/http"
    
       "github.com/gorilla/websocket"
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    定义服务地址

    var addr = flag.String("addr", "localhost:8080", "http service address")
    
    • 1

    这是定义了服务器启动服务的地址,flag包用于处理命令行参数。意思是这个服务地址是可以通过命令行参数动态修改的。

    比如你可以这样启动:go run server.go -addr="localhost:8888"

    那么浏览器就应该打开localhost:8888来访问。

    当然如果你不需要命令后参数传入addr,完全可以删掉这行,改为:

    const addr = "localhost:8080"
    
    • 1

    同时,还要把main函数中,最后一行改成:(删掉了addr前面的星号)

    log.Fatal(http.ListenAndServe(addr, nil))
    
    • 1

    同时,把flag相关的行都删掉。(开头的import和main函数中的Parse)

    主函数

    我们先介绍一下主函数(虽然主函数定义在后面)。但是主函数有一个路由的作用,分发了请求。我们先介绍一下,方便后续理解。

    func main() {
       flag.Parse()
       log.SetFlags(0)
       http.HandleFunc("/echo", echo)
       http.HandleFunc("/", home)
       log.Fatal(http.ListenAndServe(*addr, nil))
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    我们通过net/http提供的能力,使用ListenAndServe启动了Http/WebSocket服务。

    其中,我们注册了2个处理函数,一个是针对path为/echo的,这是用echo函数处理。另一个是针对path为/的,这是用home函数处理。

    当你用浏览器直接访问localhost:8080时,是用了home函数处理,一个http请求,获得一个html文件,在浏览器展示。

    当你在JS中写new WebSocket('wss://localhost:8080/echo')时,是用了echo函数处理,一个WebSocket连接。

    我们接下来介绍这2个函数。

    定义echo服务(WebSocket协议)

    var upgrader = websocket.Upgrader{} // use default options
    
    func echo(w http.ResponseWriter, r *http.Request) {
       c, err := upgrader.Upgrade(w, r, nil)
       if err != nil {
          log.Print("upgrade:", err)
          return
       }
       defer c.Close()
       for {
          mt, message, err := c.ReadMessage()
          if err != nil {
             log.Println("read:", err)
             break
          }
          log.Printf("recv: %s", message)
          err = c.WriteMessage(mt, message)
          if err != nil {
             log.Println("write:", err)
             break
          }
       }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    当客户端使用new WebSocket('ws://localhost:8080/echo')建立时,就会开启一个goroutine,执行类似go echo(w, r)的操作。只要这个WebSocket没有关闭,那么这个goroutine就会一直存在。

    如果客户端关闭了WebSocket,或者服务端的这个goroutine执行结束了(因为有defer c.Close()),都会导致WebSocket断掉。这是合理且正确的,不这么写会有问题。

    这段echo函数很简单,不断循环,读取消息c.ReadMessage(),如果没消息,那么就会暂停执行,直到有了消息。有消息后,通过log打印收到的消息,并且通过c.WriteMessage(mt, message)输出消息给客户端。

    这里mt是消息类型Message Type,有2种:二进制消息、文本消息。

    当服务器输出完毕后,又在等待客户端的输入了。

    可以看到,目前是一个有序的线性服务:收一个、发一个、收一个、发一个。如果客户端同时发了100个,那么服务端也会按照这100个消息的顺序读取,并且按原先的顺序echo回去。处理完一个、才会去接收下一个。好处是保证了收发的顺序性(服务端发的顺序一定跟收的顺序一致),坏处是无法并发的读,性能有影响,如果每个处理收到消息要处理很久,后面的消息就阻塞、积压在内存中了。

    下一篇我们会介绍chat server,避免了这种问题。 敬请期待,可以先关注专栏、关注我噢~。

    Html文本服务(Http协议)

    func home(w http.ResponseWriter, r *http.Request) {
       homeTemplate.Execute(w, "ws://"+r.Host+"/echo")
    }
    
    var homeTemplate = template.Must(template.New("").Parse(`
    
    
    
    
    
    
    
    

    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.

    `
    ))
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77

    这个服务比较简单,就是Html模板渲染。

    注意有个模板变量:"ws://"+r.Host+"/echo",其实这个模板变量是不需要的。

    HTML中可以直接这么写:把ws = new WebSocket("{{.}}");改为ws = new WebSocket('ws://' + window.location.host + '/echo');

    写在最后

    我是HullQin,公众号线下聚会游戏的作者(欢迎关注公众号,发送加微信,交个朋友),转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费没广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我 HullQin 噢~我有空了会分享做游戏的相关技术。

  • 相关阅读:
    国际阿里云服务器退订过程与时间怎样呢?
    vue - 组件通信:关于v-model语法糖
    SpringBoot中过滤器与拦截器的区别
    1. Windows平台下如何编译C++版本的Redis库hiredis
    《深入理解java虚拟机 第三版》学习笔记一
    数据处理时代,数据质量建设才是企业的生存之道
    echarts实现柱图的下钻功能
    分布式搜索引擎es-3
    短剧app系统开发(对接广告联盟)源码搭建
    SpringBoot 手动渲染Thymeleaf模版工具类
  • 原文地址:https://blog.csdn.net/kd_2015/article/details/126825967