自学内容网 自学内容网

Go语言--广播式并发聊天服务器

实现功能

  1. 每个客户端上线,服务端可以向其他客户端广播上线信息;
  2. 发送的消息可以广播给其他在线的客户
  3. 支持改名
  4. 支持客户端主动退出
  5. 支持通过who查找当前在线的用户
  6. 超时退出

流程

在这里插入图片描述

变量

  • 用户结构体 保存用户的管道,用户名以及网络地址信息
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
}
}
}

设计到的知识点

  1. 网络编程,监听客户端连接,处理连接请求,发送转发消息等
  2. map,切片,结构体数据,通道.
  3. 通过select检测channel的流动
  4. 并发编程,开辟子协程处理当前请求等
  5. 超时判断

效果展示

在这里插入图片描述


原文地址:https://blog.csdn.net/m0_73537205/article/details/140151350

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!