目录
- 实现功能
- 流程
- 变量
- 主协程
- 处理用户连接子协程
- 给客户端发送信息
- 发送消息
- 转发消息子协程
- 设计到的知识点
- 效果展示
实现功能
- 每个客户端上线,服务端可以向其他客户端广播上线信息;
- 发送的消息可以广播给其他在线的客户
- 支持改名
- 支持客户端主动退出
- 支持通过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,给每个成员发送消息 javascriptgo 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, cliwww.devze.comAddr} //把结构体添加到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 phpstring) (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 <- http://www.devze.commsg } } }
设计到的知识点
- 网络编程,监听客户端连接,处理连接请求,发送转发消息等
- map,切片,结构体数据,通道.
- 通过select检测channel的流动
- 并发编程,开辟子协程处理当前请求等
- 超时判断
效果展示
到此这篇关于Go语言实现广播式并发聊天服务器的文章就介绍到这了,更多相关Go语言 广播式并发聊天 内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程客栈(www.devze.com)!
精彩评论