1. 程式人生 > >Go實戰 golang中使用WebSocket實時聊天室 gorilla/websocket nkovacs/go s

Go實戰 golang中使用WebSocket實時聊天室 gorilla/websocket nkovacs/go s

                     

生命不止,繼續 go go go!!!

其實,早就應該跟大家分享golang中關於websocket的使用,但是一直不知道從何入手,也不能夠很清晰的描述出來。

今天就淺嘗輒止,通過第三方庫實現websocket。

WebSocket

WebSocket協議是基於TCP的一種新的網路協議。它實現了瀏覽器與伺服器全雙工(full-duplex)通訊——允許伺服器主動傳送資訊給客戶端。 WebSocket通訊協議於2011年被IETF定為標準RFC 6455,並被RFC7936所補充規範。

WebSocket協議支援(在受控環境中執行不受信任的程式碼的)客戶端與(選擇加入該程式碼的通訊的)遠端主機之間進行全雙工通訊。用於此的安全模型是Web瀏覽器常用的基於原始的安全模式。 協議包括一個開放的握手以及隨後的TCP層上的訊息幀。 該技術的目標是為基於瀏覽器的、需要和伺服器進行雙向通訊的(伺服器不能依賴於開啟多個HTTP連線(例如,使用XMLHttpRequest或iframe和長輪詢))應用程式提供一種通訊機制。

這裡寫圖片描述

gorilla/websocket

A WebSocket implementation for Go.

Star: 4307

獲取:

go get github.com/gorilla/websocket
  • 1

Server

server.go

package mainimport (    "encoding/json"    "fmt"    "net/http"    "github.com/gorilla/websocket"    "github.com/satori/go.uuid")type ClientManager struct {    clients    map[*Client]bool    broadcast  chan
[]byte    register   chan *Client    unregister chan *Client}type Client struct {    id     string    socket *websocket.Conn    send   chan []byte}type Message struct {    Sender    string `json:"sender,omitempty"`    Recipient string `json:"recipient,omitempty"`    Content   string `json:"content,omitempty"`
}var manager = ClientManager{    broadcast:  make(chan []byte),    register:   make(chan *Client),    unregister: make(chan *Client),    clients:    make(map[*Client]bool),}func (manager *ClientManager) start() {    for {        select {        case conn := <-manager.register:            manager.clients[conn] = true            jsonMessage, _ := json.Marshal(&Message{Content: "/A new socket has connected."})            manager.send(jsonMessage, conn)        case conn := <-manager.unregister:            if _, ok := manager.clients[conn]; ok {                close(conn.send)                delete(manager.clients, conn)                jsonMessage, _ := json.Marshal(&Message{Content: "/A socket has disconnected."})                manager.send(jsonMessage, conn)            }        case message := <-manager.broadcast:            for conn := range manager.clients {                select {                case conn.send <- message:                default:                    close(conn.send)                    delete(manager.clients, conn)                }            }        }    }}func (manager *ClientManager) send(message []byte, ignore *Client) {    for conn := range manager.clients {        if conn != ignore {            conn.send <- message        }    }}func (c *Client) read() {    defer func() {        manager.unregister <- c        c.socket.Close()    }()    for {        _, message, err := c.socket.ReadMessage()        if err != nil {            manager.unregister <- c            c.socket.Close()            break        }        jsonMessage, _ := json.Marshal(&Message{Sender: c.id, Content: string(message)})        manager.broadcast <- jsonMessage    }}func (c *Client) write() {    defer func() {        c.socket.Close()    }()    for {        select {        case message, ok := <-c.send:            if !ok {                c.socket.WriteMessage(websocket.CloseMessage, []byte{})                return            }            c.socket.WriteMessage(websocket.TextMessage, message)        }    }}func main() {    fmt.Println("Starting application...")    go manager.start()    http.HandleFunc("/ws", wsPage)    http.ListenAndServe(":12345", nil)}func wsPage(res http.ResponseWriter, req *http.Request) {    conn, error := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil)    if error != nil {        http.NotFound(res, req)        return    }    client := &Client{id: uuid.NewV4().String(), socket: conn, send: make(chan []byte)}    manager.register <- client    go client.read()    go client.write()}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129

Go Client

