This is a creation in Article, where the information may have evolved or changed.
Since chat has become the basic function of most apps, and most of the app user base is not spicy, the common chat server architecture such as XMPP or message queue implementation and so on is quite troublesome, it is more difficult to interact with the web side, coupled with the H5 standard Landing, So WebSocket has become a lightweight and high-availability Chat server implementation method;
WebSocket server is commonly used in Nodejs or Java Netty Framework implementation, Netty relatively heavy, direct buffer memory leakage is more troublesome, try a go, light, stable good, good performance, So with go to achieve a bit;
WebSocket agreement standards and basic concepts on-line search a piece, here do not repeat;
HTTP server with gin to do, WebSocket handler with gorilla, because do not repeat to build the wheel, so the whole process of building quickly;
import (
"util"
"os"
"fmt"
"github.com/DeanThompson/ginpprof"
"github.com/gin-gonic/gin"
"runtime"
)
var (
logger * util.LogHelper
)
func main () {
runtime.GOMAXPROCS (runtime.NumCPU ())
logFile, err: = os.OpenFile ("/ var / log / gows.log", os.O_CREATE | os.O_RDWR, 0777)
if err! = nil {
fmt.Println (err.Error ())
os.Exit (0)
}
defer logFile.Close ()
logger = util.NewLogger (logFile)
logger.Info ("Starting system ...")
wsHandler: = new (WebSocketHandler)
gin.SetMode (gin.ReleaseMode)
r: = gin.Default ()
r.GET ("/", func (c * gin.Context) {
wsHandler.HandleConn (c.Writer, c.Request)
})
ginpprof.Wrapper (r) // For debugging, you can see the stack status and all goroutine status
// err = r.Run (listenPath, certPath, keyPath) This can support wss
err = r.Run ("127.0.0.1:8888")
if err! = nil {
fmt.Println (err)
}
}
So we can have the entrance.
The websocket pattern is probably onopen OnMessage onerror onclose Four callback to cover the entire communication process
So let's take a look at the simple version of the Websockethandler implementation
package main
import (
"bytes"
"compress / gzip"
"encoding / json"
"errors"
"net / http"
"strconv"
"time"
"util"
"github.com/gorilla/websocket"
)
var (
ctxHashMap = util.NewConcurrentMap ()
)
// Used to upgrade http protocol to ws protocol
type WebSocketHandler struct {
wsupgrader websocket.Upgrader
}
func (wsh * WebSocketHandler) NewWebSocketHandler () {
wsh.wsupgrader = websocket.Upgrader {
ReadBufferSize: 4096,
WriteBufferSize: 4096,
}
}
func (wsh * WebSocketHandler) onMessage (conn * websocket.Conn, ctx * ConnContext, msg [] byte, msgType int) {
// Process text messages or binary messages. Binary numbers are usually gzip texts. Voices, pictures, videos, etc. are generally used by other cloud services or the bandwidth will burst.
if msgType == websocket.TextMessage {
wsh.processIncomingTextMsg (conn, ctx, msg)
}
if msgType == websocket.BinaryMessage {
}
}
func (wsh * WebSocketHandler) onOpen (conn * websocket.Conn, r * http.Request) (ctx * ConnContext, err error) {
if err: = r.ParseForm (); err! = nil {
return nil, errors.New ("Parameter check error")
}
specialKey: = r.FormValue ("specialKey")
supportGzip: = r.FormValue ("support_gzip")
ctx = & ConnContext {specialKey, supportGzip}
// Used to identify a tcp link
keyString: = ctx.AsHashKey ()
if oldConn, ok: = ctxHashMap.Get (keyString); ok {
wsh.onClose (oldConn. (* websocket.Conn), ctx)
oldConn. (* websocket.Conn) .Close ()
}
ctxHashMap.Set (keyString, conn)
return ctx, nil
}
func (wsh * WebSocketHandler) onClose (conn * websocket.Conn, ctx * ConnContext) {
logger.Info ("client close itself as" + ctx.String ())
wsh.closeConnWithCtx (ctx)
return
}
func (wsh * WebSocketHandler) onError (errMsg string) {
logger.Error (errMsg)
}
func (wsh * WebSocketHandler) HandleConn (w http.ResponseWriter, r * http.Request) {
wsh.wsupgrader.CheckOrigin = func (r * http.Request) bool {return true}
conn, err: = wsh.wsupgrader.Upgrade (w, r, nil)
if err! = nil {
logger.Error ("Failed to set websocket upgrade:" + err.Error ())
return
}
defer conn.Close ()
if ctx, err: = wsh.onOpen (conn, r); err! = nil {
logger.Error ("Open connection failed" + err.Error () + r.URL.RawQuery)
return
} else {
conn.SetPingHandler (func (message string) error {
conn.WriteControl (websocket.PongMessage, [] byte (message), time.Now (). Add (time.Second))
return nil
})
for {
t, msg, err: = conn.ReadMessage ()
if err! = nil {
logger.Error ("READ ERR FROM" + ctx.String () + "ERR" + err.Error ())
wsh.onClose (conn, ctx)
return
}
switch t {
case websocket.TextMessage, websocket.BinaryMessage:
wsh.onMessage (conn, ctx, msg, t)
case websocket.CloseMessage:
wsh.onClose (conn, ctx)
return
case websocket.PingMessage:
case websocket.PongMessage:
}
}
}
}
func (wsh * WebSocketHandler) closeConnWithCtx (ctx * ConnContext) {
keyString: = ctx.AsHashKey ()
ctxHashMap.Remove (keyString)
return
}
func (wsh * WebSocketHandler) processIncomingTextMsg (conn * websocket.Conn, ctx * ConnContext, msg [] byte) {
logger.Debug ("CLIENT SAID" + string (msg))
sendMessageToAll (msg)
}
func (wsh * WebSocketHandler) sendMessageToAll (msg [] byte]) {
var gzMsg bytes.Buffer
gzWriter: = gzip.NewWriter (& gzMsg)
gzWriter.Write (msg)
gzWriter.Flush ()
gzWriter.Close ()
for key, conn: = range ctxHashMap.Items () {
if ctx, err: = HashKeyAsCtx (key. (string)); err! = nil {
wsh.onError (err.Error ())
} else {
if ctx.supportGzip == "1" {
err = conn. (* websocket.Conn) .WriteMessage (websocket.BinaryMessage, gzMsg.Bytes ())
logger.Debug ("send binary msg to" + ctx.String ())
} else {
err = conn. (* websocket.Conn) .WriteMessage (websocket.TextMessage, [] byte (msg))
logger.Debug ("send text msg to" + ctx.String ())
}
if err! = nil {
wsh.onClose (conn. (* websocket.Conn), ctx)
conn. (* websocket.Conn) .Close ()
wsh.onError ("WRITE ERR TO" + key. (string) + "ERR:" + err.Error ())
}
}
}
}
Because of the deletion of some online code sensitive information so may not compile, but almost a meaning, mainly look at temperament
Inside an inexplicable called ctx things appear many times in fact is connectioncontext abbreviation, General link shape such as ws://ip:port/?param=value¶m1=value1 form, of course, will encrypt, So in the OnOpen, the URL will be the base check, and back to record some key parameters of the URL tag, to confirm the message to whom to send
A simple Conncontext implementation is as follows
// connContext.go
package main
import (
"errors"
"strings"
"util"
)
type ConnContext struct {
specialKey string
supportGzip string
}
func HashKeyAsCtx (hashKey string) (* ConnContext, error) {
values: = strings.Split (hashKey, ":")
if (len (values)! = 2) {
return nil, errors.New ("Emma key is wrong:" + hashKey)
} else {
return & ConnContext {values [0], values [1]}, nil
}
}
func (ctx * ConnContext) AsHashKey () string {
return strings.Join ([] string {ctx.specialKey, ctx.supportGzip}, ":")
}
func (ctx * ConnContext) String () string {
return util.NewStringBuilder ("specialkey:", ctx.specialkey, "gzip", ctx.supportGzip) .String ()
}
Above a simple WebSocket server to complete the gratifying
Something to find here.
Http://weibo.com/SandCu