首先利用的是go的多协程,再利用goroute之间的通信,对多个任务的管控
服务端的搭建:
server.go
package main import ( "net" "fmt" ) //储存用户信息的方法体C type Client struct { C chan string Name string Addr string } //储存在线用户的map var onlieclent = make(map[string]Client) //管道间的消息 var msg = make(chan string) //遍历管道消息 func pushonline() { //没有消息会阻塞在这里 for { pushmsg := <-msg for _, v := range onlieclent { //把当前的信息传递给通讯C,此时map中的c的数据相同 v.C <- pushmsg } } } //处理数据 func chuli(conn net.Conn) { //结束后关闭连接 defer conn.Close() //当前连接的地址 address := conn.RemoteAddr().String() //打印连接成功 fmt.Printf("[%s]:%s\n", address, "连接成功") //在线用户输入map[]中 cli := Client{make(chan string), address, address} onlieclent[address] = cli fmt.Println("更新后:", onlieclent) //往msg存入数据,pushonline不在阻塞,便开始通信 msg <- "用户:" + address + "上线了!!" //新开一个协程,给客户端发送信息 go writer(cli, conn) //协程转发用户的请求 go func() { by := make([]byte, 1024) for { number, err := conn.Read(by) if number == 0 { fmt.Println("没有信息:", err) return } str := string(by[:number]) msg <- str } }() for { } } //给客户端发送消息 func writer(cli Client, conn net.Conn) { for v := range cli.C { conn.Write([]byte("["+cli.Name+" say:]"+v+ "\n")) } } func main() { //监听端口 ln, err := net.Listen("tcp", ":11") defer ln.Close() if err != nil { fmt.Println("你有错误:", err.Error()) } //遍历在线用户的消息 go pushonline() for { //通信实现和等待下一个呼叫 conn, err1 := ln.Accept() if err1 != nil { fmt.Println("你有错误1:", err1.Error()) continue } //储存在线用户,并发送信息 go chuli(conn) } }
代码解读:
- 首先先是正常的创建连接和监听端口 ln, err := net.Listen("tcp", ":11");
- 其次执行pushonline()函数的时候,由于此时的msg并没有任何的数据,所以在程序走到pushmsg := <-msg的时候便会发生阻塞,从而不会再执行下面的遍历.
- 完后在执行通信开始的时候启用协程函数chuli();在此函数中主要的功能是把新连接的客户加入onlieclent中,并在共有的管道中加入msg <- "用户:" + address + "上线了!!",这时阻塞的pushonline()函数由于msg的数据加入而变得不在阻塞,且此时的onlieclent是有刚刚加入的数据的, 所以函数可以继续执行,并在所有的map.c中加入了信息
- 最后执行了writer,由于刚才的pushonline()函数在每个map用户的C中都加有数据,所以此时的cli.C中也是有数据的,最终实现了多次传递信息的功能(当初很不解cli是一个单维集合,并不是多维,为什么要遍历,其实这里也可以用for的死循环,但是相比起来效率,确实不如阻塞遍历cli.c要快.每一个writer可以看做是各个任务执行了一遍,所以会全部展示)
深度解析:
因为当初做的是php中swoole的websocket连接,那边直接一个push全部实现推送,所以对这里的不是很明确,在对比了之前的单机连接(https://blog.csdn.net/feiwutudou/article/details/80680840)以后,发现了conn.Write([]byte())这个函数只是会对当前连接有用,也就是说是单线的,而不会想swoole中的push一样,所以这里面要实现聊天室的功能在并发的情况下必须要用到channels,也就是文中的<-c;
具体测试如下:
扫描二维码关注公众号,回复:
1691633 查看本文章
//给客户端发送消息 func writer(cli Client, conn net.Conn) { for v:=range cli.C{ fmt.Println("消息是:",v) fmt.Println("新的cli是:",cli) conn.Write([]byte("["+cli.Name+" say:]"+v+ "\n")) } }
当我修改了writer函数的时候,println的测试结果如图:
我们可以看到,在进行两个nc连接的时候,第二次"新的cli是:"要比第一次多了一个,其实也就是在channles中的msg在有数据的情况下,pushonline()函数会执行,并分发数据给每个在线onlieclent中的C,从而最终onlieclent中每个集合的C中有了数据,也使得writer()函数的for v:=range cli.C{}不在阻塞,从而执行写入数据.
逻辑结构图: