• 【Java转Go】Go中使用WebSocket实现聊天室(私聊+群聊)


    前言

    之前在Java中,用 springboot+websocket 实现了一个聊天室:springboot+websocket聊天室(私聊+群聊)

    有这几个功能:上线、下线、消息发送到公共频道(群聊)、消息发送给指定用户(私聊)。

    这两天Go学了网络编程,也学了 websocket ,所以打算也用 Go + websocket 实现一个聊天室。然后这次用Go实现,还比Java多了个 离线留言上传头像 的功能。

    这次用Go实现的聊天室,客户端同样用的html+js实现,就是在以前写的那个页面的基础上,改了亿点,然后还把公共频道和私聊频道分开来了。功能更加完善,可玩性更高了。

    功能

    • 上线
    • 下线
    • 上传头像
    • 消息发送到公共频道(群聊)
    • 消息发送给指定用户(私聊)
    • 离线留言

    效果(一人分饰多角.jpg😎)

    废话不多说,我们直接先来看实现效果。如果没有上传头像,则默认用名称的前两个字作为头像。并且消息框分为左右两边,左边的是别人的消息框,右边的是自己的消息框。

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    用户上线、群聊

    左边为群聊,所有在线用户都可以看。

    私聊和留言

    右边为私聊,指定接收人,显示在私聊频道。

    并且当接收人不在线时,则私聊信息为留言,当接收人上线了,处理私信,将私信内容显示出来。

    私聊对象不在线:

    在这里插入图片描述

    当私聊对象一上线,会将留言显示出来,显示出来就删掉了,下次重新登录上线就没有了,除非是有新消息。

    在这里插入图片描述

    私聊对象在线:

    在这里插入图片描述
    在这里插入图片描述

    其他人看不到私聊信息:

    在这里插入图片描述

    下线

    关闭连接
    在这里插入图片描述
    在这里插入图片描述

    实现

    思路

    我们通过用户名来区分用户,一个用户一个连接,每个用户可以发送消息,然后一个消息对象需要多个字段来存发送人、接收人等这些信息。

    • 所以我们要先定义两个结构体,一个是用户结构体,一个是消息结构体
    • 有了这两个结构体后,我们还需要两个列表,一个存储在线用户的列表,一个存储消息的列表。
    • 用户列表中,用户下线时,将用户从这个列表中删除。
    • 消息列表中,是为了实现离线留言的功能:指定给某个人发送消息,而这个人不在线时,将消息存储起来;等这个人上线了,再把消息发过去。如果是公共频道或者接收人在线的消息,则不进行存储。
    • 用户上线,需要判断上线的用户名是否已存在,存在,则不可以重复上线;不存在,则上线成功,将该用户添加到用户列表,并往公共频道发送一条消息。
    • 同时需要在消息列表中,找到有没有接收人为这个用户的消息,有就发送给该用户的私聊频道。
    • 用户上线之后,处理消息,是群聊还是私聊。
    • 用户下线,关闭连接。

    代码

    1、定义用户和消息结构体,同时给消息结构体绑定两个方法:一个是解析客户端发来的消息;一个是将消息编码,发给客户端。

    然后再定义两个列表

    • 用户列表因为是用户名唯一,所以用的map,用户名作为key。
    • 消息列表,本来一开始用的切片,但是删除元素不好删,百度了一下,决定使用 list 。
    // 定义一个用户结构体
    type User struct {
    	Name  string          // 用户名
    	Pic   string          // 头像图片地址
    	IsImg bool            // 头像是否是图片
    	Conn  *websocket.Conn // 用户连接
    }
    
    // 解析base64图片
    func (user *User) EncodingBase64() error {
    	if user.IsImg {
    		splits := strings.Split(user.Pic, ",")
    		// 截取文件后缀
    		imgType := splits[0][strings.LastIndex(splits[0], "/")+1 : strings.LastIndex(splits[0], ";")]
    		imgType = strings.Replace(imgType, "e", "", -1) // jpeg 去掉 e,改成jpg格式
    		// 解码base64图片数据
    		imageBytes, err := base64.StdEncoding.DecodeString(strings.Replace(user.Pic, splits[0]+",", "", 1))
    		if err != nil {
    			fmt.Println(err)
    			return err
    		}
    		dirPath := "img"
    		// 创建目录
    		err = os.MkdirAll(dirPath, os.ModePerm)
    		if err != nil {
    			fmt.Println(err)
    			return err
    		}
    		// 拼接图片路径
    		//savePath := "聊天室/main/img/" + user.Name + "." + imgType
    		imgPath := dirPath + "/" + user.Name + "." + imgType // 相对路径
    		// 保存图片到服务器
    		err = os.WriteFile(imgPath, imageBytes, 0644)
    		if err != nil {
    			fmt.Println(err)
    			return err
    		}
    		user.Pic = imgPath
    	}
    	return nil
    }
    
    // 定义一个消息结构体
    type Msg struct {
    	SendUser string // 发送人
    	ReceUser string // 接收人
    	SendTime string // 发送时间
    	Msg      string // 消息内容
    	IsPublic bool   // 消息类型是否是公开的 true 公开 false 私信
    	IsRece   bool   // 接收人是否接收成功 true 接收成功 false 离线还未接收(当接收人离线时,设置为false,当对方上线时,将消息发过去,改为true)
    	IsSend   bool   // 是否是发送消息,用于区分发送消息和上线下线消息(true 发送消息 false 上线/下线消息)
    	IsImg    bool   // 头像是否是图片
    	Pic      string // 头像图片地址
    }
    
    // 解析消息的方法(将客户端返回的消息解析)
    func (msg *Msg) ParseMessage(message []byte) error {
    	fmt.Println(string(message))
    	err := json.Unmarshal(message, msg)
    	if err != nil {
    		fmt.Println(err)
    	}
    	return nil
    }
    
    // 编码消息(将服务端消息发送给客户端)
    func (msg *Msg) EncodeMessage() []byte {
    	b, _ := json.Marshal(msg) // 直接将对象返回过去
    	return b
    }
    
    var users = make(map[string]User) // 用户列表,用户名作为key
    var msgs = list.New()             // 消息列表(用于存储私信消息)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    2、定义WebSocket连接

    // 定义WebSocket连接的升级器。升级器是一个http.HandlerFunc,它将HTTP连接升级为WebSocket连接
    var upgrader = websocket.Upgrader{
    	ReadBufferSize:  1024,
    	WriteBufferSize: 1024,
    	CheckOrigin: func(r *http.Request) bool {
    		return true
    	},
    }
    
    func main() {
    	http.HandleFunc("/web-socket", func(w http.ResponseWriter, r *http.Request) {
    		// 在这里处理连接
    	})
    	log.Fatal(http.ListenAndServe(":7070", nil))
    }
    
    // 在这里面处理连接
    func handleWebSocket(w http.ResponseWriter, r *http.Request) {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    3、开始连接,拿到客户端传过来的用户名,然后校验该用户名是否已在线。

    conn, err := upgrader.Upgrade(w, r, nil)
    fmt.Println(conn.RemoteAddr().String())
    if err != nil {
    	log.Println("err====>>>", err)
    	return
    }
    defer conn.Close()
    user := User{}
    data := r.FormValue("data") // 获取连接的数据
    err := json.Unmarshal([]byte(data), &user)
    if err != nil {
    	conn.WriteMessage(websocket.TextMessage, []byte("连接发生错误"))
    	return
    }
    _, ok := users[user.Name]
    if ok { // 当用户已经在线时,不允许重复连接
    	conn.WriteMessage(websocket.TextMessage, []byte("该用户已连接,不允许重复连接"))
    	return
    }
    err = user.EncodingBase64() // 解码用户头像
    if err != nil {
    	conn.WriteMessage(websocket.TextMessage, []byte("连接发生错误"))
    	return
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    4、连接没有出错,并且校验通过了,把用户添加到用户列表,然后发送一条 该用户上线 的消息给公共频道。同时处理属于该用户的私信信息。

    // 用户上线
    user := User{ // 添加用户到用户列表
    	Name: sendUser,
    	Conn: conn,
    }
    users[user.Name] = user
    str := fmt.Sprintf("%s 加入聊天室,当前聊天室人数为 %d。", user.Name, len(users))
    fmt.Println(str)
    
    // 发送上线消息给其他用户
    msg := Msg{
    	SendUser: user.Name,
    	SendTime: time.Now().Format("2006-01-02 15:04:05"), // 日期格式化为 yyyy-MM-dd HH:mm:ss 格式
    	Msg:      str,
    	IsPublic: true,
    	IsRece:   true,
    	IsSend:   false,
    	IsImg:    user.IsImg,
    	Pic:      user.Pic,
    }
    publicMessage(msg) // 公共消息
    
    // 用户上线时,遍历消息列表,看是否有当前上线用户的未处理的私信
    var next *list.Element
    for el := msgs.Front(); el != nil; el = next {
    	next = el.Next()
    	v := el.Value.(Msg) // 用户上线处理这个用户的私信消息
    	if v.ReceUser == user.Name && !v.IsRece {
    		err := user.Conn.WriteMessage(websocket.TextMessage, v.EncodeMessage())
    		if err != nil {
    			log.Println(err)
    		}
    		msgs.Remove(el) // 处理完成后,将这条私信从消息列表中移除
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    5、循环监听连接,读取客户端发过来的消息,进行处理。

    // 处理消息
    for {
    	_, message, err := conn.ReadMessage()
    	if err != nil {
    		conn.WriteMessage(websocket.TextMessage, []byte("连接已关闭"))
    		log.Println(conn.RemoteAddr().String(), "关闭连接", err)
    		break
    	}
    	// 解析消息
    	msg := Msg{}
    	err = msg.ParseMessage(message)
    	if err != nil {
    		log.Println(err)
    		break
    	}
    	if msg.IsPublic {
    		// 群聊消息
    		publicMessage(msg)
    	} else {
    		// 私聊消息
    		privateMessage(msg)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    6、当监听到客户端关闭了连接时,用户列表里删除下线的用户,并发送一条 该用户下线 的消息给公共频道。

    // 用户下线
    name := user.Name
    removeUser(user) // 删除用户
    str := fmt.Sprintf("%s 离开了聊天室,当前聊天室人数为 %d。", name, len(users))
    fmt.Println(str)
    // 发送下线消息给其他用户
    msg1 := Msg{
    	SendUser: name,
    	SendTime: time.Now().Format("2006-01-02 15:04:05"),
    	Msg:      str,
    	IsPublic: true,
    	IsRece:   true,
    	IsSend:   false,
    	IsImg:    user.IsImg,
    	Pic:      user.Pic,
    }
    publicMessage(msg1)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    服务端 chat.go 完整代码

    package main
    
    import (
    	"container/list"
    	"encoding/base64"
    	"encoding/json"
    	"fmt"
    	"github.com/gorilla/websocket"
    	"log"
    	"net/http"
    	"os"
    	"strings"
    	"time"
    )
    
    /*
    聊天室:
    	上线:输入用户名登录
    	下线:离线
    	群聊:在公共频道发送消息,全部人可见
    	私聊:指定给某个人发送消息,仅那一个人可见
    	留言:用户下线后,其他人给这个人发送消息,是留言
    */
    
    // 定义一个用户结构体
    type User struct {
    	Name  string          // 用户名
    	Pic   string          // 头像图片地址
    	IsImg bool            // 头像是否是图片
    	Conn  *websocket.Conn // 用户连接
    }
    
    // 解析base64图片
    func (user *User) EncodingBase64() error {
    	if user.IsImg {
    		splits := strings.Split(user.Pic, ",")
    		// 截取文件后缀
    		imgType := splits[0][strings.LastIndex(splits[0], "/")+1 : strings.LastIndex(splits[0], ";")]
    		imgType = strings.Replace(imgType, "e", "", -1) // jpeg 去掉 e,改成jpg格式
    		// 解码base64图片数据
    		imageBytes, err := base64.StdEncoding.DecodeString(strings.Replace(user.Pic, splits[0]+",", "", 1))
    		if err != nil {
    			fmt.Println(err)
    			return err
    		}
    		dirPath := "img"
    		// 创建目录
    		err = os.MkdirAll(dirPath, os.ModePerm)
    		if err != nil {
    			fmt.Println(err)
    			return err
    		}
    		// 拼接图片路径
    		//savePath := "聊天室/main/img/" + user.Name + "." + imgType
    		imgPath := dirPath + "/" + user.Name + "." + imgType // 相对路径
    		// 保存图片到服务器
    		err = os.WriteFile(imgPath, imageBytes, 0644)
    		if err != nil {
    			fmt.Println(err)
    			return err
    		}
    		user.Pic = imgPath
    	}
    	return nil
    }
    
    // 定义一个消息结构体
    type Msg struct {
    	SendUser string // 发送人
    	ReceUser string // 接收人
    	SendTime string // 发送时间
    	Msg      string // 消息内容
    	IsPublic bool   // 消息类型是否是公开的 true 公开 false 私信
    	IsRece   bool   // 接收人是否接收成功 true 接收成功 false 离线还未接收(当接收人离线时,设置为false,当对方上线时,将消息发过去,改为true)
    	IsSend   bool   // 是否是发送消息,用于区分发送消息和上线下线消息(true 发送消息 false 上线/下线消息)
    	IsImg    bool   // 头像是否是图片
    	Pic      string // 头像图片地址
    }
    
    // 解析消息的方法(将客户端返回的消息解析)
    func (msg *Msg) ParseMessage(message []byte) error {
    	fmt.Println(string(message))
    	err := json.Unmarshal(message, msg)
    	if err != nil {
    		fmt.Println(err)
    	}
    	return nil
    }
    
    // 编码消息(将服务端消息发送给客户端)
    func (msg *Msg) EncodeMessage() []byte {
    	b, _ := json.Marshal(msg) // 直接将对象返回过去
    	return b
    }
    
    var users = make(map[string]User) // 用户列表,用户名作为key
    var msgs = list.New()             // 消息列表(用于存储私信消息)
    
    // 定义WebSocket连接的升级器。升级器是一个http.HandlerFunc,它将HTTP连接升级为WebSocket连接
    var upgrader = websocket.Upgrader{
    	ReadBufferSize:  1024,
    	WriteBufferSize: 1024,
    	CheckOrigin: func(r *http.Request) bool {
    		return true
    	},
    }
    
    func main() {
    	http.HandleFunc("/web-socket", func(w http.ResponseWriter, r *http.Request) {
    		conn, err := upgrader.Upgrade(w, r, nil)
    		if err != nil {
    			log.Println("err====>>>", err)
    			return
    		}
    		go handleConnection(conn, r)
    	})
    	log.Fatal(http.ListenAndServe(":7070", nil))
    }
    
    func handleConnection(conn *websocket.Conn, r *http.Request) {
    	defer conn.Close()
    	user := User{}
    	data := r.FormValue("data") // 获取连接的数据
    	err := json.Unmarshal([]byte(data), &user)
    	if err != nil {
    		conn.WriteMessage(websocket.TextMessage, []byte("连接发生错误"))
    		return
    	}
    	_, ok := users[user.Name]
    	if ok { // 当用户已经在线时,不允许重复连接
    		conn.WriteMessage(websocket.TextMessage, []byte("该用户已连接,不允许重复连接"))
    		return
    	}
    	err = user.EncodingBase64() // 解码用户头像
    	if err != nil {
    		conn.WriteMessage(websocket.TextMessage, []byte("连接发生错误"))
    		return
    	}
    	// 用户上线
    	user.Conn = conn
    	goLive(user)
    	// 处理消息
    	for {
    		_, message, err := conn.ReadMessage()
    		if err != nil {
    			conn.WriteMessage(websocket.TextMessage, []byte("连接已关闭"))
    			log.Println(conn.RemoteAddr().String(), "关闭连接", err)
    			break
    		}
    		// 解析消息
    		msg := Msg{}
    		err = msg.ParseMessage(message)
    		if err != nil {
    			log.Println(err)
    			break
    		}
    		if msg.IsPublic {
    			// 群聊消息
    			publicMessage(msg)
    		} else {
    			// 私聊消息
    			privateMessage(msg)
    		}
    	}
    	offLine(user)
    }
    
    // 用户上线
    func goLive(user User) {
    	users[user.Name] = user
    	str := fmt.Sprintf("%s 加入聊天室,当前聊天室人数为 %d。", user.Name, len(users))
    	fmt.Println(str)
    	// 发送上线消息给其他用户
    	msg := Msg{
    		SendUser: user.Name,
    		SendTime: time.Now().Format("2006-01-02 15:04:05"), // 日期格式化为 yyyy-MM-dd HH:mm:ss 格式
    		Msg:      str,
    		IsPublic: true,
    		IsRece:   true,
    		IsSend:   false,
    		IsImg:    user.IsImg,
    		Pic:      user.Pic,
    	}
    	publicMessage(msg)
    	privateMessageHandle(user)
    }
    
    // 用户上线,处理自己的私信消息
    func privateMessageHandle(user User) {
    	// 用户上线时,遍历消息列表,看是否有当前上线用户的未处理的私信
    	var next *list.Element
    	for el := msgs.Front(); el != nil; el = next {
    		next = el.Next()
    		v := el.Value.(Msg) // 用户上线处理这个用户的私信消息
    		if v.ReceUser == user.Name && !v.IsRece {
    			err := user.Conn.WriteMessage(websocket.TextMessage, v.EncodeMessage())
    			if err != nil {
    				log.Println(err)
    			}
    			msgs.Remove(el) // 处理完成后,将这条私信从消息列表中移除
    		}
    	}
    }
    
    // 公共消息
    func publicMessage(msg Msg) {
    	for _, user := range users {
    		// 当 msg.IsSend 为true时,说明是发送消息,则必须判断 user.Name != msg.SendUser
    		if user.Conn != nil && ((msg.IsSend && user.Name != msg.SendUser) || !msg.IsSend) {
    			err := user.Conn.WriteMessage(websocket.TextMessage, msg.EncodeMessage())
    			if err != nil {
    				log.Println(err)
    			}
    		}
    	}
    }
    
    // 发送私聊消息给指定用户
    func privateMessage(msg Msg) {
    	for _, user := range users {
    		if user.Name == msg.ReceUser && user.Conn != nil { // 当接收人在线时
    			// 发送私聊消息
    			err := user.Conn.WriteMessage(websocket.TextMessage, msg.EncodeMessage())
    			if err != nil {
    				log.Println(err)
    			}
    			msg.IsRece = true // 将 IsRece 设置为true
    			break
    		}
    	}
    	if !msg.IsRece { // 只有接收人离线时,才将消息存到消息列表中
    		msgs.PushBack(msg)
    	}
    }
    
    // 用户下线
    func offLine(user User) {
    	name := user.Name
    	removeUser(user) // 删除用户
    	str := fmt.Sprintf("%s 离开了聊天室,当前聊天室人数为 %d。", name, len(users))
    	fmt.Println(str)
    	// 发送下线消息给其他用户
    	msg1 := Msg{
    		SendUser: name,
    		SendTime: time.Now().Format("2006-01-02 15:04:05"),
    		Msg:      str,
    		IsPublic: true,
    		IsRece:   true,
    		IsSend:   false,
    		IsImg:    user.IsImg,
    		Pic:      user.Pic,
    	}
    	publicMessage(msg1)
    }
    
    // 用户下线删除用户
    func removeUser(user User) {
    	for _, v := range users {
    		if v.Name == user.Name {
    			os.Remove(user.Pic) // 删除头像文件
    			delete(users, v.Name)
    			break
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265

    客户端 html 完整代码

    DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8">
            <meta http-equiv="X-UA-Compatible" content="IE=edge">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>聊天室title>
    		<script src="https://code.jquery.com/jquery-3.3.1.min.js">script>
    	    <style>
    		    /* 设置滚动条的样式 */
    		    ::-webkit-scrollbar {
    			    width:5px;
    		    }
    		    /* 滚动槽 */
    		    ::-webkit-scrollbar-track {
    			    -webkit-box-shadow:inset 0 0 6px rgba(0,0,0,0.3);
    			    border-radius:10px;
    		    }
    		    /* 滚动条滑块 */
    		    ::-webkit-scrollbar-thumb {
    			    border-radius:10px;
    			    background:rgba(0,0,0,0.1);
    			    -webkit-box-shadow:inset 0 0 6px rgba(72, 192, 164,0.5);
    		    }
    		    ::-webkit-scrollbar-thumb:window-inactive {
    			    background:rgba(72, 192, 164,0.4);
    		    }
    		    ::placeholder {
    			    color: #79879a; /* 文字颜色 */
    		    }
    
    		    /*预定义样式,通过js动态生成dom时,加上指定类名*/
    		    .dpn-message {font-family: "\5FAE\8F6F\96C5\9ED1", Helvetica, sans-serif;font-size: 18px;z-index: 99999;}
    		    .dpn-message {box-sizing: border-box;position: fixed;top: -200px;left: 50%;transform: translateX(-50%);z-index: 99999;padding: 20px;padding-right: 32px;min-width: 25%;max-width: 50%;border-radius: 4px;transition: top .3s;}
    		    /*info 消息*/
    		    .dpn-message.dpn-info {background: #EDF2FC;border: 1px solid #EBEEF5;color: #909399;}
    		    /*success消息*/
    		    .dpn-message.dpn-success {background: #f0f9eb;border: 1px solid #54f006;color: #67C23A;}
    		    /*error消息*/
    		    .dpn-message.dpn-error {background: #fef0f0;border: 1px solid #fde2e2;color: #F56C6C;}
    		    /*warning消息*/
    		    .dpn-message.dpn-warning {background: #fdf6ec;border: 1px solid #faecd8;color: #E6A23C;}
    		    .dpn-message .dpn-close {position: absolute;right: 8px;top: 50%;transform: translateY(-50%);width: 16px;height: 16px;line-height: 16px;text-align: center;font-style: normal;cursor: pointer;}
    	    style>
            <style>
    	        body{font-family: Arial, sans-serif;margin: 0;padding: 0;background-color: #282C34;background-position: center;background-repeat: no-repeat;background-attachment: fixed;background-size:100%;}
    	        .main {display: flex;justify-content: space-between;margin: 20px;}
    	        .public-channel,
    	        .private-channel {flex: 1;border: 1px solid #68768a;border-radius: 5px;padding: 10px;box-sizing: border-box;margin-right: 10px;}
    	        .public-channel{margin-right: 80px;}
    	        h3 {font-size: 20px;color: #48c0a4;}
    	        .top {display: flex;align-items: center;margin-bottom: 10px;}
    	        #sendUser,
    	        #receUser {
    		        width: 300px;
    		        height: 38px;
    		        margin-right: 10px;
    		        border: 1px solid rgba(0,0,0,0.2);
    		        background: rgba(53,75,105,0.5);
    		        padding: 5px 10px;
    		        border-radius: 5px;
    		        font-size: 16px;
    		        color: #cfe2f3;
    		        outline: none;
    	        }
    	        #头像box {
    		        display: flex;
    		        align-items: center;
    		        width: 50px;
    		        height: 50px;
    		        cursor: pointer;
    		        font-size: 14px;
    	        }
    	        #fileImg {display: none;vertical-align: middle;}
    	        #img {max-width: 50px;}
    	        #fileBox {
    		        position: relative;
    		        display: inline-block;
    		        width: 50px;
    		        height: 50px;
    		        line-height: 50px;
    		        border: 1px solid rgba(0,0,0,0.2);
    		        background: rgba(53,75,105,0.5);
    		        border-radius: 5px;
    		        color: gold;
    		        text-align: center;
    		        cursor: pointer;
    	        }
    	        #file {
    		        position: absolute;
    		        opacity: 0;
    		        width: 50px;
    		        height: 50px;
    		        cursor: pointer;
    		        padding: 0;
    		        margin-left: -12px;
    	        }
    	        button {
    		        width: 16%;
    		        height: 50px;
    		        padding: 10px 20px;
    		        color: gold;
    		        background: rgba(53,75,105,0.5);
    		        border-radius: 5px;
    		        border: none;
    		        cursor: pointer;
    		        margin-left: 20px;
    		        outline: none;
    	        }
    	        button:hover{color: darkorange;}
    	        button:active {border: none;color: white;outline: none;background: LightSkyBlue;}
    
    	        .msgBox {
    		        border: 1px solid rgba(0,0,0,0.2);
    		        padding: 10px;
    		        height: 600px;
    		        margin-bottom: 15px;
    		        border-radius: 15px;
    		        overflow: auto;
    	        }
    	        #publicMsg,
    	        #privateMsg{display: flex;flex-direction: column;}
    			.msg{display: flex;margin: 15px 5px 10px 5px;}
    	        .right {margin-left: auto;}
    	        .msg .pic-box{float: left;width:50px;height:50px;line-height:50px;text-align: center;}
    	        .left .info-box{margin-left: 10px;}
    	        .right .info-box{text-align: right;margin-right: 10px;}
    	        .msg .msg-pic{color: deepskyblue;display: block;font-weight: bold;font-size: 20px;width:50px;vertical-align: middle;}
    			.msg .msg-name{color: gold;font-size: 14px;margin-bottom: 1px;}
    			.msg .msg-name1{font-weight: bold;color: #f75c2f;}
    			.msg .msg-name2{font-weight: bold;}
    			.msg .msg-time{color: #cfe2f3;font-size: 12px;}
    			.msg .msg-msginfo{font-size: 15px;color: #cfe2f3;display: block;margin-top: 3px;word-break: break-all;}
    
    	        textarea {
    		        width: 97.5%;
    		        height: 60px;
    		        border: 1px solid rgba(0,0,0,0.2);
    		        background: rgba(53,75,105,0.5);
    		        padding: 10px;
    		        outline: none;
    		        border-radius: 15px;
    		        font-size: 16px;
    		        color: #cfe2f3;
    	        }
            style>
        head>
    
        <body>
            <div class="main">
    	        <div class="public-channel">
    		        <h3>公共频道h3>
    		        <div class="top">
    			        <input type="text" id="sendUser" placeholder="登录用户名">
    			        <div id="头像box">
    				        <div id="fileImg"><img src="" id="img" width="50">div>
    				        <div id="fileBox">
    					        <input type="file" id="file" class="file" multiple="multiple" accept="image/jpeg,image/png,image/jpg">
    					        <span style="cursor: pointer;">头像span>
    				        div>
    			        div>
    			        <button id="上线" onclick="connectWebSocket()">登录button>
    			        <button id="下线" onclick="closeWebSocket()">退出button>
    		        div>
    		        <div class="msgBox">
    			        <div id="publicMsg">div>
    		        div>
    		        <div class="buttom">
    			        <textarea type="text" id="msg1" placeholder="按Enter发送消息(Shift + Enter 换行)">textarea>
    		        div>
    	        div>
    	        <div class="private-channel">
    		        <h3>私聊频道h3>
    		        <div class="top">
    		            <input type="text" id="receUser" placeholder="私聊对象用户名"/>
    		        div>
    		        <div class="msgBox">
    			        <div id="privateMsg">div>
    		        div>
    		        <div class="buttom">
    			        <textarea type="text" id="msg2" placeholder="按Enter发送消息(Shift + Enter 换行)">textarea>
    		        div>
    	        div>
            div>
    
            <script>
    	        class MessageBox {
    		        constructor(options) {
    			        for(let key in options) {
    				        if(!options.hasOwnProperty(key)) break;
    				        this[key] = options[key];
    			        }
    			        this.init();
    		        }
    		        init() {if(this.status === "message") {this.createMessage();this.open();return;}}
    		        createMessage() {
    			        this.messageBox = document.createElement('div');
    			        this.messageBox.className = `dpn-message dpn-${this.type}`;
    			        this.messageBox.innerHTML = `${this.message}X`;
    			        document.body.appendChild(this.messageBox);
    			        this.messageBox.onclick = ev => {
    				        let target = ev.target;
    				        if(target.className === "dpn-close") {this.close();}
    			        };
    			        this.oninit();
    		        }
    		        open() {
    			        if(this.status === "message") {
    				        let messageBoxs = document.querySelectorAll('.dpn-message'),len = messageBoxs.length;
    				        this.messageBox.style.top = `${len===1 ? 20:20+(len-1)*70}px`;
    				        this.autoTimer = setTimeout(() => {this.close();}, this.duration);
    				        this.onopen();
    				        return;
    			        }
    		        }
    		        close() {
    			        if(this.status === "message") {
    				        clearTimeout(this.autoTimer);
    				        this.messageBox.style.top = '-200px';
    				        let anonymous = () => {document.body.removeChild(this.messageBox);this.onclose();};
    				        this.messageBox.addEventListener('transitionend', anonymous);
    				        return;
    			        }
    		        }
    	        }
    	        window.messageplugin = function(options = {}) {
    		        if(typeof options === "string") {options = { message: options };}
    		        options = Object.assign({status: 'message', message: '我是默认信息', type: 'info', duration: 1000, oninit() {}, onopen() {}, onclose() {},}, options);
    		        return new MessageBox(options);
    	        };
            script>
    
            <script>
    	        let file = document.getElementById('file'); // 选择文件
    	        let fileBox = document.getElementById('fileBox'); // 选择文件box
    	        let img = document.getElementById('img'); // 头像img标签
    	        let fileImg = document.getElementById('fileImg'); // 头像img标签box
    	        let textareaMsg1 = document.getElementById("msg1"); // 获取多行文本框元素
    	        let textareaMsg2 = document.getElementById("msg2"); // 获取多行文本框元素
    	        let isImg = false; // 标识当前用户是否上传了头像
    	        let imgBase64 = "",fileSuffix = "";
    	        file.onchange = function (e){
    		        if (e.target.files.length>0){
                        var selectedImage = e.target.files[0];
                        var fileSize = selectedImage.size / 1024; // 转换为KB
                        if (fileSize > 1024) {messageplugin({ message: "文件大小不得超过1MB", type: "error" });return;}
    			        var name = selectedImage.name;
    			        fileSuffix = name.slice(name.lastIndexOf(".")).replace("e",""); // 获取文件后缀
    			        img.src = getFileURL(selectedImage);
    			        fileImg.style.display = "inline-block";
    			        fileBox.style.display = "none";
    			        isImg = true;
    			        var fileReader = new FileReader();
    			        fileReader.readAsDataURL(selectedImage); // 文件读取为url
    			        fileReader.onload = function(e) {
    				        imgBase64 = e.target.result; // 获取头像的base64
    			        }
    		        }
    	        }
    	        //获取文件地址
    	        function getFileURL(file) {
    		        var url = null ;
    		        if (window.createObjectURL!=undefined) { // basic
    			        url = window.createObjectURL(file) ;
    		        } else if (window.URL!=undefined) { // mozilla(firefox)
    			        url = window.URL.createObjectURL(file) ;
    		        } else if (window.webkitURL!=undefined) { // webkit or chrome
    			        url = window.webkitURL.createObjectURL(file) ;
    		        }
    		        return url;
    	        }
    
    	        // 定义两个模板字符串,使用占位符 ${} 来表示待填充的位置
    	        // 公共频道其他用户的消息的模板(位于左边)
    	        const publicLeftTemplate = `
    				
    {{ImgHtmlSnippet}}
    ${"{{SendUser}}"} ${"{{SendTime}}"} ${"{{Msg}}"}
    `
    ; // 公共频道我的消息的模板(位于右边) const publicRightTemplate = `
    ${"{{SendTime}}"} ${"{{SendUser}}"} ${"{{Msg}}"}
    {{ImgHtmlSnippet}}
    `
    ; // 私聊频道其他用户的消息的模板 const privateLeftTemplate = `
    {{ImgHtmlSnippet}}
    ${"{{SendUser}}"} 发给 ${"{{ReceUser}}"} ${"{{SendTime}}"} ${"{{Msg}}"}
    `
    ; // 私聊频道我的消息的模板 const privateRightTemplate = `
    ${"{{SendTime}}"} ${"{{SendUser}}"} 发给 ${"{{ReceUser}}"} ${"{{Msg}}"}
    {{ImgHtmlSnippet}}
    `
    ; // 填充模板 function filledTemplate(obj){ var sendUser = document.getElementById("sendUser").value; // 当前用户 var isPublic = obj["IsPublic"]; // 头像代码段 const imgHtmlSnippet = obj["IsImg"] ? `` : `${obj["Pic"]}`; var filledTemplate; if (isPublic){ msgList = document.getElementById("publicMsg"); var template = publicLeftTemplate; if (obj["SendUser"] == sendUser || obj["SendUser"] == "我"){ template = publicRightTemplate; } // 替换模板中的占位符 filledTemplate = template.replace("{{ImgHtmlSnippet}}", imgHtmlSnippet).replace(/\{\{(\w+)\}\}/g, (match, key) => { if (key == "SendUser" && !obj["IsSend"]){ return ""; }else if (key == "SendUser" && obj["IsSend"]){ return " "+obj[key]; } return obj[key] || ""; }); }else { msgList = document.getElementById("privateMsg"); var template = privateLeftTemplate; if (obj["SendUser"] == "我"){ template = privateRightTemplate; } // 替换模板中的占位符 filledTemplate = template.replace("{{ImgHtmlSnippet}}", imgHtmlSnippet).replace(/\{\{(\w+)\}\}/g, (match, key) => { if (key == "ReceUser" && obj[key] == sendUser){ // 当前用户是接收人时 return "我"; } return obj[key] || ""; }); } // 将生成的 HTML 插入到页面中 msgList.innerHTML += filledTemplate; }
    script> <script type="text/javascript"> var websocket = null; //连接WebSocket function connectWebSocket() { var sendUser = document.getElementById("sendUser").value; if (sendUser === "") {messageplugin({ message: "请输入用户名", type: "error" });return;} //判断当前浏览器是否支持websocket if ('WebSocket' in window) { var val = document.getElementById("sendUser").value; // websocket = new WebSocket("ws://localhost:7070/web-socket/"+val); // websocket = new WebSocket("ws://localhost:7070/web-socket?username="+val); var pic = isImg ? imgBase64 : val.slice(0,2); var jsonData = {Name: val, Pic: pic, IsImg: isImg,}; // 准备要发送的JSON数据 var jsonStr = JSON.stringify(jsonData); // 将JSON数据转换为字符串 websocket = new WebSocket(`ws://localhost:7070/web-socket?data=${encodeURIComponent(jsonStr)}`); } else {messageplugin({ message: "当前浏览器不支持 websocket", type: "error" });} //连接发生错误的回调方法 websocket.onerror = function () {messageplugin({ message: "连接发生错误", type: "error" });}; //连接成功建立的回调方法 websocket.onopen = function () { // 连接成功后,将连接的用户输入框和上线按钮禁用 var sendUser = document.getElementById("sendUser"); var 上线 = document.getElementById("上线"); sendUser.readOnly = true; sendUser.disabled = "disabled"; sendUser.style.backgroundColor='rgba(0,0,0,0.2)'; file.style.display = "none"; fileBox.style.backgroundColor = "rgba(0,0,0,0.2)"; // 上线.removeAttribute("onclick"); 上线.disabled = "disabled"; 上线.style.backgroundColor='rgba(0,0,0,0.2)'; } //接收到消息的回调方法 websocket.onmessage = function (event) { try { var obj = JSON.parse(event.data); // 将服务器发过来的消息,转为json格式,捕获异常 filledTemplate(obj); // 填充模板 } catch (error) { messageplugin({ message: event.data, type: "error" }); // 如果不是json格式则直接用 alert 提示 } } //连接关闭的回调方法 websocket.onclose = function () {messageplugin({ message: "连接已关闭", type: "warning" });} //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。 window.onbeforeunload = function () {closewebsocket();} } //关闭连接 function closeWebSocket() {websocket.close();} // 公共频道发送消息(监听键盘enter事件) textareaMsg1.addEventListener("keydown", function(event) { if (event.key === "Enter" && !event.shiftKey) { // 按下enter且没有按住shift键的情况下 event.preventDefault(); // 阻止默认的换行行为 send(textareaMsg1.value,true); textareaMsg1.value = ""; // 清空消息 } }); // 私聊频道发送消息(监听键盘enter事件) textareaMsg2.addEventListener("keydown", function(event) { if (event.key === "Enter" && !event.shiftKey) { // 按下enter且没有按住shift键的情况下 event.preventDefault(); // 阻止默认的换行行为 var receUser = document.getElementById("receUser").value; //接收者 if (receUser === "") {messageplugin({ message: "私聊对象不能为空", type: "error" });return;} send(textareaMsg2.value,false); textareaMsg2.value = ""; // 清空消息 } }); //发送消息 function send(msg,isPublic) { var m = new Map(); // 空Map var sendUser = document.getElementById("sendUser"); //发送者 if (msg === "") {messageplugin({ message: "不能发送空消息", type: "error" });return;} var receUser = document.getElementById("receUser").value; //接收者 var currentTime = getCurrentTime(); m.set("SendUser",sendUser.value); m.set("SendTime",currentTime); m.set("Msg",msg); m.set("IsSend",true); m.set("IsPublic",isPublic); if (!isPublic){ if (receUser === "") {messageplugin({ message: "私聊对象不能为空", type: "error" });return;} m.set("ReceUser",receUser); } m.set("IsImg",isImg); m.set("Pic",isImg ? "img/"+sendUser.value + fileSuffix : sendUser.value.slice(0,2)); var json = mapToJson(m); // map转json websocket.send(JSON.stringify(json)); // 先将json转json字符串,再发送 json["SendUser"] = "我"; filledTemplate(json); } // 获取当前时间 function getCurrentTime(){ //可以使用字符串操作方法来将日期时间格式化为特定格式的字符串。例如: const date = new Date(); const year = date.getFullYear().toString().padStart(4, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0'); const hour = date.getHours().toString().padStart(2, '0'); const minute = date.getMinutes().toString().padStart(2, '0'); const second = date.getSeconds().toString().padStart(2, '0'); return `${year}-${month}-${day} ${hour}:${minute}:${second}`; // 2023-02-16 08:25:05 } //map转换为json function mapToJson(map) { var obj= Object.create(null); for (var[k,v] of map) { obj[k] = v; } return obj; } script> body> html>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465
    • 466
    • 467
    • 468
    • 469
    • 470
    • 471
    • 472
    • 473
    • 474
    • 475
    • 476
    • 477
    • 478
    • 479
    • 480
    • 481
    • 482
    • 483
    • 484
    • 485
    • 486

    最后

    不知道我这种代码实现有没有问题?我百度查看别人实现的都是有考虑高并发,用管道和协程来处理读写数据(接收客户端消息、给客户端发送消息),还有需要服务端不停地向客户端发送消息保持心跳?但是我都没有这样子实现😢,顶多也就给每个连接开了个协程去处理,也不确定这种实现方式有没有啥问题🤣如果有的话,可以在评论区说一下😘


    ok,以上就是本篇文章的全部内容了,如果你觉得文章对你有帮助或者写得还不错的话,不要吝啬你的大拇指,给博主点个赞吧~😎😘

  • 相关阅读:
    windows server 2012搭建ftp站点服务器,新建ftp用户,设置ftp文件夹权限,通过ftp实现文件远程备份、异地备份
    计算机组成原理课程设计(1)
    Docker搭建Sentinel 控制台环境搭建及使用介绍
    Java老人护理上门服务类型系统小程序APP源码
    第2-4-8章 规则引擎Drools实战(1)-个人所得税计算器
    热量衡算习题课
    【多线程进阶】synchronized 原理
    Etcd 解析
    Django系列8-员工管理系统实战--部门管理
    魏牌拿铁PHEV获E-NCAP五星安全认证,搭载毫末智行方案
  • 原文地址:https://blog.csdn.net/weixin_43165220/article/details/132346043