go-webSocket——gorilla
Posted 尚墨1111
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go-webSocket——gorilla相关的知识,希望对你有一定的参考价值。
文章目录
gorilla-websocket
参考文章:
-
GitHub:https://github.com/gorilla/websocket
-
Doc:https://godoc.org/github.com/gorilla/websocket
0 HTTP
http 是典型的 C/S 架构,客户端向服务端发送请求(request),服务端做出应答(response)。
golang 的标准库 net/http
提供了 http 编程有关的接口,封装了内部TCP连接和报文解析的复杂琐碎的细节,使用者只需要和 http.request
和 http.ResponseWriter
两个对象交互。
0.1 http.HandleFunc
源码,相当于一个适配器
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request)
f(w, r)
0.1.1 简单实现
package main
import (
"io"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request)
io.WriteString(w, "hello world!\\n")
func main()
// 回调函数
http.HandleFunc("/", helloHandler)
http.ListenAndServe(":8080", nil)
0.1.2 net/http 提供的handler
大部分的服务器逻辑都需要使用者编写对应的 Handler,不过有些 Handler 使用频繁,因此 net/http
提供了它们的实现。
- 比如负责文件 hosting 的
FileServer
- 负责 404 的
NotFoundHandler
- 负责重定向的
RedirectHandler
0.1.3 ListenAndServer()
// addr:监听的地址
// handler:回调函数
func ListenAndServe(addr string, handler Handler) error
server := &ServerAddr: addr, Handler: handler
return server.ListenAndServe()
http.ListenAndServe("127.0.0.1:8000", nil)
0.1.4 Request
Request 就是封装好的客户端请求,包括 URL,method,header 等等所有信息,以及一些方便使用的方法:
Handler 需要知道关于请求的任何信息,都要从这个对象中获取,一般不会直接修改这个对象
type Request struct
// Method specifies the HTTP method (GET, POST, PUT, etc.).For client requests an empty string means GET.
Method string
// URL specifies either the URI being requested (for server requests) or the URL to access (for client requests).
URL *url.URL
// The protocol version for incoming requests.
// Client requests always use HTTP/1.1.
Proto string // "HTTP/1.0"
ProtoMajor int // 1
ProtoMinor int // 0
// A header maps request lines to their values.
// If the header says
//
// accept-encoding: gzip, deflate
// Accept-Language: en-us
// Connection: keep-alive
//
// then
//
// Header = map[string][]string
// "Accept-Encoding": "gzip, deflate",
// "Accept-Language": "en-us",
// "Connection": "keep-alive",
//
Header Header
// Body is the request's body.
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
...
RemoteAddr string
...
0.1.5 ResponseWriter
ResponseWriter 是一个接口,定义了三个方法:
Header()
:返回一个 Header 对象,可以通过它的Set()
方法设置头部,注意最终返回的头部信息可能和你写进去的不完全相同,因为后续处理还可能修改头部的值(比如设置Content-Length
、Content-type
等操作)Write()
: 写 response 的主体部分,比如html
或者json
的内容就是放到这里的WriteHeader()
:设置 status code,如果没有调用这个函数,默认设置为http.StatusOK
, 就是200
状态码
// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
type ResponseWriter interface
Header() Header
Write([]byte) (int, error)
WriteHeader(int)
0.2 test
0.2.1 HTTP服务端
package main
import (
"fmt"
"net/http"
)
func main()
http.HandleFunc("/go", myHandler)
http.ListenAndServe("127.0.0.1:8000", nil)
func myHandler(w http.ResponseWriter, r *http.Request)
fmt.Println(r.RemoteAddr, "连接成功")
// 请求方式:GET POST DELETE PUT UPDATE
fmt.Println("method:", r.Method)
fmt.Println("url:", r.URL.Path)
fmt.Println("header:", r.Header)
fmt.Println("body:", r.Body)
// 回复
w.Write([]byte("test成功"))
0.2.2 HTTP客户端
package main
import (
"fmt"
"io"
"net/http"
)
func main()
//resp, _ := http.Get("http://www.baidu.com")
//fmt.Println(resp)
resp, _ := http.Get("http://127.0.0.1:8000/go")
defer resp.Body.Close()
// 200 OK
fmt.Println(resp.Status)
fmt.Println(resp.Header)
buf := make([]byte, 1024)
for
// 接收服务端信息
n, err := resp.Body.Read(buf)
if err != nil && err != io.EOF
fmt.Println(err)
return
else
fmt.Println("读取完毕")
res := string(buf[:n])
fmt.Println(res)
break
1 webSocket
1.1 是什么
- WebSocket是一种在单个TCP连接上进行全双工通信的协议,长连接,双向传输
- 需要安装第三方包:go get -u -v github.com/gorilla/websocket
- WebSocket 协议实现起来相对简单。
HTTP
协议初始握手建立连接,WebSocket
实质上使用原始TCP
读取 / 写入数据 - http有良好的兼容性,ws和http的默认端口都是80,wss和https的默认端口都是443
1.2 webSocket握手协议
1.2.1 客户端请求 Request Header
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket // 指明使用WebSocket协议
Connection: Upgrade // 指明使用WebSocket协议
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== // Bse64 encode的值,是浏览器随机生成的
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13 //指定Websocket协议版本
Origin: http://example.com
服务端收到Sec-WebSocket-Key后拼接上一个固定的GUID,进行一次SHA-1摘要,再转成Base64编码,得到Sec-WebSocket-Accept返回给客户端。客户端对本地的Sec-WebSocket-Key执行同样的操作跟服务端返回的结果进行对比,如果不一致会返回错误关闭连接。如此操作是为了把websocket header 跟http header区分开
1.2.2 服务器响应 Response Header
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat
1.2.3 websocket发送的消息类型
5种:TextMessag、BinaryMessage、CloseMessag、PingMessage、PongMessage
-
TextMessag
和BinaryMessage
分别表示发送文本消息和二进制消息 -
CloseMessage
关闭帧,接收方收到这个消息就关闭连接 -
PingMessage
和PongMessage
是保持心跳的帧,服务器发ping
给浏览器,浏览器返回pong
消息
2 gorilla/websocket
websocket由http升级而来,首先发送附带Upgrade请求头的Http请求,所以我们需要在处理Http请求时拦截请求并判断其是否为websocket升级请求,如果是则调用gorilla/websocket
库相应函数处理升级请求
2.1 Upgrader
Upgrader
发送附带Upgrade请求头的Http请求,把 http 请求升级为长连接的 WebSocket
,结构如下:
type Upgrader struct
// 升级 websocket 握手完成的超时时间
HandshakeTimeout time.Duration
// io 操作的缓存大小,如果不指定就会自动分配。
ReadBufferSize, WriteBufferSize int
// 写数据操作的缓存池,如果没有设置值,write buffers 将会分配到链接生命周期里。
WriteBufferPool BufferPool
//按顺序指定服务支持的协议,如值存在,则服务会从第一个开始匹配客户端的协议。
Subprotocols []string
// http 的错误响应函数,如果没有设置 Error 则,会生成 http.Error 的错误响应。
Error func(w http.ResponseWriter, r *http.Request, status int, reason error)
// 如果请求Origin标头可以接受,CheckOrigin将返回true。 如果CheckOrigin为nil,则使用安全默认值:如果Origin请求头存在且原始主机不等于请求主机头,则返回false。
// 请求检查函数,用于统一的链接检查,以防止跨站点请求伪造。如果不检查,就设置一个返回值为true的函数
CheckOrigin func(r *http.Request) bool
// EnableCompression 指定服务器是否应尝试协商每个邮件压缩(RFC 7692)。 将此值设置为true并不能保证将支持压缩。 目前仅支持“无上下文接管”模式
EnableCompression bool
2.1.1 创建Upgrader实例
该实例用于升级请求
var upgrader = websocket.Upgrader
ReadBufferSize: 1024, //指定读缓存大小
WriteBufferSize: 1024, //指定写缓存大小
CheckOrigin: checkOrigin,
// 检测请求来源
func checkOrigin(r *http.Request) bool
if r.Method != "GET"
fmt.Println("method is not GET")
return false
if r.URL.Path != "/ws"
fmt.Println("path error")
return false
return true
其中CheckOringin
是一个函数,该函数用于拦截或放行跨域请求。函数返回值为bool
类型,即true
放行,false
拦截。如果请求不是跨域请求可以不赋值
2.1.2 升级协议
func (*Upgrader) Upgrade
函数将 http 升级到 WebSocket 协议。
// responseHeader包含在对客户端升级请求的响应中。
// 使用responseHeader指定cookie(Set-Cookie)和应用程序协商的子协议(Sec-WebSocket-Protocol)。
// 如果升级失败,则升级将使用HTTP错误响应回复客户端
// 返回一个 Conn 指针,使用 Conn 读写数据与客户端通信。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error)
升级为websocket连接并获得一个conn实例,之后的发送接收操作皆有conn,其类型为websocket.Conn。
//Http入口
func (e *Engine) ServeHTTP(w http.ResponseWriter, r *http.Request)
//判断请求是否为websocket升级请求。
if websocket.IsWebSocketUpgrade(r)
// 收到 http 请求后升级协议
conn, err := upgrader.Upgrade(w, r, w.Header())
// 向客户端发送消息使用 WriteMessage(messageType int, data []byte),参数1为消息类型,参数2消息内容
conn.WriteMessage(websocket.TextMessage, []byte("升级成功"))
// 接受客户端消息使用 ReadMessage(),该操作阻塞线程所以建议运行在其他协程上。
//返回值(接收消息类型、接收消息内容、发生的错误)当然正常执行时错误为 nil。一旦连接关闭返回值类型为-1可用来终止读操作。
go func()
for
t, c, _ := conn.ReadMessage()
fmt.Println(t, string(c))
if t == -1
return
()
else
//处理普通请求
c := newContext(w, r)
e.router.handle(c)
2.1.3 设置关闭连接监听
函数为SetCloseHandler(h func(code int, text string) error)
函数接收一个函数为参数,参数为nil时有一个默认实现,其源码为:
func (c *Conn) SetCloseHandler(h func(code int, text string) error)
if h == nil
h = func(code int, text string) error
message := FormatCloseMessage(code, "")
c.WriteControl(CloseMessage, message, time.Now().Add(writeWait))
return nil
c.handleClose = h
可以看到作为参数的函数的参数为int和string类型正好和前端的close(long string)对应即前端调用close(long string)关闭连接后两个参数会被发送给后端并最终被func(code int, text string) error
所使用。
// 设置关闭连接监听
conn.SetCloseHandler(func(code int, text string) error
fmt.Println(code, text) // 断开连接时将打印code和text
return nil
)
2.1.4 总览
type WsServer struct
......
// 定义一个 upgrade 类型用于升级 http 为 websocket
upgrade *websocket.Upgrader
func NewWsServer() *WsServer
ws.upgrade = &websocket.Upgrader
ReadBufferSize: 4096,//指定读缓存区大小
WriteBufferSize: 1024,// 指定写缓存区大小
// 检测请求来源
CheckOrigin: func(r *http.Request) bool
if r.Method != "GET"
fmt.Println("method is not GET")
return false
if r.URL.Path != "/ws"
fmt.Println("path error")
return false
return true
,upgrade
return ws
func (self *WsServer) ServeHTTP(w http.ResponseWriter, r *http.Request)
......
// 收到 http 请求后 升级 协议
conn, err := self.upgrade.Upgrade(w, r, nil)
if err != nil
fmt.Println("websocket error:", err)
return
fmt.Println("client connect :", conn.RemoteAddr())
go self.connHandle(conn)
3 Demo
3.1
3.1.1 服务端
package main
import (
"fmt"
"github.com/gorilla/websocket"
"log"
"net/http"
)
var upgrader = websocket.Upgrader
ReadBufferSize: 4096,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool
//if r.Method != "GET"
// fmt.Println("method is not GET")
// return false
//
//if r.URL.Path != "/ws"
// fmt.Println("path error")
// return false
//
return true
,
// ServerHTTP 用于升级协议
func ServerHTTP(w http.ResponseWriter, r *http.Request)
// 收到http请求之后升级协议
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil
log.Println("Error during connection upgrade:", err)
return
defer conn.Close()
for
// 服务端读取客户端请求
messageType, message, err := conn.ReadMessage()
if err != nil
log.Println("Error during message reading:", err)
break
log.Printf("Received:%s", message)
// 开启关闭连接监听
conn.SetCloseHandler(func(code int, text string) error
fmt.Println(code, text) // 断开连接时将打印code和text
return nil
)
//服务端给客户端返回请求
err = conn.WriteMessage(messageType, message)
if err != nil
log.Println("Error during message writing:", err)
return
func home(w http.ResponseWriter, r *http.Request)
fmt.Fprintf(w, "Index Page")
func main()
http.HandleFunc("/socket", ServerHTTP)
http.HandleFunc("/", home)
log.Fatal(http.ListenAndServe("localhost:8080", nil))
3.1.2 客户端
// client.go
package main
import (
"github.com/gorilla/websocket"
"log"
"os"
"os/signal"
"time"
)
var done chan interface
var interrupt chan os.Signal
func receiveHandler(connection *websocket.Conn)
defer close(done)
for
_, msg, err := connection.ReadMessage()
if err != nil
log.Println("Error in receive:", err)
return
log.Printf("Received: %s\\n", msg)
func main()
done = make(chan interface) // Channel to indicate that the receiverHandler is done
interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully
signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT
socketUrl := "ws://localhost:8080" + "/socket"
conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
if err != nil
log.Fatal("Error connecting to Websocket Server:", err)
defer conn.Close()
go receiveHandler(conn)
// 无限循环使用select来通过通道监听事件
for
select
case <-time.After(time.Duration(1) * time.Millisecond * 1000):
//conn.WriteMessage()每秒钟写一条消息
err := conn.WriteMessage(websocket.TextMessage, []byte("Hello from GolangDocs!"))
if err != nil
log.Println("Error during writing to websocket:", err)
return
//如果激活了中断信号,则所有未决的连接都将关闭
case <-interrupt:
// We received a SIGINT (Ctrl + C). Terminate gracefully...
log.Println("Received SIGINT 以上是关于go-webSocket——gorilla的主要内容,如果未能解决你的问题,请参考以下文章
GolangWeb 入门 08 集成 Gorilla Mux
GolangWeb 入门 08 集成 Gorilla Mux