使用golang怎么创建一个WebSocket服务
这篇文章给大家介绍使用golang怎么创建一个WebSocket服务,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。
WebSocket介绍
WebSocket
通信协议通过单个 TCP
连接提供全双工通信通道。与 HTTP
相比, WebSocket
不需要你为了获得响应而发送请求。它允许双向数据流,因此您只需等待服务器发送的消息即可。当 Websocket
可用时,它将向您发送一条消息。 对于需要连续数据交换的服务(例如即时通讯程序,在线游戏和实时交易系统), WebSocket
是一个很好的解决方案。 WebSocket
连接由浏览器请求,并由服务器响应,然后建立连接,此过程通常称为握手。 WebSocket
中的特殊标头仅需要浏览器与服务器之间的一次握手即可建立连接,该连接将在其整个生命周期内保持活动状态。 WebSocket
解决了许多实时 Web
开发的难题,并且与传统的 HTTP
相比,具有许多优点:
轻量级报头减少了数据传输开销。
单个Web客户端仅需要一个TCP连接。
WebSocket服务器可以将数据推送到Web客户端。
WebSocket协议实现起来相对简单。它使用 HTTP
协议进行初始握手。握手成功后即建立连接, WebSocket
实质上使用原始 TCP
读取/写入数据。
客户端请求如下所示:
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规范 定义了的一个客户机和服务器之间使用的特定帧格式。这是框架的位模式:
图:传输数据帧的位模式
使用以下代码对客户端有效负载进行解码:
//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
调用连接的
WriteMessage
和ReadMessage
方法发送和接收消息。上面的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
,在输入框中输入任何消息都能再次回显到浏览器中。
服务端则是把收到的消息打印到终端中然后把调用 writeMessage
把消息再回传给客户端,可以在终端中查看到记录。
关于使用golang怎么创建一个WebSocket服务就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。
推荐阅读
-
Golang中的nil和零值怎么声明
Golang中的nil和零值怎么声明这篇文章主要讲解了“Golan...
-
golang Gob怎么使用
-
golang切片长度与容量指的是什么
golang切片长度与容量指的是什么今天小编给大家分享一下gola...
-
golang循环遍历map的方式有哪些
-
golang如何添加list元素
-
golang包的特性有哪些
golang包的特性有哪些这篇文章主要介绍了golang包的特性有...
-
如何从 PHP 过渡到 Golang?
我是PHP开发者,转Go两个月了吧,记录一下使用Golang怎么一步步开发新项目。本着有坑填坑,有错改错的宗...
-
golang web从入门到精通 获取请求头信息、GET(POST)请求参数
-
适合PHP转Golang的函数类库
近几年Golang越来越火了,很多之前写PHP的也开始投入到Golang的怀抱中,今天小编给大家推荐一个挺好用的适合php转go的...
-
Golang编程 golang实现php函数json_decode()