即时通讯系统 -- V0.2用户上线及广播功能
V0.1实现了server的基础构建,并在main.go中测试了连接成功
V0.2要实现用户上线功能,并且某用户上线时所有在线用户都会收到该用户的上线信息,即蓝线功能
- 实现用户上线功能,先定义一个用户结构体
type User struct {
Name string
Addr string
C chan string
conn net.Conn
}
Name为用户名(为了方便Name与Addr一致)
Addr为ip地址
C为用户的chanel
conn为该用户与服务器tcp连接
- 创建一个用户的API
// 创建一个用户的API
func NewUser(conn net.Conn) *User {
userAddr := conn.RemoteAddr().String()
user := &User{
Name: userAddr,
Addr: userAddr,
C: make(chan string),
conn: conn,
}
//启动监听当前user channel消息的 goruntine
go user.ListenMessage()
return user
}
- 监听当前 user 的 channel,一旦有消息直接发给对端客户端
func (this *User) ListenMessage() {
for {
msg := <-this.C
//net.Conn.Write()向网络连接里写数据,参数是字节切片,因此要转换一下
this.conn.Write([]byte(msg + "\n"))
}
}
- 完善 server 结构体
- 要维护当前在线用户,需要一个 map
- 可能多个 goruntine 访问该 map,需要加读写互斥锁
- server 也需要一个 channel 来存储广播消息
type Server struct {
Ip string
Port int
//在线用户列表
OnlineMap map[string]*User
mapLock sync.RWMutex
//消息广播的channel
Message chan string
}
- server 监听 Message 广播消息的 goruntine,一旦有消息就发送给全部在线 uesr
// 广播消息的方法
func (this *Server) BroadCast(user *User, msg string) {
sendMsg := "[" + user.Addr + "]" + user.Name + ":" + msg
this.Message <- sendMsg
}
// 监听Message广播消息的goroutine,一旦有消息就发给全部的在线user
func (this *Server) ListenMessager() {
for {
msg := <-this.Message
//将msg发给全部在线user
this.mapLock.Lock()
for _, cli := range this.OnlineMap {
cli.C <- msg
}
this.mapLock.Unlock()
}
}
func (this *Server) Handler(conn net.Conn) {
//当前连接的业务
//fmt.Println("连接建立成功")
user := NewUser(conn)
//用户上线,将用户加入OnlineMap中
this.mapLock.Lock()
this.OnlineMap[user.Name] = user
this.mapLock.Unlock()
//广播当前用户上线信息
this.BroadCast(user, "已上线")
//当前handler阻塞
select {}
}
总结
-
在Handler()中,select{}语句的作用是让当前的Handler方法永久阻塞,以保持该连接的持续性。如果没有这个select{}语句,当一个连接被处理完后,Handler方法会立即返回,从而导致连接被关闭。
由于该Server使用goroutine并发处理多个客户端连接,每个连接都会在单独的goroutine中运行。在每个连接的Handler方法中,为了保持连接持续,需要使用某种方法防止连接被关闭,以便在接收到来自客户端的数据时能够继续处理。select{}语句是一种常见的阻塞方法,它可以防止当前goroutine返回并保持连接持续。
这种阻塞方法并不是最佳的选择,因为它会浪费CPU资源。更好的方式是使用通道或定时器,以便在接收到数据或超时时解除阻塞。 -
要加读写锁mapLock,是因为OnlineMap这个map可能会被多个goroutine同时访问,这样会导致数据不一致或者竞态条件。为了保证数据的安全性和一致性,需要用sync.RWMutex这个类型的锁来控制对map的读写操作。sync.RWMutex是一个读写互斥锁,它允许多个goroutine同时读取map,但是只允许一个goroutine写入map,并且读写是互斥的