go-websocket

1、websocket多人聊天室实现

1.1.1. webSocket是什么

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议
  • WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
  • 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
  • 需要安装第三方包:
    • cmd中:go get -u -v github.com/gorilla/websocket

这段代码是使用 Go 语言编写的,用于创建一个 WebSocket 升级器对象。

在 WebSocket 协议中,客户端和服务器之间的通信是通过一个双向的持久化连接来实现的。当客户端向服务器发送一个 WebSocket 连接请求时,服务器需要对该请求进行处理并将其升级为一个 WebSocket 连接。这个过程被称为 WebSocket 升级。

其中,ReadBufferSize 和 WriteBufferSize 分别表示读取和写入缓冲区的大小,CheckOrigin 函数用于检查 WebSocket 连接请求的来源是否合法。在这里,CheckOrigin 函数被设置为始终返回 true,也就是允许来自任何来源的 WebSocket 连接请求。

当使用 Upgrader 对象时,以下是一些建议:

  1. 在创建 Upgrader 对象时,需要为其设置一些属性,例如读取和写入缓冲区的大小、检查 WebSocket 连接请求的来源是否合法等。这些属性的设置应根据具体应用程序的需求来进行调整。
  2. 当收到 WebSocket 连接请求时,需要使用 Upgrader 对象的 Upgrade 方法将其升级为一个 WebSocket 连接。在调用 Upgrade 方法时,需要传入当前的 HTTP 请求和响应对象,以及一个处理 WebSocket 连接的回调函数。
var wu = &websocket.Upgrader{
	ReadBufferSize:  512,
	WriteBufferSize: 512,
	CheckOrigin: func(r *http.Request) bool {
		return true
	},
}
erver.go文件代码
package main

import (
    "fmt"
    "net/http"

    "github.com/gorilla/mux"
)

func main() {
    router := mux.NewRouter()
    go h.run()
    router.HandleFunc("/ws", myws)
    if err := http.ListenAndServe("127.0.0.1:8080", router); err != nil {
        fmt.Println("err:", err)
    }
}
hub.go文件代码
package main

import "encoding/json"

var h = hub{
    c: make(map[*connection]bool),
    u: make(chan *connection),
    b: make(chan []byte),
    r: make(chan *connection),
}

type hub struct {
    c map[*connection]bool
    b chan []byte
    r chan *connection
    u chan *connection
}

func (h *hub) run() {
    for {
        select {
        case c := <-h.r:
            h.c[c] = true
            c.data.Ip = c.ws.RemoteAddr().String()
            c.data.Type = "handshake"
            c.data.UserList = user_list
            data_b, _ := json.Marshal(c.data)
            c.sc <- data_b
        case c := <-h.u:
            if _, ok := h.c[c]; ok {
                delete(h.c, c)
                close(c.sc)
            }
        case data := <-h.b:
            for c := range h.c {
                select {
                case c.sc <- data:
                default:
                    delete(h.c, c)
                    close(c.sc)
                }
            }
        }
    }
}
data.go文件代码
package main

type Data struct {
    Ip       string   `json:"ip"`
    User     string   `json:"user"`
    From     string   `json:"from"`
    Type     string   `json:"type"`
    Content  string   `json:"content"`
    UserList []string `json:"user_list"`
}
connection.go文件代码
package main

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

    "github.com/gorilla/websocket"
)

type connection struct {
    ws   *websocket.Conn
    sc   chan []byte
    data *Data
}

var wu = &websocket.Upgrader{ReadBufferSize: 512,
    WriteBufferSize: 512, CheckOrigin: func(r *http.Request) bool { return true }}

func myws(w http.ResponseWriter, r *http.Request) {
    ws, err := wu.Upgrade(w, r, nil)
    if err != nil {
        return
    }
    c := &connection{sc: make(chan []byte, 256), ws: ws, data: &Data{}}
    h.r <- c
    go c.writer()
    c.reader()
    defer func() {
        c.data.Type = "logout"
        user_list = del(user_list, c.data.User)
        c.data.UserList = user_list
        c.data.Content = c.data.User
        data_b, _ := json.Marshal(c.data)
        h.b <- data_b
        h.r <- c
    }()
}

