Gin+gorilla=a Golang WEBSOCKET SERVER

Source: Internet
Author: User
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&param1=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





Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.