分享一波WebSocket图解加多人聊天室详细解释

网络编程基础部分

脑图链接:http://note.youdao.com/noteshare?id=c112cd41ab78aa9cfd9695d1b74dc90f&sub=92A36F7B9E3D4AAEA42AED00258495B8

网络编程七层模型

在这里插入图片描述

图片引入配合下面代码理解

在这里插入图片描述

多人聊天室代码

server服务端

package main

import (
	"fmt"
	"github.com/gorilla/mux"
	"net/http"
)

func main() {
    
    
	// 创建路由
	router := mux.NewRouter()
	// ws控制器不断去处理管道数据,进行同步数据
	go h.run()
	// 指定ws回调函数
	router.HandleFunc("/ws", mYws)
	// 开启服务端监听
	if err := http.ListenAndServe("127.0.0.1:9001", router);
	err != nil {
    
    
		fmt.Println("err:", err)
	}
}

data结构体

package main

// 将连接中传输的数据抽象出对象
type Data struct {
    
    
	Ip       string   `json:"ip"`
	// 用户列表
	User     string   `json:"user"`
	// 代表哪个用户说的
	From     string   `json:"from"`
	// 标识信息的类型
	// login 登录
	// handshake 握手信息,刚打开网页的状态
	// system 系统信息,xxx上线了
	// logout 退出信息
	// user 普通信息
	Type     string   `json:"type"`
	// 传输内容
	Content  string   `json:"content"`
	// 用户列表
	UserList []string `json:"user_list"`
}

hub

package main

import (
	"encoding/json"
)

// 抽象ws连接器
// 处理ws中的各种逻辑
type hub struct {
    
    
	// connections注册了连接器
	c map[*connection]bool
	// 从连接器发送的信息
	b chan []byte
	// 从连接器注册请求
	r chan *connection
	// 销毁请求
	u chan *connection
}


// 将连接器对象初始化
var h = hub{
    
    
	// connections注册了连接器
	c: make(map[*connection]bool),
	// 从连接器发送的信息
	b: make(chan []byte),
	// 从连接器注册请求
	r: make(chan *connection),
	// 销毁请求
	u: make(chan *connection),
}

// 处理ws的逻辑实现
func (h *hub) run() {
    
    
	// 监听数据管道, 在后端不断处理管道数据
	for {
    
    
		// 根据不同的数据管道,处理不同的逻辑
		select {
    
    
		// 注册
		case c := <-h.r:
			// 标志注册了
			h.c[c] = true
			// 组装data数据
			c.data.Ip = c.ws.RemoteAddr().String()
			// 更新类型
			c.data.Type = "handshake"
			// 用户列表
			c.data.UserList = UserList
			dataB, _ := json.Marshal(c.data)
			// 将数据放入数据管道
			c.sc <- dataB
		case c := <-h.u:
			// 判断map里面存在要删的数据
			if _, ok := h.c[c]; ok {
    
    
				delete(h.c, c)
				close(c.sc)
			}
		case data := <-h.b:
			// 处理数据流转,将数据同步到所有用户
			// c是具体的每一个连接
			for c := range h.c {
    
    
				// 将数据同步
				select {
    
    
				case c.sc <- data:
				default:
					// 防止死循环
					delete(h.c, c)
					close(c.sc)
				}
			}
		}
	}
}

connection

package main
import (
	"encoding/json"
	"fmt"
	"github.com/gorilla/websocket"
	"net/http"
)

// 抽象出一个需要的结构体
// ws连接器 数据 管道
type connection struct {
    
    
	// ws连接器
	ws   *websocket.Conn
	// 管道
	sc   chan []byte
	// 数据
	data *Data
}

// 定义升级器,将http请求升级为ws请求
var wu = &websocket.Upgrader{
    
    ReadBufferSize: 512,
	WriteBufferSize: 512, CheckOrigin: func(r *http.Request) bool {
    
     return true }}

// ws的回调函数
func mYws(w http.ResponseWriter, r *http.Request) {
    
    
	// 1.获取ws对象
	ws, err := wu.Upgrade(w, r, nil)
	if err != nil {
    
    
		return
	}
	// 创建连接对象去做事情
	// 初始化连接对象
	c := &connection{
    
    sc: make(chan []byte, 256), ws: ws, data: &Data{
    
    }}
	// 在ws中注册一下
	h.r <- c
	// ws将数据读写跑起来
	go c.writer()
	c.reader()
	defer func() {
    
    
		c.data.Type = "logout"
		// 用户列表删除
		UserList = del(UserList, c.data.User)
		// 数据序列化,让所有人看到XXX下线了
		c.data.UserList = UserList
		c.data.Content = c.data.User
		//println(c.data.Type)
		dataB, _ := json.Marshal(c.data)
		h.b <- dataB
		h.u <- c
	}()
}

// 先实现ws的读和写
// ws中写数据

//var user_list = []string{}
func (c *connection) writer() {
    
    
	// 从管道遍历数据
	for message := range c.sc {
    
    
		// 数据写出 写入方法需要一个utf-8类型一个管道数据
		err := c.ws.WriteMessage(websocket.TextMessage, message)
		if err != nil {
    
    
		}
	}
	c.ws.Close()
}

var UserList = make([]string,0)

// ws连接中读数据
func (c *connection) reader() {
    
    
	// 不断读websocket数据
	for {
    
    
		// 返回数据:类型,数据,状态
		_, message, err := c.ws.ReadMessage()
		// 发生错误: 关闭连接返回值类型为-1,可用来终止读操作
		if err != nil {
    
    
			// 读不进数据,将用户移除
			h.r <- c
			break
		}
		// 读取数据
		// json使用反序列化,将数据传入,反射需要拿到对象地址
		// Unmarshal和Marshal做相反的操作
		_ = json.Unmarshal(message, &c.data)
		// 根据data的type判断该做什么
		switch c.data.Type {
    
    
		case "login":
			// 弹出窗口,输用户名
			c.data.User = c.data.Content
			c.data.From = c.data.User
			// 登录后,将用户加入到用户列表
			UserList = append(UserList, c.data.User)
			// 每个用户都加载所有登录了的列表
			c.data.UserList = UserList
			// 数据序列化
			dataB, _ := json.Marshal(c.data)
			h.b <- dataB
			// 用户状态
		case "user":
			c.data.Type = "user"
			dataB, _ := json.Marshal(c.data)
			h.b <- dataB
		case "logout":
			c.data.Type = "logout"
			// 用户列表删除
			UserList = del(UserList, c.data.User)
			// 数据序列化,让所有人看到XXX下线了
			dataB, _ := json.Marshal(c.data)
			h.b <- dataB
			//fmt.Println(c.data.Type)
			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{}
	// 定义新的返回切片
	var nSlice = make([]string,0)
	// 删除传入切片中的指定用户,其他用户放到新的切片
	for i := range slice {
    
    
		// 利用索引删用户
		if slice[i] == user && i == count {
    
    
			return slice[:count]
		} else if slice[i] == user {
    
    
			nSlice = append(slice[:i], slice[i+1:]...)
			break
		}
	}
	//fmt.Println(nSlice)
	return nSlice
}

local前端

<!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));
    let uName = prompt('请输入用户名', 'user' + uuid(8, 16));
    let ws = new WebSocket("ws://127.0.0.1:9001/ws");
    ws.onopen = function () {
     
     
        let data = "系统消息:建立连接成功";
        listMsg(data);
    };
    ws.onmessage = function (e) {
     
     
        let msg = JSON.parse(e.data);
        let sender, user_name, name_list, change_type;
        // alert(msg.type)
        switch (msg.type) {
     
     
            case 'system':
                sender = '系统消息: ';
                break;
            case 'user':
                sender = msg.from + ': ';
                break;
            case 'handshake':
                let user_info = {
     
     'type': 'login', 'content': uName};
                sendMsg(user_info);
                return;
            case 'login':
            case 'logout':
                user_name = msg.content;
                name_list = msg.user_list;
                // console.log(msg.user_list)
                change_type = msg.type;
                dealUser(user_name, change_type, name_list);
                return;
        }
        let data = sender + msg.content;
        listMsg(data);
    };
    ws.onerror = function () {
     
     
        let data = "系统消息 : 出错了,请退出重试.";
        listMsg(data);
    };
    function confirm(event) {
     
     
        let key_num = event.keyCode;
        if (13 === key_num) {
     
     
            send();
        } else {
     
     
            return false;
        }
    }
    function send() {
     
     
        let msg_box = document.getElementById("msg_box");
        let content = msg_box.value;
        let reg = new RegExp("\r\n", "g");
        content = content.replace(reg, "");
        let msg = {
     
     'content': content.trim(), 'type': 'user'};
        sendMsg(msg);
        msg_box.value = '';
    }
    function listMsg(data) {
     
     
        let msg_list = document.getElementById("msg_list");
        let msg = document.createElement("p");
        msg.innerHTML = data;
        msg_list.appendChild(msg);
        msg_list.scrollTop = msg_list.scrollHeight;
    }
    function dealUser(user_name, type, name_list) {
     
     
        let user_list = document.getElementById("user_list");
        let user_num = document.getElementById("user_num");
        while(user_list.hasChildNodes()) {
     
     
            user_list.removeChild(user_list.firstChild);
        }
        for (let index in name_list) {
     
     
            let 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;
        let change = type === 'login' ? '上线' : '下线';
        let data = '系统消息: ' + user_name + ' 已' + change;
        listMsg(data);
    }
    function sendMsg(msg) {
     
     
        let data = JSON.stringify(msg);
        ws.send(data);
    }
    function uuid(len, radix) {
     
     
        let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
        let uuid = [], i;
        radix = radix || chars.length;
        if (len) {
     
     
            for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
        } else {
     
     
            let 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>

猜你喜欢

转载自blog.csdn.net/weixin_45765795/article/details/110294095