func (c *connection) writer() {
    for message := range c.sc {
        c.ws.WriteMessage(websocket.TextMessage, message)
    }
    c.ws.Close()
}

var user_list = []string{}

func (c *connection) reader() {
    for {
        _, message, err := c.ws.ReadMessage()
        if err != nil {
            h.r <- c
            break
        }
        json.Unmarshal(message, &c.data)
        switch c.data.Type {
        case "login":
            c.data.User = c.data.Content
            c.data.From = c.data.User
            user_list = append(user_list, c.data.User)
            c.data.UserList = user_list
            data_b, _ := json.Marshal(c.data)
            h.b <- data_b
        case "user":
            c.data.Type = "user"
            data_b, _ := json.Marshal(c.data)
            h.b <- data_b
        case "logout":
            c.data.Type = "logout"
            user_list = del(user_list, c.data.User)
            data_b, _ := json.Marshal(c.data)
            h.b <- data_b
            h.r <- c
        default:
            fmt.Print("========default================")
        }
    }
}

func del(slice []string, user string) []string {
    count := len(slice)
    if count == 0 {
        return slice
    }
    if count == 1 && slice[0] == user {
        return []string{}
    }
    var n_slice = []string{}
    for i := range slice {
        if slice[i] == user && i == count {
            return slice[:count]
        } else if slice[i] == user {
            n_slice = append(slice[:i], slice[i+1:]...)
            break
        }
    }
    fmt.Println(n_slice)
    return n_slice
}
local.html文件代码
<!DOCTYPE html>
<html>
<head>
    <title></title>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
    <style>
        p {
            text-align: left;
            padding-left: 20px;
        }
    </style>
</head>
<body>
<div style="width: 800px;height: 600px;margin: 30px auto;text-align: center">
    <h1>www.5lmh.comy演示聊天室</h1>
    <div style="width: 800px;border: 1px solid gray;height: 300px;">
        <div style="width: 200px;height: 300px;float: left;text-align: left;">
            <p><span>当前在线:</span><span id="user_num">0</span></p>
            <div id="user_list" style="overflow: auto;">
            </div>
        </div>
        <div id="msg_list" style="width: 598px;border:  1px solid gray; height: 300px;overflow: scroll;float: left;">
        </div>
    </div>
    <br>
    <textarea id="msg_box" rows="6" cols="50" onkeydown="confirm(event)"></textarea><br>
    <input type="button" value="发送" onclick="send()">
</div>
</body>
</html>
<script type="text/javascript">
    var uname = prompt('请输入用户名', 'user' + uuid(8, 16));
    var ws = new WebSocket("ws://127.0.0.1:8080/ws");
    ws.onopen = function () {
        var data = "系统消息:建立连接成功";
        listMsg(data);
    };
    ws.onmessage = function (e) {
        var msg = JSON.parse(e.data);
        var sender, user_name, name_list, change_type;
        switch (msg.type) {
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                var user_info = {'type': 'login', 'content': uname};
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.user_list;
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }
        var data = sender + msg.content;
        listMsg(data);
    };
    ws.onerror = function () {
        var data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };
    function confirm(event) {
        var key_num = event.keyCode;
        if (13 == key_num) {
            send();
        } else {
            return false;
        }
    }
    function send() {
        var msg_box = document.getElementById("msg_box");
        var content = msg_box.value;
        var reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        var msg = {'content': content.trim(), 'type': 'user'};
        sendMsg(msg);
        msg_box.value = '';
    }
    function listMsg(data) {
        var msg_list = document.getElementById("msg_list");
        var msg = document.createElement("p");
        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }
    function dealUser(user_name, type, name_list) {
        var user_list = document.getElementById("user_list");
        var user_num = document.getElementById("user_num");
        while(user_list.hasChildNodes()) {
            user_list.removeChild(user_list.firstChild);
        }
        for (var index in name_list) {
            var user = document.createElement("p");
            user.innerHTML = name_list[index];
            user_list.appendChild(user);
        }
        user_num.innerHTML = name_list.length;
        user_list.scrollTop = user_list.scrollHeight;
        var change = type == 'login' ? '上线' : '下线';
        var data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }
    function sendMsg(msg) {
        var data = JSON.stringify(msg);
        ws.send(data);
    }
    function uuid(len, radix) {
        var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        var uuid = [], i;
        radix = radix || chars.length;
        if (len) {
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
            var r;
            uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
            uuid[14] = '4';
            for (i = 0; i < 36; i++) {
                if (!uuid[i]) {
                    r = 0 | Math.random() * 16;
                    uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
                }
            }
        }
        return uuid.join('');
    }
