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

目录

  • 前言
  • 功能
  • 效果(一人分饰多角.jpg😎)
    • 用户上线、群聊
    • 私聊和留言
    • 下线
  • 实现
    • 思路
    • 代码
      • 服务端 chat.go 完整代码
      • 客户端 html 完整代码
  • 最后

前言

之前在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 + "." + imgTypeimgPath := 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()             // 消息列表(用于存储私信消息)

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) {}

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
}

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) // 处理完成后,将这条私信从消息列表中移除}
}

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)}
}

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)

服务端 chat.go 完整代码

package mainimport ("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 + "." + imgTypeimgPath := 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 = conngoLive(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] = userstr := 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.Elementfor 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.SendUserif 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 设置为truebreak}}if !msg.IsRece { // 只有接收人离线时,才将消息存到消息列表中msgs.PushBack(msg)}
}// 用户下线
func offLine(user User) {name := user.NameremoveUser(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}}
}

客户端 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);}input{width: 15%;height: 30px;line-height: 25px;padding: 5px 10px;border-radius: 5px;border: 1px solid #48c0a4;font-size: 16px;outline: none;}#头像box{display: inline-block;width: 50px;height: 50px;line-height: 50px;vertical-align: middle;text-align: center;cursor: pointer;font-size: 14px;}#fileImg{display: none;vertical-align: middle;}#fileBox{display: inline-block;width: 50px;height: 42px;line-height: 44px;border: 1px solid #48c0a4;border-radius: 5px;}.file{position: absolute;opacity: 0;width: 50px;height: 42px;cursor: pointer;padding: 0;margin-left: -12px;}#msg{width: 30%;}button{width: 6%;height: 44px;/*padding: 5px 10px;*/border-radius: 5px;border: 1px solid #48c0a4;outline: none;cursor: pointer;}.msgBox{width: 40%;float: left;margin-left: 20px;margin-right: 20px;}#publicMsg{width: 100%;height: 620px;border: 1px solid #48c0a4;overflow: auto;border-radius: 15px;}#privateMsg{width: 100%;height: 620px;border: 1px solid #48c0a4;overflow: auto;border-radius: 15px;}.msg{margin: 15px 5px 10px 5px;}.msg .left{float: left;width:50px;height:50px;line-height:50px;text-align: center;}.msg .right{margin-left: 58px;}.msg .msg-name{color: #6d9eeb;font-size: 12px;margin-bottom: 1px;}.msg .msg-name1{font-weight: bold;color: #f75c2f;}.msg .msg-name2{font-weight: bold;}.msg .msg-time{color: grey;font-size: 12px;}.msg .msg-msginfo{font-size: 14px;color: #1e88a8;display: block;margin-top: 3px;word-break: break-all;}.msg .msg-pic{color: dodgerblue;display: block;font-weight: bold;width:50px;vertical-align: middle;}</style></head><body><h2 style="margin-left: 10px;">聊天室</h2><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><br/><br><input type="text" id="receUser" placeholder="接收人的用户名"/><input type="text" id="msg" placeholder="要发送的信息"/><button onclick="send()">发送</button><br><br><hr><div><div class="msgBox"><h3>公共频道</h3><div id="publicMsg"></div></div><div class="msgBox"><h3>私聊频道</h3><div id="privateMsg"></div></div></div><script>let file = document.getElementById('file'); // 选择文件let fileBox = document.getElementById('fileBox'); // 选择文件boxlet img = document.getElementById('img'); // 头像img标签let fileImg = document.getElementById('fileImg'); // 头像img标签boxlet 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; // 转换为KBif (fileSize > 1024) { // 限制为1MBalert("文件大小超过限制!");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); // 文件读取为urlfileReader.onload = function(e) {imgBase64 = e.target.result; // 获取头像的base64}}}//获取文件地址function getFileURL(file) {var url = null ;if (window.createObjectURL!=undefined) { // basicurl = window.createObjectURL(file) ;} else if (window.URL!=undefined) { // mozilla(firefox)url = window.URL.createObjectURL(file) ;} else if (window.webkitURL!=undefined) { // webkit or chromeurl = window.webkitURL.createObjectURL(file) ;}return url;}// 定义两个模板字符串,使用占位符 ${} 来表示待填充的位置// 公共频道的模板const publicTemplate = `<div class="msg"><div class="left">{{ImgHtmlSnippet}}</div><div class="right"><span class="msg-name">${"{{SendUser}}"}</span><span class="msg-time">${"{{SendTime}}"}</span><span class="msg-msginfo">${"{{Msg}}"}</span></div></div>`;// 私聊频道的模板const privateTemplate = `<div class="msg"><div class="left">{{ImgHtmlSnippet}}</div><div class="right"><span class="msg-name msg-name2">${"{{SendUser}}"}</span><span class="msg-time">发给</span><span class="msg-name msg-name1">${"{{ReceUser}}"}</span><span class="msg-time">${"{{SendTime}}"}</span><span class="msg-msginfo">${"{{Msg}}"}</span></div></div>`;// 填充模板function filledTemplate(obj){var isPublic = obj["IsPublic"];// 头像代码段const imgHtmlSnippet = obj["IsImg"] ? `<img src='${obj["Pic"]}' width="50" class="msg-pic">` : `<span class="msg-pic">${obj["Pic"]}</span>`;var filledTemplate;if (isPublic){msgList = document.getElementById("publicMsg");// 替换模板中的占位符filledTemplate = publicTemplate.replace("{{ImgHtmlSnippet}}", imgHtmlSnippet).replace(/\{\{(\w+)\}\}/g, (match, key) => {if (key == "SendUser" && !obj["IsSend"]){return "";}else if (key == "SendUser" && obj["IsSend"]){return "&nbsp;"+obj[key];}return obj[key] || "";});}else {msgList = document.getElementById("privateMsg");var sendUser = document.getElementById("sendUser").value; // 当前用户// 替换模板中的占位符filledTemplate = privateTemplate.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;//连接WebSocketfunction connectWebSocket() {var sendUser = document.getElementById("sendUser").value;if (sendUser === "") {alert("请输入用户名");return;}//判断当前浏览器是否支持websocketif ('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 = { // 准备要发送的JSON数据Name: val,Pic: pic,IsImg: isImg,};var jsonStr = JSON.stringify(jsonData); // 将JSON数据转换为字符串websocket = new WebSocket(`ws://localhost:7070/web-socket?data=${encodeURIComponent(jsonStr)}`);} else {alert('当前浏览器不支持 websocket')}//连接发生错误的回调方法websocket.onerror = function () {alert("连接发生错误");};//连接成功建立的回调方法websocket.onopen = function () {// 连接成功后,将连接的用户输入框和上线按钮禁用var sendUser = document.getElementById("sendUser");var 上线 = document.getElementById("上线");sendUser.readOnly = true;sendUser.style.backgroundColor='#9c9c9c';file.style.display = "none";fileBox.style.backgroundColor = "#9c9c9c";上线.removeAttribute("onclick");上线.style.backgroundColor='#9c9c9c';}//接收到消息的回调方法websocket.onmessage = function (event) {try {// 将服务器发过来的消息,转为json格式,捕获异常var obj = JSON.parse(event.data);filledTemplate(obj); // 填充模板} catch (error) {// 如果不是json格式则直接用 alert 提示alert(event.data);}}//连接关闭的回调方法websocket.onclose = function () {alert("连接已关闭");}//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。window.onbeforeunload = function () {closewebsocket();}}//关闭连接function closeWebSocket() {websocket.close();}//发送消息function send() {var m = new Map(); // 空Mapvar sendUser = document.getElementById("sendUser");  //发送者var msg = document.getElementById("msg").value;  //发送消息if (msg === "") {alert("请输入消息");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);// 接收者为空时,为群聊,否则为私聊if (receUser === "") {m.set("IsPublic",true);}else{m.set("ReceUser",receUser);m.set("IsPublic",false);}m.set("IsImg",isImg);m.set("Pic",isImg ? "img/"+sendUser.value + fileSuffix : sendUser.value.slice(0,2));var json = mapToJson(m); // map转jsonwebsocket.send(JSON.stringify(json)); // 先将json转json字符串,再发送json["SendUser"] = "我";filledTemplate(json);document.getElementById("msg").value = ""; // 清空消息框}// 获取当前时间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转换为jsonfunction  mapToJson(map) {var obj= Object.create(null);for (var[k,v] of map) {obj[k] = v;}return obj;}</script></body>
</html>

最后

html的代码,消息显示样式有一点变化,就是本文的前三张图片,更好看一点了,但是后面的图懒得重新截了。


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


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/98575.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python爬虫关于ip代理池的获取和随机生成

前言 在进行爬虫开发时&#xff0c;代理IP池是一个非常重要的概念。代理IP池是指一个包含多个可用代理IP的集合&#xff0c;这些代理IP可以用来绕过网站的防爬虫策略&#xff0c;从而提高爬取数据的成功率。 在本文中&#xff0c;我们将介绍如何获取代理IP池&#xff0c;并且随…

单臂路由实验:通过Trunk和子接口实现VLAN互通

文章目录 一、实验背景与目的二、实验拓扑三、实验需求四、实验解法1. PC 配置 IP 地址2. PC3 属于 Vlan10&#xff0c;PC4 属于 Vlan20&#xff0c;配置单臂路由实现 Vlan10 和 Vlan20 三层互通3. 测试在 PC3 上 Ping PC4 &#xff0c;可以 Ping 通 PC4 摘要&#xff1a; 本文…

2023最新UI工作室官网个人主页源码/背景音乐/随机壁纸/一言

2023最新UI工作室官网个人主页源码/支持背景音乐/随机壁纸/一言 功能介绍&#xff1a; 载入动画 站点简介 Hitokoto 一言 日期及时间 实时天气 时光进度条 音乐播放器 移动端适配 打开文件&#xff1b;index.html和setting.json修改替换你的相关信息&a…

SECOND:Sparsely Embedded Convolutional Detection

论文背景 为了克服图像单独提供空间信息的不足&#xff0c;点云数据在三维应用中变得越来越重要。点云数据包含精确的深度信息&#xff0c;可以由LiDAR或RGB-D相机生成。 VoxelNet&#xff1a;首先将点云数据分组成体素&#xff0c;然后在将体素转换成密集的3D张量用于区域提…

Vue3回到顶部(BackTop)

效果如下图&#xff1a;在线预览 APIs 参数说明类型默认值必传bottomBackTop 距离页面底部的高度number | string40falserightBackTop 距离页面右侧的宽度number | string40falsevisibilityHeight滚动时触发显示回到顶部的高度number180falsetoBackTop 渲染的容器节点 可选 元…

Springboot启动异常 Command line is too long

Springboot启动异常 Command line is too long Springboot启动时直接报异常 Command line is too long. Shorten command line for xxxxxApplication or also for Spring Boot default解决方案: 修改 SystemApplication 的 Shorten command line&#xff0c;选择 JAR manife…

unity tolua热更新框架教程(2)

Lua启动流程 增加脚本luamain&#xff0c;继承luaclient 建立第一个场景GameMain&#xff0c;在对象GameMain挂载脚本LuaMain&#xff0c;启动场景 看到打印&#xff0c;lua被成功加载 lua入口及调用堆栈 这里会执行main.lua文件的main函数 C#接口导出 在此处配置C#导出的代码 …

【综述+3D】基于NeRF的三维视觉2023年度进展报告(截止2023.06.10)

论文&#xff1a;2003.Representing Scenes as Neural Radiance Fields for View Synthesis 官方网站&#xff1a;https://www.matthewtancik.com/nerf 突破性后续改进&#xff1a; Instant Neural Graphics Primitives with a Multiresolution Hash Encoding | 展示官网&#…

【Vuex状态管理】Vuex的基本使用;核心概念State、Getters、Mutations、Actions、Modules的基本使用

目录 1_应用状态管理1.1_状态管理1.2_复杂的状态管理1.3_Vuex的状态管理 2_Vuex的基本使用2.1_安装2.2_创建Store2.3_组件中使用store 3_核心概念State3.1_单一状态树3.2_组件获取状态3.3_在setup中使用mapState 4_核心概念Getters4.1_getters的基本使用4.2_getters第二个参数4…

vue3哪个数组方法在vue2上做了升级处理

在 Vue 3 中&#xff0c;v-for 指令的数组更新行为进行了升级处理。在 Vue 2 中&#xff0c;当使用 v-for 渲染数组时&#xff0c;如果对数组进行了以下操作&#xff0c;Vue 无法检测到变化&#xff1a; 直接通过索引修改数组元素&#xff0c;例如 arr[0] newValue修改数组的…

一文读懂GPU显卡的10个重要参数

在当今的高性能计算机世界中&#xff0c;GPU显卡的性能至关重要。这一领域的快速发展&#xff0c;使得图形渲染、游戏体验、视频编辑等高性能计算任务变得更加高效和流畅。正因如此&#xff0c;选择一款合适的GPU显卡变得越来越重要。在挑选GPU显卡时&#xff0c;了解其关键参数…

【SpringSecurity】十一、SpringSecurity集成JWT实现token的方法与校验

文章目录 1、依赖与配置2、JWT工具类3、认证成功处理器4、创建JWT过滤器5、安全配置类 1、依赖与配置 添加JWT的maven依赖&#xff1a; <!-- 添加jwt的依赖 --> <dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId…