前言
下雨声,伴随着窗外的车的声音。心中,思索自己的梦与想。
今天,我们来聊一下,GoLang中的Websocket使用。正如,我们在学习Java的Websocket时候,可能需要一个细致的学习过程。今天,我们就好好收拾心情,
学习走起!
WebSocket
首先,我们先来好好学习下,WebSocket是个什么鬼。以下呢,摘自 百度百科。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
Gorilla Websocket
在Go中编写websocket客户端/服务器的功能,在GoLang中使用Gorilla Websocket软件包。主要的存储库代码位于Github上。
现在让我们了解如何使用Gorilla快速设置可测试的Websocket应用程序。
安装Gorilla Websocket Go软件包
除了可以运行的Go编译器之外,没有其他依赖项,因此您只需要使用即可go get
!
go get github.com/gorilla/websocket
Websocket应用程序设计
在继续进行任何示例之前,让我们首先设计一个需要完成的工作的粗略布局。
任何使用websocket协议的应用程序通常都需要一个客户端和一个服务器。
服务器程序绑定到服务器上的端口,并开始侦听任何Websocket连接。与连接有关的详细信息由websocket协议定义,该协议通过原始HTTP连接起作用。
客户端程序尝试使用websocket URL与服务器建立连接。请注意,尽管Gorilla为我们提供了用于编写客户端的API,但无需使用Golang来实现客户端程序。
如果您的Web应用程序使用单独的前端,则通常Websocket客户端将以该语言(Javascript等)实现。
但是,出于说明的目的,我们将在Go中同时编写客户端程序和服务器程序。
现在,让我们的客户端-服务器体系结构运行!
我们将为server.go
服务器和client.go
客户端提供一个程序。
使用Gorilla Websockets –创建我们的服务器
该websocket服务器将在常规的http服务器上实现。我们将net/http
用于提供原始HTTP连接。
现在,在中server.go
,让我们编写常规的HTTP服务器,并添加一个socketHandler()
函数来处理websocket逻辑。
// server.go package main import ( "log" "net/http" "time" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{} // use default options func socketHandler(w http.ResponseWriter, r *http.Request) { // Upgrade our raw HTTP connection to a websocket based one conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Print("Error during connection upgradation:", err) return } defer conn.Close() // The event loop for { messageType, message, err := conn.ReadMessage() if err != nil { log.Println("Error during message reading:", err) break } log.Printf("Received: %s", message) err = conn.WriteMessage(messageType, message) if err != nil { log.Println("Error during message writing:", err) break } } } func home(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Index Page") } func main() { http.HandleFunc("/socket", socketHandler) http.HandleFunc("/", home) log.Fatal(http.ListenAndServe("localhost:8080", nil)) }
Gorilla的工作是转换原始HTTP连接进入一个有状态的websocket连接。
这就是为什么使用struct
调用Upgrader
来帮助我们的原因。
我们使用全局升级程序变量通过来帮助我们将任何传入的HTTP连接转换为websocket协议upgrader.Upgrade()
。这将返回给我们*websocket.Connection
,我们现在可以使用它来处理websocket连接。
服务器使用读取消息,然后使用conn.ReadMessage()
写入消息conn.WriteMessage()
该服务器只是将所有传入的Websocket消息回显到客户端,因此这说明了如何将Websocket用于全双工通信。
现在让我们转到的客户端实现client.go
。
创建我们的客户端程序
我们还将使用Gorilla编写客户端。这个简单的客户端将每隔1秒钟不断发出消息。如果我们的整个系统按预期工作,则服务器将接收间隔为1秒的数据包,并回复相同的消息。
客户端还将具有接收传入的Websocket数据包的功能。在我们的程序中,我们将有一个单独的goroutine处理程序receiveHandler
,用于侦听这些传入的数据包。
// client.go package main import ( "log" "os" "os/signal" "time" "github.com/gorilla/websocket" ) 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) // Our main loop for the client // We send our relevant packets here for { select { case <-time.After(time.Duration(1) * time.Millisecond * 1000): // Send an echo packet every second 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 interrupt signal. Closing all pending connections") // Close our websocket connection err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("Error during closing websocket:", err) return } select { case <-done: log.Println("Receiver Channel Closed! Exiting....") case <-time.After(time.Duration(1) * time.Second): log.Println("Timeout in closing receiving channel. Exiting....") } return }
如果您观察代码,您会发现我创建了两个通道done
,interrupt
用于receiveHandler()
和之间的通信main()
。
我们使用无限循环使用select来通过通道监听事件。我们conn.WriteMessage()
每秒钟写一条消息。如果激活了中断信号,则所有未决的连接都将关闭,并且我们可以正常退出!
嵌套select
是为了确保两件事:
- 如果
receiveHandler
通道退出,则通道'done'
将关闭。这是第一个case <-done
条件 - 如果
'done'
通道未关闭,则在1秒钟后会有超时,因此程序将在1秒钟超时后退出
通过使用通道仔细处理所有情况select
,您可以拥有一个可以轻松扩展的最小体系结构。
最后,让我们看看同时运行客户端和服务器时获得的输出!
输出
总结
学习是一件持续的事情,我们今天学习了Websocket的使用。
加油同志们,不变的努力!!