</script>

2、代码关系图谱

Local.html Server.go Hub.go Connection.go Data.go 创建 hub 实例 h 创建 channel b, r, u 启动 hub 实例 h 发起 WebSocket 连接 升级连接为 WebSocket 将 connection 实例 c 加入到 hub 的 c map 中 发送连接请求 c 从 c 中读取消息 解析消息并根据消息类型进行处理 根据消息类型发送数据到 b 或 r 或 c 从 b 中读取消息并发送给客户端 发送消息 将消息发送到 c 的 sc channel 从 c 中读取消息并将消息发送到 b 遍历 c map 中的所有连接并将消息发送到每个连接的 sc channel 从 c 中读取消息 解析消息并根据消息类型进行处理 根据消息类型发送数据到 b 或 r 或 c 从 b 中读取消息并发送给客户端 关闭连接 关闭 WebSocket 连接 从 c 中读取消息 根据消息类型将连接从 c map 中删除并关闭连接 发送 logout 消息到 b Local.html Server.go Hub.go Connection.go Data.go

在这个流程图中,首先 server.go 创建了一个 hub 实例并启动该实例。当客户端通过 WebSocket 连接到服务器时,connection.go 升级连接为 WebSocket,然后将连接请求发送到 hub 实例中的 r channel。hub.go 从 r channel 中读取连接请求并将连接加入到 c map 中。客户端发送消息时,server.go 将消息发送到 connection.go 中的 sc channel。hub.go 从客户端的连接中读取消息并将消息发送到 b channel。然后,hub.go 遍历 c map 中的所有连接并将消息发送到每个连接的 sc channel。当客户端关闭连接时,connection.go 从 c channel 中读取消息并将连接从 c map 中删除,然后关闭连接。hub.go 发送 logout 消息到 b channel,通知其他客户端该用户已经下线。

以下是使用 Mermaid 格式描述这些文件之间如何处理消息阻塞的详细执行过程的流程图:

Local.html Server.go Hub.go Connection.go Data.go 创建 hub 实例 h 创建 channel b, r, u 启动 hub 实例 h 发起 WebSocket 连接 升级连接为 WebSocket 将 connection 实例 c 加入到 hub 的 c map 中 发送连接请求 c 从 c 中读取消息 解析消息并根据消息类型进行处理 根据消息类型发送数据到 b 或 r 或 c 从 b 中读取消息并发送给客户端 发送消息 将消息发送到 c 的 sc channel 从 c 中读取消息并将消息发送到 b 遍历 c map 中的所有连接并将消息发送到每个连接的 sc channel 从 c 中读取消息 解析消息并根据消息类型进行处理 根据消息类型发送数据到 b 或 r 或 c 从 b 中读取消息并发送给客户端 关闭连接 关闭 WebSocket 连接 从 c 中读取消息 根据消息类型将连接从 c map 中删除并关闭连接 发送 logout 消息到 b 从 r 或 b 或 c channel 中读取消息 处理读取的消息 根据消息类型选择不同的处理方式 如果 channel 已经关闭则从 c map 中删除连接 Local.html Server.go Hub.go Connection.go Data.go

在这个流程图中,当 hub.go 从 channel 中读取消息时,会阻塞等待消息的到来,直到有新消息到达或 channel 被关闭。如果 channel 被关闭,则会从 c map 中删除相应的连接。当客户端发送消息时,server.go 将消息发送到 connection.go 中的 sc channel。hub.go 从客户端的连接中读取消息并将消息发送到 b channel,然后遍历 c map 中的所有连接并将消息发送到每个连接的 sc channel。当客户端关闭连接时,connection.go 从 c channel 中读取消息并将连接从 c map 中删除,然后关闭连接。hub.go 从 r 或 b 或 c channel 中读取消息,然后根据消息类型选择不同的处理方式。整个过程中,channel 的阻塞机制保证了消息的有序处理,避免了消息的竞态条件和数据不一致等问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

陌微阳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值