今天要來介紹,如何用 golang 做一個 webosocket server 的相關應用。我個人沒有使用 golang 原生的 webscoket,而是用我前面一直有介紹到的 gorilla 團隊做的 gorilla/websocket,
他一樣有符合我的原則,而且沒有太多的依賴,非常適合拿來當個應用的基礎 package 。

gorilla/websocket

在開始介紹如何寫 websocket 之前,我們還是簡單科普一下 websocket 到底是什麼?

WebSocket是一種在單個TCP連接上進行全雙工通訊的協定。WebSocket通訊協定於2011年被IETF定為標準RFC 6455,並由RFC7936補充規範。WebSocket API也被W3C定為標準。
WebSocket使得用戶端和伺服器之間的資料交換變得更加簡單,允許伺服器端主動向用戶端推播資料。在WebSocket API中,瀏覽器和伺服器只需要完成一次交握,兩者之間就直接可以建立永續性的連接,並進行雙向資料傳輸。

由 wiki 摘錄上面的段話,可以把它想像成類似 socket 協定的東西,只是 websocket 是基於 http 之上的協定,但兩者最終都還是 TCP 之上的東西。運用這個特性,大家在 web 上想要實現即時的推播,就不需要使用輪詢等相關技術,減少 http request 相關檔頭肥大的問題。 剩下更詳細的介紹就請到 wiki 閱讀了。

接下來我們來介紹,如何運用 gorilla/websocket 寫一個簡單的 client / server 的應用

在開發之前,請記得下

go get github.com/gorilla/websocket

才有辦法執行下面範例,以及對應開發需求喔

websocket server

func main() {
	upgrader := &websocket.Upgrader{
		//如果有 cross domain 的需求,可加入這個,不檢查 cross domain
		CheckOrigin: func(r *http.Request) bool { return true },
	}
	http.HandleFunc("/echo", func(w http.ResponseWriter, r *http.Request) {
		c, err := upgrader.Upgrade(w, r, nil)
		if err != nil {
			log.Println("upgrade:", err)
			return
		}
		defer func() {
			log.Println("disconnect !!")
			c.Close()
		}()
		for {
			mtype, msg, err := c.ReadMessage()
			if err != nil {
				log.Println("read:", err)
				break
			}
			log.Printf("receive: %s\n", msg)
			err = c.WriteMessage(mtype, msg)
			if err != nil {
				log.Println("write:", err)
				break
			}
		}
	})
	log.Println("server start at :8899")
	log.Fatal(http.ListenAndServe(":8899", nil))
}

websocket client

func main() {

	c, _, err := websocket.DefaultDialer.Dial("ws://127.0.0.1:8899/echo", nil)
	if err != nil {
		log.Fatal("dial:", err)
	}
	defer c.Close()

	err = c.WriteMessage(websocket.TextMessage, []byte("hello ithome30day"))
	if err != nil {
		log.Println(err)
		return
	}
	_, msg, err := c.ReadMessage()
	if err != nil {
		log.Println("read:", err)
		return
	}
	log.Printf("receive: %s\n", msg)

}
hello ithome30day