使用golang怎么创建一个WebSocket服务

这篇文章给大家介绍使用golang怎么创建一个WebSocket服务,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

WebSocket介绍

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

  1. 轻量级报头减少了数据传输开销。

  2. 单个Web客户端仅需要一个TCP连接。

  3. WebSocket服务器可以将数据推送到Web客户端。

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

使用golang怎么创建一个WebSocket服务

客户端请求如下所示:

GET/chatHTTP/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.1101SwitchingProtocols
Upgrade:websocket
Connection:Upgrade
Sec-WebSocket-Accept:HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol:chat

如何在Go中创建WebSocket应用

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

  • 发起握手

  • 从客户端接收数据帧

  • 发送数据帧给客户端

  • 关闭握手

发起握手

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

//HTTPserverwithWebSocketendpoint
funcServer(){
http.HandleFunc("/",func(whttp.ResponseWriter,r*http.Request){
ws,err:=NewHandler(w,r)
iferr!=nil{
//handleerror
}
iferr=ws.Handshake();err!=nil{
//handleerror
}
…

然后初始化 WebSocket 结构。

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

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

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

//NewHandlerinitializesanewhandler
funcNewHandler(whttp.ResponseWriter,req*http.Request)(*WS,error){
hj,ok:=w.(http.Hijacker)
if!ok{
//handleerror
}.....
}

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

//Handshakecreatesahandshakeheader
func(ws*WS)Handshake()error{

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

returnbase64.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规范 定义了的一个客户机和服务器之间使用的特定帧格式。这是框架的位模式:

使用golang怎么创建一个WebSocket服务

图:传输数据帧的位模式

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

//RecvreceivesdataandreturnsaFrame
func(ws*WS)Recv()(frameFrame,_error){
frame=Frame{}
head,err:=ws.read(2)
iferr!=nil{
//handleerror
}

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

//SendsendsaFrame
func(ws*WS)Send(frFrame)error{
//makeasliceofbytesoflength2
data:=make([]byte,2)

//Savefragmentation&opcodeinformationinthefirstbyte
data[0]=0x80|fr.Opcode
iffr.IsFragment{
data[0]&=0x7F
}
.....

关闭握手

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

//ClosesendsacloseframeandclosestheTCPconnection
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)
iferr:=ws.Send(f);err!=nil{
returnerr
}
returnws.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
packagews

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

varupgrader=websocket.Upgrader{
	ReadBufferSize:1024,
	WriteBufferSize:1024,
}

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

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

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

		//把消息写回客户端,完成回音
		iferr=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
packagews

import"net/http"

funcDisplayEcho(whttp.ResponseWriter,r*http.Request){
	http.ServeFile(w,r,"views/websockets.html")
}

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

<form>
<inputid="input"type="text"/>
<buttononclick="send()">Send</button>
<preid="output"></pre>
</form>
...
<script>
varinput=document.getElementById("input");
varoutput=document.getElementById("output");
varsocket=newWebSocket("ws://localhost:8000/ws/echo");

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

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

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

注册路由

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

//router/router.go
funcRegisterRoutes(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 ,在输入框中输入任何消息都能再次回显到浏览器中。

使用golang怎么创建一个WebSocket服务

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

使用golang怎么创建一个WebSocket服务

关于使用golang怎么创建一个WebSocket服务就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

发布于 2021-03-21 22:38:26
收藏
分享
海报
0 条评论
172
上一篇:怎么在Spring Boot中使用EasyCode插件一键生成代码 下一篇:怎么在python中拼接多行字符串
目录

    0 条评论

    本站已关闭游客评论,请登录或者注册后再评论吧~

    忘记密码?

    图形验证码