在本教程中,我们将研究如何在Golang中使用Gorilla Websocket包。
这个库为我们提供了在Go中编写websocket客户端/服务器的便利。它有一个websocket协议的工作生产质量Go实现,使我们能够使用websocket处理有状态的http连接。
主要的仓库代码位于Github上。
现在让我们了解一下,我们如何使用Gorilla快速建立一个可测试的websocket应用程序。
安装Gorilla Websocket Go包
除了工作中的Go编译器外,没有其他依赖性,所以你只需要使用go get!
go get github.com/gorilla/websocket
Websocket应用程序的设计
在继续讨论任何实例之前,我们先来设计一下需要做的事情的大致布局。
任何使用websocket协议的应用程序一般都需要一个客户端和一个服务器。
服务器程序绑定到服务器上的一个端口,并开始监听任何websocket连接。连接相关的细节由websocket协议定义,它在原始HTTP连接上发挥作用。
客户端程序尝试使用websocket URL与服务器建立连接。请注意,客户端程序不需要用Golang实现,尽管Gorilla为我们提供了编写客户端的API。
如果你有一个使用独立前端的网络应用,一般来说,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连接。这就是为什么该库使用一个名为Upgrader 的struct 来帮助我们完成这一工作。
我们使用一个全局升级器变量来帮助我们将任何传入的HTTP连接转换成websocket协议,通过upgrader.Upgrade() 。这将返回给我们一个*websocket.Connection ,我们现在可以用它来处理websocket连接。
服务器使用conn.ReadMessage() 读取信息,并使用 conn.WriteMessage()写回信息。
该服务器只需将任何传入的websocket消息回传给客户端,因此这表明websockets可用于全双工通信。
现在让我们来看看客户端的实现: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 ,你可以有一个最小的架构,可以很容易地扩展。
希望你已经对如何在Golang中开始编写自己的WebSocket客户端/服务器程序有了一个很好的概念!这种风格在许多应用程序中都有使用。
Github中的许多开源项目也采用了同样的风格,因此你也可以在不同的软件库中探索,以了解这种架构在实际工作中是如何运作的。直到下一次!