跟着😽猫猫学Golang,快人一步系列初开,跟着我走进Go 语言的世界里🌍
系列目录
Golang 从零开始实现多人聊天室(一)服务端监听
Golang 从零开始实现多人聊天室(二)客户端访问
Golang 从零开始实现多人聊天室(三)上线通知与公屏聊天
运用 go 里面的net包中的相关方法来实现一个基于tcp的简单多人聊天室
实现
- 加入聊天室
- 广播通知
- 重新登录
- 上线与离线通知
- 公屏聊天
- 群聊
- 单聊
- 退出与注销
项目代码持续更新建立文件目录进行服务端与客户端区分
服务端代码就写在server 文件夹下的 server 文件中
客户端代码就写在cliemt 文件夹下的 client文件中
一、服务端 client此时更新到离线与用户信息修改
客户端的离线与注销要区别开来,
- 离线 也就是我们可以重新登陆,还要保留我们的用户信息
- 注销 也就是我们离开该服务器,那么我们就要删除连接信息
1.用户信息
为了区分离线与注销,我们为每个客户端新建一个用户信息模型
type User struct {
Age string //年龄
Sex string //性别
}
存入Client模型中,表示每一个人有他们对应的信息
type Client struct {
Conn net.Conn // 连接信息
Name string // 别名
IsQuit bool // 是否退出
User
}
也等于下方模型
type Client struct {
Conn net.Conn // 连接信息
Name string // 别名
Age string //年龄
Sex string //性别
}
2.离线
如果我们想要退出聊天室,那么我们就不能与服务器再次通信,需要重新建立连接。所以我们新建一个方法Quit 来处理退出,
switch cMsg.Op {
case Read:
cMsg.Read()
case Quit:
cMsg.Quit()
case NtyLogin:
cMsg.ntyLogin()
default:
fmt.Println("无效OP")
}
func (m Message) Quit() {
fmt.Printf("%v 用户[%s]: 退出 \n", time.Now().Format("2006-01-02 15:04:05"), m.Name)
// 与上线通知同理,遍历所有链接进行离线通知
for _, client := range ConnMap {
// 当找到自己时,关闭与自身的连接且忽略给自己的离线通知
if client.Name == m.Name {
client.Conn.Close()
continue
}
msg := fmt.Sprintf("%v [%s]: %v", time.Now().Format("2006-01-02 15:04:05"), m.Name, "I Logout")
_, err := client.Conn.Write([]byte(msg))
if err != nil {
fmt.Println("client Conn Error")
return
}
}
}
离线后客户但继续发送则服务端无法接收
3.区分离线与注销的准备——用户信息OP
我们离线需要保留自身的信息,那么我们需要先给服务器自身的信息,服务器在连接中保留你的用户信息,并在你查询时为你显示。我们则需要一个修改用户信息的方法
const (
Read = iota + 1
Quit
NtyLogin
UpdUser // 修改用户信息
)
改进服务端Op表
switch cMsg.Op {
case Read:
cMsg.Read()
case Quit:
cMsg.Quit()
case NtyLogin:
cMsg.ntyLogin()
case UpdUser:
cMsg.UpdUser()
default:
fmt.Println("无效OP")
}
当我们收到Op为4的请求时,我们判断为更新用户信息的请求
4.区分离线与注销的准备——修改用户信息方法
我们更新需要直到新的用户信息,那么我们可以通过Json的方式传输,通过 json.Marshal() 与 json.Unmarshal() 实现我们的消息通信
func (m Message) UpdUser() {
fmt.Printf("%v 用户[%s]: 修改用户信息 \n", time.Now().Format("2006-01-02 15:04:05"), m.Name)
var user User
// 解码 msg
err := json.Unmarshal([]byte(m.Msg), &user)
if err != nil {
return
}
// 修改当前客户端信息
ConnMap[m.Name] = Client{
Conn: ConnMap[m.Name].Conn,
Name: ConnMap[m.Name].Name,
User: user,
}
fmt.Printf("%v 用户[%s]: 用户信息 %v \n", time.Now().Format("2006-01-02 15:04:05"), m.Name, user)
msg := fmt.Sprintf("%v 用户[%s]: 用户信息 %v \n", time.Now().Format("2006-01-02 15:04:05"), m.Name, ConnMap[m.Name].User)
// 通知用户修改信息成功
_, err = ConnMap[m.Name].Conn.Write([]byte(msg))
if err != nil {
fmt.Println("client Conn Error")
return
}
}
服务端成功接收到用户修改信息请求,并修改用户信息成功
1.用户信息
为了区分离线与注销,我们为每个客户端新建一个用户信息模型
type User struct {
Age string //年龄
Sex string //性别
}
2.离线
我们新建一个方法Quit 来处理离线,约定好的Quit 作为Op
func (m Message) Quit(conn net.Conn) {
msg := m.Name + "|" + strconv.Itoa(Quit) + "|"
_, err := conn.Write([]byte(msg))
if err != nil {
fmt.Println("离线失败")
return
}
fmt.Println("离线成功")
}
3.修改用户信息
我们离线需要保留自身的信息,那么我们需要先给服务器自身的信息,服务器在连接中保留你的用户信息,并在你查询时为你显示。我们则需要一个修改用户信息的方法
const (
Say = iota + 1
Quit
Login
UpdUser
)
改进服务端Op表
switch msg.Op {
case Say:
msg.Say(conn)
case Quit:
msg.Quit(conn)
case Login:
fmt.Println("您已登录,输入无效,请重新输入")
case UpdUser:
msg.UpdUser(conn)
default:
fmt.Println("输入无效op,请重新输入")
}
收到Op为4的输入时,我们进行用户信息修改
func (m Message) UpdUser(conn net.Conn) {
var user User
fmt.Println("请输入想要的更新的用户年龄:")
_, _ = fmt.Scanln(&user.Age)
fmt.Println("请输入想要的更新的用户性别:")
_, _ = fmt.Scanln(&user.Sex)
// 将 user 结构体转为json字符串格式
marshal, err := json.Marshal(user)
if err != nil {
return
}
// 与服务端约定好的格式发送
m.Msg = string(marshal)
msg := m.Name + "|" + strconv.Itoa(m.Op) + "|" + m.Msg
_, err = conn.Write([]byte(msg))
if err != nil {
fmt.Println("修改失败")
return
}
fmt.Println("修改成功")
}
发送信息成功,收到服务端的信息更新成功提示
🎶感谢您看到这里🎶
从零开始实现一个基于Go的多人在线聊天室,功能包括:单聊、群聊、昵称、上下线通知、聊天日志等等,
通过约定好的格式进行数据的传输,今天我们实现的json格式的传输,同时也进行了TCP连接的关闭,实现的一个人的离线与修改自身的用户信息,这将会实用的出现在工作的方方面面。我们下篇博文将会重点放在我们的用户信息是否在重新登陆后还存在与注销的简单使用,通过简单的连接管理能否实现离线与注销的简单区分。多人在线聊天室是我们学习TCP等消息协议最简单的项目,能让我们熟悉服务端与客户端的连接,也能更好的服务于工作的需要。