1. 程式人生 > >Go websocket 聊天室的詳細實現和詳細分析

Go websocket 聊天室的詳細實現和詳細分析

websocket 聊天室資料結構分析
  • 首先要做一個聊天室我們需要把所有的連線資訊都儲存下來
  • 所以就需要有一個客戶端 clientmanagermanager 裡應該儲存所有的client 資訊
  • 所以在我們的程式裡定義了 ClientManager 這個結構體
  • clients 這個 map 結構來儲存所有的連線資訊
  • 遍歷 clients 通過使用 broadcast 這個 channelweb 端傳送來的訊息分發給所有的客戶端client

  • 其次每個成功建立長連線的 client 開一個 read 協程和 wrtie
    協程
  • read 協程不斷讀取 web 端輸入的 meaasge,並把 message 傳遞給 boradcast ,讓 manager 遍歷 clientsmessage 通過 broadcast channel ,傳遞給各個客戶端 clientsend channel
  • write 協程不斷的將 send channel 裡的訊息傳送給 web

流程圖

在這裡插入圖片描述

具體程式碼
  • server.go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/gorilla/websocket"
"github.com/satori/go.uuid" ) //客戶端管理 type ClientManager struct { //客戶端 map 儲存並管理所有的長連線client,線上的為true,不在的為false clients map[*Client]bool //web端傳送來的的message我們用broadcast來接收,並最後分發給所有的client broadcast chan []byte //新建立的長連線client register chan *Client //新登出的長連線client unregister chan *Client } //客戶端 Client
type Client struct { //使用者id id string //連線的socket socket *websocket.Conn //傳送的訊息 send chan []byte } //會把Message格式化成json type Message struct { //訊息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 { //如果有新的連線接入,就通過channel把連線傳遞給conn case conn := <-manager.register: //把客戶端的連線設定為true manager.clients[conn] = true //把返回連線成功的訊息json格式化 jsonMessage, _ := json.Marshal(&Message{Content: "/A new socket has connected."}) //呼叫客戶端的send方法,傳送訊息 manager.send(jsonMessage, conn) //如果連線斷開了 case conn := <-manager.unregister: //判斷連線的狀態,如果是true,就關閉send,刪除連線client的值 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) } } } } } //定義客戶端管理的send方法 func (manager *ClientManager) send(message []byte, ignore *Client) { for conn := range manager.clients { //不給遮蔽的連線傳送訊息 if conn != ignore { conn.send <- message } } } //定義客戶端結構體的read方法 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 } //如果沒有錯誤資訊就把資訊放入broadcast jsonMessage, _ := json.Marshal(&Message{Sender: c.id, Content: string(message)}) manager.broadcast <- jsonMessage } } func (c *Client) write() { defer func() { c.socket.Close() }() for { select { //從send裡讀訊息 case message, ok := <-c.send: //如果沒有訊息 if !ok { c.socket.WriteMessage(websocket.CloseMessage, []byte{}) return } //有訊息就寫入,傳送給web端 c.socket.WriteMessage(websocket.TextMessage, message) } } } func main() { fmt.Println("Starting application...") //開一個goroutine執行開始程式 go manager.start() //註冊預設路由為 /ws ,並使用wsHandler這個方法 http.HandleFunc("/ws", wsHandler) //監聽本地的8011埠 http.ListenAndServe(":8011", nil) } func wsHandler(res http.ResponseWriter, req *http.Request) { //將http協議升級成websocket協議 conn, err := (&websocket.Upgrader{CheckOrigin: func(r *http.Request) bool { return true }}).Upgrade(res, req, nil) if err != nil { http.NotFound(res, req) return } //每一次連線都會新開一個client,client.id通過uuid生成保證每次都是不同的 client := &Client{id: uuid.Must(uuid.NewV4()).String(), socket: conn, send: make(chan []byte)} //註冊一個新的連結 manager.register <- client //啟動協程收web端傳過來的訊息 go client.read() //啟動協程把訊息返回給web端 go client.write() }

  • client.go
package main

import (
	"flag"
	"fmt"
	"github.com/gorilla/websocket"
	"net/url"
)

//定義連線的服務端的網址
var addr = flag.String("addr", "localhost:8011", "http service address")

func main() {
	u := url.URL{Scheme: "ws", Host: *addr, Path: "/ws"}
	var dialer *websocket.Dialer

	//通過Dialer連線websocket伺服器
	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")))
//	}
//}


  • chatroom.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>


  • 本文章是對 一蓑煙雨1989 文章的分析 ,如想了解原文章可以點選超連線檢視原文