go中多人聊天室的搭建

首先利用的是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)
   }
}


代码解读:

  1. 首先先是正常的创建连接和监听端口  ln, err := net.Listen("tcp", ":11");
  2. 其次执行pushonline()函数的时候,由于此时的msg并没有任何的数据,所以在程序走到pushmsg := <-msg的时候便会发生阻塞,从而不会再执行下面的遍历.
  3. 完后在执行通信开始的时候启用协程函数chuli();在此函数中主要的功能是把新连接的客户加入onlieclent中,并在共有的管道中加入msg <- "用户:" + address + "上线了!!",这时阻塞的pushonline()函数由于msg的数据加入而变得不在阻塞,且此时的onlieclent是有刚刚加入的数据的, 所以函数可以继续执行,并在所有的map.c中加入了信息   
  4.        
  5. 最后执行了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;

具体测试如下:

//给客户端发送消息
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{}不在阻塞,从而执行写入数据.


逻辑结构图:




猜你喜欢

转载自blog.csdn.net/feiwutudou/article/details/80695055