源码地址
https://gitee.com/bin-0821/chat-room-demo-go-websocket
关于websocket,上一篇文章讲述了如何通过websocket进行服务端与客户端的通信,本篇将会带领大家把各个websocket进行相互通信,在开始之前,请确保有理解
1 go的通道
2 go的线程
3 gin基础
事实上,websocket与websocket之间是无法进行直接相互通信的,需要我们将数据接收后,发送给另一个websocket链接,可以理解为- conn1.ReadJson(&data)
- conn2.WriteJson(data)
复制代码 而建立一个类似微信聊天一样的,能进行多群聊,一对多,一对一的聊天,需要对websocket进行管理,本篇文章的重点便是如何管理好所有用户的websocket连接,主要有以下方面
1,一个根据业务进行设计的数据结构
2,用户上下线后conn的处理
3,用户发送信息的群发或单发
首先,要搞清楚我们在做什么,聊天室要实现的功能类似微信
a,b,c三人,1,2,3 三个聊天室a在1发送信息,全部人都能收到
a在2发送信息,c收不到,以此类推
a可以与c单独发送信息,接收方不在线时,系统能正常运行
1. 目录结构
相比上篇文章 多了manage_socket_conn,service两个模块,重点在于多了manage_socket_conn模块中- D:.
- │ go.mod
- │ go.sum
- │ main.go
- │ msg.json
- ├─api
- │ socket_conn.go
- ├─manage_socket_conn //用户的websocket管理模块
- │ char_room_thread.go //线程 主要负责对信息的群发
- │ room_set.go //聊天室房间管理,房间的创建,销毁 存储房间内的用户id
- │ user_set.go //用户websocket链接管理,信息的发送,存储所有在线的webscoket链接,用户上下线
- ├─middleware
- │ Cros.go
- ├─model
- │ socket_msg_form_front.go
- │ to_front.go
- ├─route
- │ route.go
- └─service
- chat_room.go //数据层,模拟用户加入了那些聊天室
复制代码 2. 代码内容
user_set.go- package manage_socket_conn
- import (
- "WebSocketDemo/model"
- "errors"
- "fmt"
- "github.com/gorilla/websocket"
- "sync"
- )
- func init() {
- GetUserSet()
- }
- //用户map 用来存储每个在线的用户id与对应的conn
- type userSet struct {
- // 用户链接集 用户id => 链接对象
- users map[int]*websocket.Conn
- lock sync.Mutex
- once sync.Once
- }
- var us = new(userSet)
- // 单例模式
- func GetUserSet() *userSet {
- us.once.Do(func() {
- us.users = make(map[int]*websocket.Conn)
- us.users[-1] = nil
- us.lock = sync.Mutex{}
- })
- return us
- }
- // 用户创建发起websocket连接
- // join_type 加入模式
- // 1 正常加入 占线无法加入
- // 2 强制加入 即踢下线前者
- func (u *userSet) ConnConnect(user_id, join_type int, conn *websocket.Conn) (int, error) {
- u.lock.Lock()
- defer u.lock.Unlock()
- if join_type == 1 {
- // 用户id是否已经在线
- if _, ok := u.users[user_id]; ok {
- return 1, errors.New("该账号已被登陆")
- }
- } else if join_type == 2 {
- // 如果原用户id 已经存在map内 进行销毁挤出
- if conn2, ok := u.users[user_id]; ok {
- err := conn2.Close()
- if err != nil {
- fmt.Println(err)
- }
- delete(u.users, user_id)
- }
- // 重新加入
- u.users[user_id] = conn
- }
- return -1, nil
- }
- // 链接断开
- func (u *userSet) ConnDisconnect(user_id int, conn *websocket.Conn) error {
- u.lock.Lock()
- defer u.lock.Unlock()
- if conn2, ok := u.users[user_id]; ok {
- if conn == conn2 {
- delete(u.users, user_id)
- }
- } else {
- // Log 不存在的链接申请断开
- }
- return nil
- }
- // 对单个链接发送信息
- func (u *userSet) SendMsgByUid(user_id int, msg interface{}) error {
- var err error
- if conn, ok := u.users[user_id]; ok {
- err = conn.WriteJSON(msg)
- } else {
- err = errors.New("不存在的链接")
- }
- return err
- }
- // 对多个连接发送信息
- func (u *userSet) SendMsgByUidList(user_id_list []int, msg interface{}) (id_list []int, err_list []error) {
- for _, user_id := range user_id_list {
- // 这里判断用户是否自己,是自己就跳过
- c := msg.(model.ChatMsg)
- if c.ChatMsgType == 1 {
- if (c.Data["form_user_id"].(int)) == user_id {
- continue
- }
- }
- if conn, ok := u.users[user_id]; ok {
- err := conn.WriteJSON(msg)
- if err != nil {
- id_list = append(id_list, user_id)
- err_list = append(err_list, err)
- }
- } else {
- id_list = append(id_list, user_id)
- err_list = append(err_list, errors.New("不存在的链接"))
- }
- }
- return
- }
复制代码 room_set.go
[code]package manage_socket_connimport ( "sync")//群map 用来存储每个群在线的用户idtype roomSet struct { // 群id 群内的用户id rooms map[int]map[int]struct{} lock sync.Mutex once sync.Once}var rs = new(roomSet)// 单例func GetRoomSet() *roomSet{ rs.once.Do(func() { rs.rooms = make(map[int]map[int]struct{}) rs.lock = sync.Mutex{} }) return rs}// 向用户发送func (r *roomSet)SendMsgToUserList (r_id int ,msg interface{}){ userS := GetUserSet() r.lock.Lock() defer r.lock.Unlock() var user_id_list []int for key, _ := range r.rooms[r_id] { user_id_list = append(user_id_list, key) } userS.SendMsgByUidList(user_id_list,msg)}// 用户下线/退群 退出聊天室链接集合func (r *roomSet) UserQuitRooms(room_id_list []int ,user_id int) { r.lock.Lock() defer r.lock.Unlock() for _, room_id := range room_id_list { if v ,ok := r.rooms[room_id];ok { delete(v,user_id) // 房间没人就销毁 if len(r.rooms[room_id]) |