在线用户查询
先查询map中的用户,将信息写到OnlineMsg中
再将OnlineMsg写到user管道中,由user的监听管道功能发送给对应的client。
总结一下handler函数中现有的功能:
1、用户上线,将用户加入map中
2、广播用户上线消息
3、循环接收用户消息,并判断是不是“who”,是就行查询,不是就广播。
单独和广播的区别在于一个在for循环外,一个在for循环里
修改用户名
超时强踢
笔记:
select格式:
select { case <-ch1: // 如果从 ch1 信道成功接收数据,则执行该分支代码 case ch2 <- 1: // 如果成功向 ch2 信道成功发送数据,则执行该分支代码 default: // 如果上面都没有成功,则进入 default 分支处理流程 }
1、select里的case后面不带判断条件,而是一个信道的操作。
2、select的case条件是并发执行的,select会选择先操作成功的那个case条件去执行,如果多个同时返回,则随机选择一个执行,此时将无法保证执行顺序。
3、如果有超时条件语句,判断逻辑为如果在这个时间段内一直没有满足条件的case,则执行这个超时case。如果此段时间内出现了可操作的case,则直接执行这个case。一般用超时语句代替了default语句。
1、实际超时使用:
select { case str := <- ch1: fmt.Println("receive str", str) case <- time.After(time.Second * 5): fmt.Println("timeout!!") }2、阻塞main函数
让main函数阻塞不退出,使用空的select{}来阻塞main函数。
私聊功能
现将处理msg的函数单独拿出来
加入对私聊的判断:
if len(msg) > 4 && msg[:3] == "to|" {...}
调试:
最后附上现有代码:
其中client.go和user.go没有变化和上一版一样Go语言练习:聊天室(一)——server搭建、client搭建、用户上线提醒_you_you_lu的博客-CSDN博客
只有server.go进行改动,除了加入新功能外,将一些代码(上线加入map,下线从map删掉,发送消息)进行封装,使得代码更加简洁。
package main
import (
"fmt"
"io"
"net"
"strings"
"sync"
"time"
)
// 创建server类型
type Server struct {
Ip string
Port int
//增加onlineMap
OnlineMap map[string]*User //map是哈希列表,键是字符串类型,值是指向User类型对象的指针
mapLock sync.RWMutex //读写锁,用于保护map
//消息广播的channel
Message chan string
}
// 创建server的接口,用于创建一个server类型的实例
func NewServer(ip string, port int) *Server {
server := &Server{
Ip: ip,
Port: port,
OnlineMap: make(map[string]*User),
Message: make(chan string),
}
return server
}
// 监听Message广播消息channel的goroutine,一旦有消息就发送给全部的在线User
func (s *Server) ListenMessager() {
for {
msg := <-s.Message //从管道中读数据
//将msg发送给全部的在线User
s.mapLock.Lock()
for _, cli := range s.OnlineMap { //key不关心,只关心value,value就是指向User的指针
cli.C <- msg
}
s.mapLock.Unlock()
}
}
// 广播消息的方法 将上线消息写到Message通道中
func (s *Server) Broadcast(user *User, msg string) {
sendMsg := "[" + user.Name + "]" + ":" + msg
//将sendMsg写到通道中
s.Message <- sendMsg
}
// 给当前user管道发消息
func (s *Server) sendMessage(user *User, msg string) {
user.C <- msg
}
// 对msg进行处理
func (s *Server) handleMessage(user *User, msg string) {
//查询功能
if msg == "who" {
s.mapLock.RLock()
OnlineMsg := "Online Users:"
for _, cli := range s.OnlineMap {
OnlineMsg += "[" + cli.Name + "]"
}
user.C <- OnlineMsg
s.mapLock.RUnlock()
} else if len(msg) > 7 && msg[:7] == "rename:" { //修改名称
//消息格式rename:张三
Newname := msg[7:] //获取名字
//判断名字是否被占用
_, ok := s.OnlineMap[Newname]
if ok {
s.sendMessage(user, "The name is already taken")
} else {
s.mapLock.Lock()
delete(s.OnlineMap, user.Name)
s.OnlineMap[Newname] = user
s.mapLock.Unlock()
user.Name = Newname
s.sendMessage(user, "You have updated your username:"+Newname)
}
} else if len(msg) > 4 && msg[:3] == "to|" {
//to|zhangsan|...
//获取名字
Getname := strings.Split(msg, "|")[1]
if Getname == "" {
s.sendMessage(user, "The message format is incorrect")
return
}
//根据名字获取对方User对象,利用map
Getuser, ok := s.OnlineMap[Getname]
if !ok {
s.sendMessage(user, "The user does not exist")
return
}
//获取消息
content := strings.Split(msg, "|")[2]
if content == "" {
s.sendMessage(user, "There is no message content, please resend it")
return
}
s.sendMessage(Getuser, user.Name+" say: "+content)
} else {
//将消息广播
s.Broadcast(user, msg)
}
}
// 用户上线,将用户加入map中
func (s *Server) Online(user *User) {
//用户上线,将用户加入map中
s.mapLock.Lock()
s.OnlineMap[user.Name] = user
s.mapLock.Unlock()
}
// 用户下线,将用户从map中删掉
func (s *Server) offline(user *User) {
s.mapLock.Lock()
delete(s.OnlineMap, user.Name)
s.mapLock.Unlock()
}
func (s *Server) Handler(conn net.Conn) {
user := NewUser(conn)
//用户上线,将用户加入map中
s.Online(user)
//广播当前用户上线消息
s.Broadcast(user, "user is online")
//监听用户是否活跃的channel
isLive := make(chan bool)
//接受客户端发送的消息
go func() {
buf := make([]byte, 4096)
for {
n, err := conn.Read(buf)
if n == 0 {
s.offline(user)
s.Broadcast(user, "offline")
return
}
if err != nil && err != io.EOF {
fmt.Println("服务端的Read err=", err)
return //这个很重要
}
//提取用户消息(去除'\n')
msg := string(buf[:n-1])
s.handleMessage(user, msg)
isLive <- true //能收到用户消息,说明活跃
}
}()
for {
select {
case <-isLive: //说明用户是活跃的,应该重置定时器
case <-time.After(time.Minute * 10): //说明超时
s.sendMessage(user, "You've been kicked out")
//销毁资源
close(user.C)
conn.Close()
//从map中删掉
s.mapLock.Lock()
delete(s.OnlineMap, user.Name)
s.mapLock.Unlock()
return //退出hanlder
}
}
}
// 启动服务器的端口
func (s *Server) Start() {
//socket listen
fmt.Println("服务端开始监听...")
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%d", s.Ip, s.Port))
if err != nil {
fmt.Println("listen err=", err)
return
}
defer listener.Close() //close
//启动监听Message的goroutine
go s.ListenMessager()
//accept
for {
//等待客户端连接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err=", err)
} else {
fmt.Printf("连接到客户端ip=%v\n", conn.RemoteAddr().String()) //查看conn
}
//do handler
go s.Handler(conn)
}
}
func main() {
server := NewServer("127.0.0.1", 8888) //创建实例
server.Start()
}
注:server.go和client.go不要放在同一个文件夹里。