WebSocket 和 Golang 實現聊天功能

jopen 10年前發布 | 88K 次閱讀 WebSocket 電話/通訊/IM聊天

這個示例應用程序展示了如何使用 WebSocket, GolangjQuery 創建一個簡單的web聊天應用程序。這個示例的源代碼在 https://github.com/waylau/goChat

Running the example 運行示例

這個示例需要 Golang 開發環境。 該頁面描述如何安裝開發環境。

一旦你去啟動和運行,您可以下載、構建和運行的例子, 使用命令:

go get gary.burd.info/go-websocket-chat
go-websocket-chat

在支持 websocket 的瀏覽器嘗試打開 http://127.0.0.1:8080/ 啟動應用

Server 服務器

服務器程序實現了 http 包,包含了 Go 分發和 Gorilla 項目的 websocket 包.

應用程序定義了兩種類型, connection 和 hub 。服務器為每個 webscocket 連接 創建的一個 connection 類型的實例 。 連接器扮演了 websocket 和 hub 類型單例 之間的媒介 。 hub 保持一組注冊了的連接器 和 廣播到連接器的信息。

程序運行了一個 goroutine 給 hub 和兩個 goroutine 給每個連接器。 goroutine 通過 channel 和其他進行交流。 hub 擁有注冊連接器、注銷連接器和廣播信息的 channel。一個連機器擁有緩存的發出信息的 channel 。其中一個 連接器的 goroutine 從這個 channel 中讀信息 并把信息寫入 webscoket。另外一個連接器 goroutine 從 websocket 讀信息,并把信息發送到 hub。

下面是 hub 類型代碼:

package main

type hub struct { // 注冊了的連接器 connections map[*connection]bool

// 從連接器中發入的信息
broadcast chan []byte

// 從連接器中注冊請求
register chan *connection

// 從連接器中注銷請求
unregister chan *connection

}

var h = hub{ broadcast: make(chan []byte), register: make(chan connection), unregister: make(chan connection), connections: make(map[*connection]bool), }

func (h *hub) run() { for { select { case c := <-h.register: h.connections[c] = true case c := <-h.unregister: if _, ok := h.connections[c]; ok { delete(h.connections, c) close(c.send) } case m := <-h.broadcast: for c := range h.connections { select { case c.send <- m: default: delete(h.connections, c) close(c.send) } } } } }</pre>

應用程序的 主要 函數啟動 hub 以 goroutine 形式運行方法。連接器 發送請求到 hub 通過 注冊、注銷和廣播 channel。

hub 注冊連接器通過添加 connection 的指針作為 connections map 的主鍵。這個 map 的值通常是 true。

注銷的代碼有點復雜。除了從 connections map 刪除連接器的指針外, hub 關閉了 connection 的發送,來標識沒有信息再被發送到 connection了。

hub 通過循環注冊連接器和發送信息到連接器的發送 channel 來控制信息。 如果連接器的發送緩沖區已經滿了,那么 hub 假設 客戶端已死或卡住了。這種情況下, hub 注銷連接器 并關閉 websocket.

下面關于 connection 類型的代碼:

package main

import ( "github.com/gorilla/websocket" "net/http" )

type connection struct { // websocket 連接器 ws *websocket.Conn

// 發送信息的緩沖 channel 
send chan []byte

}

func (c *connection) reader() { for { _, message, err := c.ws.ReadMessage() if err != nil { break } h.broadcast <- message } c.ws.Close() }

func (c *connection) writer() { for message := range c.send { err := c.ws.WriteMessage(websocket.TextMessage, message) if err != nil { break } } c.ws.Close() }

var upgrader = &websocket.Upgrader{ReadBufferSize: 1024, WriteBufferSize: 1024}

func wsHandler(w http.ResponseWriter, r *http.Request) { ws, err := upgrader.Upgrade(w, r, nil) if err != nil { return } c := &connection{send: make(chan []byte, 256), ws: ws} h.register <- c defer func() { h.unregister <- c }() go c.writer() c.reader() }</pre>

wsHandler 方法被主函數當做http handler注冊。HTTP 連接到 WebSocket 協議的升級,創建一個連接對象,注冊這個連接到 sub ,并通過 defer延遲語句 來控制 連接的注銷。

接著,wsHandler 方法開啟 連接器的寫入方法作為一個 goroutine。 寫入方法將信息從連接器的 channel 轉入 websocket。當 hub 關閉 channel 或者 在寫入 websocket 時出錯,寫入方法關閉。

最后,wsHandler 方法 調用連接器的 讀 方法。 讀方法將 入站消息 從 websocket 轉到 hub。

這里是服務器的代碼的其余部分:

package main

import ( "flag" "go/build" "log" "net/http" "path/filepath" "text/template" )

var ( addr = flag.String("addr", ":8080", "http service address") assets = flag.String("assets", defaultAssetPath(), "path to assets") homeTempl *template.Template )

func defaultAssetPath() string { p, err := build.Default.Import("gary.burd.info/go-websocket-chat", "", build.FindOnly) if err != nil { return "." } return p.Dir }

func homeHandler(c http.ResponseWriter, req *http.Request) { homeTempl.Execute(c, req.Host) }

func main() { flag.Parse() homeTempl = template.Must(template.ParseFiles(filepath.Join(assets, "home.html"))) go h.run() http.HandleFunc("/", homeHandler) http.HandleFunc("/ws", wsHandler) if err := http.ListenAndServe(addr, nil); err != nil { log.Fatal("ListenAndServe:", err) } }</pre>

應用主程序啟動 hub goroutine。 接著 主程序 注冊 主頁 和 websocket 連接器的控制器N。最后主程序啟動 HTTP 服務器。

Client 客戶端

客戶端的實現是一個簡單的 HTML 文件:

<html>
<head>
<title>Chat Example</title>
<script type="text/javascript" src=";
<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://{{$}}/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>Your browser does not support WebSockets.</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="Send" /> <input type="text" id="msg" size="64"/> </form> </body> </html></pre>

客戶端使用 jQuery

文檔加載。腳本檢查 websocket 的功能 。如果 WebSocket 功能 可以用,然后打開腳本與服務器的連接,并注冊一個回調處理來自服務器的信息。回調使用 appendlog 方法將消息添加到聊天記錄。

appendlog 方法檢查在添加新的內容時的滾動位置,從而可以讓用戶手動滾動聊天記錄而不會被新來的消息中斷。如果聊天記錄滾動至底部,那么新內容添加的到舊內容的后面。否則,滾動的位置不會改變。

表單處理器將用戶的輸入寫入到 WebSocket 并且清除輸入字段。

參考:http://gary.burd.info/go-websocket-chat

來自:http://www.waylau.com/go-websocket-chat/

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!