go websocket
原文链接: go websocket
https://github.com/eranyanay/1m-go-websockets
使用Golang构建Websocket接口服务
Golang可以使用gorilla/websocket这个框架来实现websocket接口的构造.这个框架可以用于写客户端和服务器.
我们依然从一个helloworld开始.这个例子我们在客户端连同服务端后立即发送一个helloworld消息给后端服务器,服务器接到后则返回一个helloworld消息给客户端.
客户端在接收到服务器消息后发送一个close消息给服务器,服务器就断开和客户端的连接.
- 服务端
package main
import (
"flag"
"log"
"net/http"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:5000", "http service address")
var upgrader = websocket.Upgrader{} // use default options
func helloworldWsHanddler(ws *websocket.Conn) {
for {
mt, message, err := ws.ReadMessage()
if err != nil {
log.Println("read:", err)
break
} else {
switch mt {
case websocket.CloseMessage:
{
log.Println("get close signal")
break
}
case websocket.PingMessage, websocket.PongMessage:
{
log.Println("get ping pong")
}
case websocket.TextMessage:
{
log.Printf("recv: %s", message)
msg := string(message)
switch msg {
case "close":
{
break
}
case "helloworld":
{
err = ws.WriteMessage(websocket.TextMessage, []byte("Hello World"))
if err != nil {
log.Println("write:", err)
break
}
}
default:
{
err = ws.WriteMessage(websocket.TextMessage, []byte("unkonwn command"))
if err != nil {
log.Println("write:", err)
break
}
}
}
}
case websocket.BinaryMessage:
{
log.Println("not support Binary now")
}
default:
{
log.Println("not support now")
}
}
}
}
}
func helloworldHttpHanddler(w http.ResponseWriter, r *http.Request) {
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Print("upgrade:", err)
return
}
defer ws.Close()
helloworldWsHanddler(ws)
}
func main() {
flag.Parse()
log.SetFlags(0)
http.HandleFunc("/helloworld", helloworldHttpHanddler)
log.Fatal(http.ListenAndServe(*addr, nil))
}
服务端我们需要用一个http服务器监听一个url.在有客户端访问后,使用websocket.Upgrader{}
来将http访问提升为websocket连接.使用mt, message, err := ws.ReadMessage()
来获取消息.返回值的第一项为消息类型,第二项为message体.消息类型包含如下几种:
websocket.BinaryMessage
字节流数据websocket.TextMessage
文本数据websocket.PingMessage
保持连接用的消息websocket.PongMessage
保持连接用的消息websocket.CloseMessage
关闭信号
而发送回去一般使用的ws.WriteMessage(int , []byte) error
来发送消息.
因为mt有多种情况,我们一般使用switch来区分.一般为了可读性我们使用websocket.TextMessage
结合json来传递信息;
当然go语言天生亲和protobuf,所以也可以使用websocket.BinaryMessage
结合protobuf来传递消息.
- 客户端
package main
import (
"flag"
"log"
"net/url"
"os"
"os/signal"
"time"
"github.com/gorilla/websocket"
)
var addr = flag.String("addr", "localhost:5000", "http service address")
func main() {
flag.Parse()
log.SetFlags(0)
interrupt := make(chan os.Signal, 1)
signal.Notify(interrupt, os.Interrupt)
u := url.URL{Scheme: "ws", Host: *addr, Path: "/helloworld"}
log.Printf("connecting to %s", u.String())
ws, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
if err != nil {
log.Fatal("dial:", err)
}
defer ws.Close()
done := make(chan struct{})
// open后就发送
err = ws.WriteMessage(websocket.TextMessage, []byte("helloworld"))
if err != nil {
log.Println("write:", err)
}
go func() {
defer close(done)
for {
mt, message, err := ws.ReadMessage()
if err != nil {
log.Println("read:", err)
return
} else {
switch mt {
case websocket.CloseMessage:
{
log.Println("disconnected")
return
}
case websocket.PingMessage, websocket.PongMessage:
{
log.Println("get ping pong")
}
case websocket.TextMessage:
{
msg := string(message)
log.Printf("recv: %s", msg)
return
}
case websocket.BinaryMessage:
{
log.Println("not support Binary now")
return
}
default:
{
log.Println("not support now")
return
}
}
}
}
}()
for {
select {
case <-done:
return
case <-interrupt:
log.Println("interrupt")
// Cleanly close the connection by sending a close message and then
// waiting (with timeout) for the server to close the connection.
err := ws.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
if err != nil {
log.Println("write close:", err)
return
}
select {
case <-done:
case <-time.After(time.Second):
}
return
}
}
}
客户端部分与上面也类似.区分好消息类型就行
比较坑的是ws.WriteMessage
输入和ws.ReadMessage()
都是一个[]byte
类型的数据.因此即便是websocket.TextMessage
类型的数据也要做好类型转换.
package main
import (
"flag"
"log"
"os"
"syscall"
"time"
"github.com/pytool/chaos/beat"
"github.com/sevlyar/go-daemon"
)
var (
signal = flag.String("s", "", `send signal to the daemon
quit — graceful shutdown
stop — fast shutdown
reload — reloading the configuration file`)
)
const APPNAME = "snap"
func Daemon() {
flag.Parse()
daemon.AddCommand(daemon.StringFlag(signal, "quit"), syscall.SIGQUIT, termHandler)
daemon.AddCommand(daemon.StringFlag(signal, "stop"), syscall.SIGTERM, termHandler)
daemon.AddCommand(daemon.StringFlag(signal, "reload"), syscall.SIGHUP, reloadHandler)
os.Mkdir("/tmp/"+APPNAME, os.ModePerm)
cntxt := &daemon.Context{
PidFileName: "/tmp/" + APPNAME + "/" + APPNAME + ".pid",
PidFilePerm: 0644,
LogFileName: "/tmp/" + APPNAME + "/" + APPNAME + ".log",
LogFilePerm: 0640,
WorkDir: "./",
Umask: 027,
Args: []string{"[snap-daemon]"},
}
if len(daemon.ActiveFlags()) > 0 {
d, err := cntxt.Search()
if err != nil {
log.Fatalln("Unable send signal to the daemon:", err)
}
daemon.SendCommands(d)
return
}
d, err := cntxt.Reborn()
if err != nil {
log.Fatalln(err)
}
if d != nil {
return
}
defer cntxt.Release()
log.Println("- - - - - - - - - - - - - - -")
log.Println("daemon started")
go worker()
err = daemon.ServeSignals()
if err != nil {
log.Println("Error:", err)
}
log.Println("daemon terminated")
}
var (
stop = make(chan struct{})
done = make(chan struct{})
)
func worker() {
// go app.Run()
cancel := beat.Websocket()
for {
log.Println("loop")
time.Sleep(time.Second)
// 接收 控制台退出信号
if _, ok := <-stop; ok {
log.Println("recv ch stop")
break
}
}
cancel()
log.Println("done")
// 等待完全退出
done <- struct{}{}
}
func termHandler(sig os.Signal) error {
log.Println("terminating...")
stop <- struct{}{}
if sig == syscall.SIGQUIT {
<-done
}
return daemon.ErrStop
}
func reloadHandler(sig os.Signal) error {
log.Println("configuration reloaded")
return nil
}