package mainimport (    "flag"    "fmt"    "net/url"    "time"    "github.com/gorilla/websocket")var addr = flag.String("addr", "localhost:12345", "http service address")func main() {    u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"}    var dialer *websocket.Dialer    conn, _, err := dialer.Dial(u.String(), nil)    if err != nil {        fmt.Println(err)        return    }    go timeWriter(conn)    for {        _, message, err := conn.ReadMessage()        if err != nil {            fmt.Println("read:", err)            return        }        fmt.Printf("received: %s\n", message)    }}func timeWriter(conn *websocket.Conn) {    for {        time.Sleep(time.Second * 2)        conn.WriteMessage(websocket.TextMessage, []byte(time.Now().Format("2006-01-02 15:04:05")))    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

HTML Client

test_websocket.html

<html><head><title>Golang Chat</title><script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script><script type="text/javascript">    $(function() {    var conn;    var msg = $("#msg");    var log = $("#log");    function appendLog(msg) {        var d = log[0]        var doScroll = d.scrollTop == d.scrollHeight - d.clientHeight;        msg.appendTo(log)        if (doScroll) {            d.scrollTop = d.scrollHeight - d.clientHeight;        }    }    $("#form").submit(function() {        if (!conn) {            return false;        }        if (!msg.val()) {            return false;        }        conn.send(msg.val());        msg.val("");        return false    });    if (window["WebSocket"]) {        conn = new WebSocket("ws://localhost:12345/ws");        conn.onclose = function(evt) {            appendLog($("<div><b>Connection Closed.</b></div>"))        }        conn.onmessage = function(evt) {            appendLog($("<div/>").text(evt.data))        }    } else {        appendLog($("<div><b>WebSockets Not Support.</b></div>"))    }    });</script><style type="text/css">html {    overflow: hidden;}body {    overflow: hidden;    padding: 0;    margin: 0;    width: 100%;    height: 100%;    background: gray;}#log {    background: white;    margin: 0;    padding: 0.5em 0.5em 0.5em 0.5em;    position: absolute;    top: 0.5em;    left: 0.5em;    right: 0.5em;    bottom: 3em;    overflow: auto;}#form {    padding: 0 0.5em 0 0.5em;    margin: 0;    position: absolute;    bottom: 1em;    left: 0px;    width: 100%;    overflow: hidden;}</style></head><body><div id="log"></div><form id="form">    <input type="submit" value="傳送" />    <input type="text" id="msg" size="64"/></form></body></html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

執行: 執行server,執行client,瀏覽器開啟html檔案: 這裡寫圖片描述

這裡寫圖片描述

實時聊天室

main.go:

package mainimport (    "log"    "net/http"    "github.com/gorilla/websocket")var clients = make(map[*websocket.Conn]bool) // connected clientsvar broadcast = make(chan Message)           // broadcast channel// Configure the upgradervar upgrader = websocket.Upgrader{    CheckOrigin: func(r *http.Request) bool {        return true    },}// Define our message objecttype Message struct {    Email    string `json:"email"`    Username string `json:"username"`    Message  string `json:"message"`}func main() {    // Create a simple file server    fs := http.FileServer(http.Dir("../public"))    http.Handle("/", fs)    // Configure websocket route    http.HandleFunc("/ws", handleConnections)    // Start listening for incoming chat messages    go handleMessages()    // Start the server on localhost port 8000 and log any errors    log.Println("http server started on :8000")    err := http.ListenAndServe(":8000", nil)    if err != nil {        log.Fatal("ListenAndServe: ", err)    }}func handleConnections(w http.ResponseWriter, r *http.Request) {    // Upgrade initial GET request to a websocket    ws, err := upgrader.Upgrade(w, r, nil)    if err != nil {        log.Fatal(err)    }    // Make sure we close the connection when the function returns    defer ws.Close()    // Register our new client    clients[ws] = true    for {        var msg Message        // Read in a new message as JSON and map it to a Message object        err := ws.ReadJSON(&msg)        if err != nil {            log.Printf("error: %v", err)            delete(clients, ws)            break        }        // Send the newly received message to the broadcast channel        broadcast <- msg    }}func handleMessages() {    for {        // Grab the next message from the broadcast channel        msg := <-broadcast        // Send it out to every client that is currently connected        for client := range clients {            err := client.WriteJSON(msg)            if err != nil {                log.Printf("error: %v", err)                client.Close()                delete(clients, client)            }        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87

app.js:

new Vue({    el: '#app',    data: {        ws: null, // Our websocket        newMsg: '', // Holds new messages to be sent to the server        chatContent: '', // A running list of chat messages displayed on the screen        email: null, // Email address used for grabbing an avatar        username: null, // Our username        joined: false // True if email and username have been filled in    },    created: function() {        var self = this;        this.ws = new WebSocket('ws://' + window.location.host + '/ws');        this.ws.addEventListener('message', function(e) {            var msg = JSON.parse(e.data);            self.chatContent += '<div class="chip">'                    + '<img src="' + self.gravatarURL(msg.email) + '">' // Avatar                    + msg.username                + '</div>'                + emojione.toImage(msg.message) + '<br/>'; // Parse emojis            var element = document.getElementById('chat-messages');            element.scrollTop = element.scrollHeight; // Auto scroll to the bottom        });    },    methods: {        send: function () {            if (this.newMsg != '') {                this.ws.send(                    JSON.stringify({                        email: this.email,                        username: this.username,                        message: $('<p>').html(this.newMsg).text() // Strip out html                    }                ));                this.newMsg = ''; // Reset newMsg            }        },        join: function () {            if (!this.email) {                Materialize.toast('You must enter an email', 2000);                return            }            if (!this.username) {                Materialize.toast('You must choose a username', 2000);                return            }            this.email = $('<p>').html(this.email).text();            this.username = $('<p>').html(this.username).text();            this.joined = true;        },        gravatarURL: function(email) {            return 'http://www.gravatar.com/avatar/' + CryptoJS.MD5(email);        }    }});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

index.html:

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>Simple Chat</title>    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/css/materialize.min.css">    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">    <link rel="stylesheet" href="https://cdn.jsdelivr.net/emojione/2.2.6/assets/css/emojione.min.css"/>    <link rel="stylesheet" href="/style.css"></head><body><header>    <nav>        <div class="nav-wrapper">            <a href="/" class="brand-logo right">Simple Chat</a>        </div>    </nav></header><main id="app">    <div class="row">        <div class="col s12">            <div class="card horizontal">                <div id="chat-messages" class="card-content" v-html="chatContent">                </div>            </div>        </div>    </div>    <div class="row" v-if="joined">        <div class="input-field col s8">            <input type="text" v-model="newMsg" @keyup.enter="send">        </div>        <div class="input-field col s4">            <button class="waves-effect waves-light btn" @click="send">                <i class="material-icons right">chat</i>                Send            </button>        </div>    </div>    <div class="row" v-if="!joined">        <div class="input-field col s8">            <input type="email" v-model.trim="email" placeholder="Email">        </div>        <div class="input-field col s8">            <input type="text" v-model.trim="username" placeholder="Username">        </div>        <div class="input-field col s4">            <button class="waves-effect waves-light btn" @click="join()">                <i class="material-icons right">done</i>                Join            </button>        </div>    </div></main><footer class="page-footer"></footer><script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script><script src="https://cdn.jsdelivr.net/emojione/2.2.6/lib/js/emojione.min.js"></script><script src="https://code.jquery.com/jquery-2.1.1.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/md5.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.97.8/js/materialize.min.js"></script><script src="/app.js"></script></body></html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65

style.css:

body {    display: flex;    min-height: 100vh;    flex-direction: column;}main {    flex: 1 0 auto;}#chat-messages {    min-height: 10vh;    height: 60vh;    width: 100%;    overflow-y: scroll;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

這裡寫圖片描述

nkovacs/go-socket.io

獲取: go get github.com/nkovacs/go-socket.io

main.go

package mainimport (    "log"    "net/http"    "github.com/nkovacs/go-socket.io")func main() {    server, err := socketio.NewServer(nil)    if err != nil {        log.Fatal(err)    }    server.On("connection", func(so socketio.Socket) {        log.Println("on connection")        so.Join("chat")        so.On("chat message", func(msg string) {            log.Println("emit:", so.Emit("chat message", msg))            so.BroadcastTo("chat", "chat message", msg)        })        so.On("disconnection", func() {            log.Println("on disconnect")        })    })    server.On("error", func(so socketio.Socket, err error) {        log.Println("error:", err)    })    http.Handle("/socket.io/", server)    http.Handle("/", http.FileServer(http.Dir("./public")))    log.Println("Serving at localhost:12345...")    log.Fatal(http.ListenAndServe(":12345", nil))}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35

index.html

<!doctype html><html>  <head>    <title>Socket.IO chat</title>    <style>      * { margin: 0; padding: 0; box-sizing: border-box; }      body { font: 13px Helvetica, Arial; }      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }      #messages { list-style-type: none; margin: 0; padding: 0; }      #messages li { padding: 5px 10px; }      #messages li:nth-child(odd) { background: #eee; }    </style>  </head>  <body>    <ul id="messages"></ul>    <form action="">      <input id="m" autocomplete="off" /><button>Send</button>    </form>    <script>      var socket = io();      $('form').submit(function(){        socket.emit('chat message', $('#m').val());        $('#m').val('');        return false;      });      socket.on('chat message', function(msg){        $('#messages').append($('<li>').text(msg));      });    </script>  </body></html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34