实现功能
- 每个客户端上线,服务端可以向其他客户端广播上线信息;
- 发送的消息可以广播给其他在线的客户
- 支持改名
- 支持客户端主动退出
- 支持通过who查找当前在线的用户
- 超时退出
流程
变量
- 用户结构体 保存用户的管道,用户名以及网络地址信息
type Client struct {
C chan string //用于发送数据的管道
Name string //用户名
Addr string //网络地址
}
- 保存在线用户的map表
var onlineMap map[string]Client
- 消息通道
var message = make(chan string)
主协程
- 监听客户端的连接请求
listener, err := net.Listen("tcp", "127.0.0.1:8000")
- 当客户端有消息发送,就向当前用户列表中所有在线用户转发消息
go Manager()
- 接受客户端的请求
conn, err1 := listener.Accept()
- 处理用户连接
go HandleConn(conn)
func main() {
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("net.Listen.err=", err)
return
}
defer listener.Close()
//新开一个协程,转发消息,只要有消息,就遍历map,给每个成员发送消息
go Manager()
//主协程,循环阻塞等待用户连接
for {
conn, err1 := listener.Accept()
if err1 != nil {
fmt.Println("listener.Accept.err1=", err1)
continue
}
//处理用户连接
go HandleConn(conn)
}
}
处理用户连接子协程
- 获取客户端的网络地址
cliAddr := conn.RemoteAddr().String()
- 创建一个用户结构体,默认:用户名和网络地址一样
cli := Client{make(chan string), cliAddr, cliAddr}
,加入map表 - 给客户端发送信息
go WriteMsgToClient(cli, conn)
- 广播某个人在线
message <- MakeMsg(cli, "login")
- 提示当前用户
cli.C <- MakeMsg(cli, "I am here")
- 判断用户状态isQuit hasData
- 接收用户的请求,查看当前用户who,改名rename,发送消息message
func HandleConn(conn net.Conn) {
cliAddr := conn.RemoteAddr().String()
cli := Client{make(chan string), cliAddr, cliAddr}
//把结构体添加到map
onlineMap[cliAddr] = cli
//新开一个协程,给客户端发送信息
go WriteMsgToClient(cli, conn)
//广播某个人在线
message <- MakeMsg(cli, "login")
//提示当前用户
cli.C <- MakeMsg(cli, "I am here")
isQuit := make(chan bool) //对方是否主动退出
hasData := make(chan bool) //对方是否有数据
//新开一个协程,接收用户的请求
go func() {
buf := make([]byte, 2048)
for {
n, err := conn.Read(buf)
if n == 0 {
//对方断开或者出问题
isQuit <- true
fmt.Println("conn.Read.err=", err)
return
}
msg := string(buf[:n-1])
if len(msg) == 3 && msg == "who" {
//遍历map,给当前用户发送所有成员
conn.Write([]byte("user list:\n"))
for _, tmp := range onlineMap {
msg := tmp.Addr + ":" + tmp.Name + "\n"
conn.Write([]byte(msg))
}
} else if len(msg) >= 8 && msg[:6] == "rename" {
name := strings.Split(msg, "|")[1]
cli.Name = name
onlineMap[cliAddr] = cli
conn.Write([]byte("rename ok\n"))
} else {
message <- MakeMsg(cli, msg)
}
hasData <- true //代表有数据
}
}()
for {
//通过select检测channel的流动
select {
case <-isQuit:
delete(onlineMap, cliAddr) //当前用户从map移除
message <- MakeMsg(cli, "login out") //广播谁下线了
return
case <-hasData:
case <-time.After(60 * time.Second):
delete(onlineMap, cliAddr)
message <- MakeMsg(cli, "time out leave out")
return
}
}
}
给客户端发送信息
func WriteMsgToClient(cli Client, conn net.Conn) {
for msg := range cli.C {
conn.Write([]byte(msg + "\n"))
}
}
发送消息
func MakeMsg(cli Client, msg string) (buf string) {
buf = "[" + cli.Addr + "]" + cli.Name + ":" + msg
return
}
转发消息子协程
有消息到来就进行广播
- 给map分配空间
onlineMap = make(map[string]Client)
- 遍历在线用户列表,转发消息;没有消息之前message通道会阻塞
func Manager() {
//给map分配空间
onlineMap = make(map[string]Client)
for {
msg := <-message //没有消息前,会阻塞
for _, cli := range onlineMap {
cli.C <- msg
}
}
}
设计到的知识点
- 网络编程,监听客户端连接,处理连接请求,发送转发消息等
- map,切片,结构体数据,通道.
- 通过select检测channel的流动
- 并发编程,开辟子协程处理当前请求等
- 超时判断