實現功能
- 每個客戶端上線,服務端可以向其他客戶端廣播上線信息;
- 發送的消息可以廣播給其他在線的客戶
- 支持改名
- 支持客戶端主動退出
- 支持通過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}//把結構體添加到maponlineMap[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 <- truefmt.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 = nameonlineMap[cliAddr] = cliconn.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") //廣播誰下線了returncase <-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 + ":" + msgreturn
}
轉發消息子協程
有消息到來就進行廣播
- 給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的流動
- 并發編程,開辟子協程處理當前請求等
- 超時判斷