1聊天室服務器端
package main import ( "fmt" "net" "strings" "time" ) //定義的此結構體爲全局map的value值,包括每一個用戶的姓名,ip地址和私人管道 type client struct { name string addr string C chan string } /*這個函數是將私人管道中的內容發送給用戶,配合全局管道Message使用可以實現廣播的功能, 單獨使用可以實現私聊的功能*/ func writemsg2client(clinet client, conn net.Conn) { for m := range clinet.C { conn.Write([]byte(m + "\n")) } } //這只是一個封裝好用來統一(發送信息格式)的小函數,不用在意 func makemsg(name string, addr string, s string) string { return "[" + addr + "]" + name + ":" + s } //每一個進入聊天室的用戶都將啓動一個handleconn的go程來處理事件 func handleconn(conn net.Conn) { defer conn.Close() /*用戶連接進來以後要初始化全局map,把自己的信息加入到字典裏,相當於進到聊天室裏之前要登 記一下個人信息,注意姓名初始爲ip地址。*/ addr := conn.RemoteAddr().String() fmt.Printf("用戶%s進入了房間\n", addr) client := client{addr, addr, make(chan string)} //在這裏啓動子go程,功能上面已經提及,具體就是會寫信息給自己連接的客戶端 go writemsg2client(client, conn) onlinemap[addr] = client //登錄進來一切準備就緒後就給所有人廣播上線信息啦 Message <- makemsg(client.name, addr, "login") //下面這三個變量服務於下面一些小功能 var haschat = make(chan bool) var ifquit = make(chan bool) var flag bool //從這單獨開啓一個go程來讀取用戶輸入的信息 go func() { buf := make([]byte, 4096) for { n, _ := conn.Read(buf) if n == 0 { fmt.Printf("%s離開了房間\n", client.name) ifquit <- true return } //改名功能的實現 if string(buf[:7]) == "Rename|" { client.name = strings.Split(string(buf[:n]), "|")[1] onlinemap[addr] = client conn.Write([]byte("rename success\n")) } else if string(buf[:n-1]) == "/who" { //查詢在線用戶信息的功能 for _, s := range onlinemap { conn.Write([]byte(s.name + "online\n")) } } else if string(buf[:2]) == "m|" && strings.Count(string(buf[:n]), "|") == 2 { /*私聊功能的實現,其實私聊功能就是跳過了往全局Message裏傳輸信息, 改爲直接向私人管道里傳輸信息*/ flag = true slice := strings.Split(string(buf[:n]), "|") fmt.Println(slice[1]) for _, a := range onlinemap { //遍歷所有在線用戶,向指定的用戶管道中發送信息 if a.name == slice[1] { flag = false a.C <- makemsg(client.name, addr, slice[2]) conn.Write([]byte("send success")) } } if flag { conn.Write([]byte("no such man or not online")) } } else { Message <- makemsg(client.name, addr, string(buf[:n])) } haschat <- true } }() for { select { case <-haschat: //超時強踢 case <-time.After(time.Minute * 100): delete(onlinemap, addr) Message <- makemsg(client.name, addr, "out time to leave") close(client.C) return case <-ifquit: //退出處理 delete(onlinemap, addr) Message <- makemsg(client.name, addr, "out time to leave") close(client.C) return } } } //這個函數用來將全局Message中的內容全部塞到私人管道C裏,實現上下線廣播和羣聊的功能 func Manager() { for { msg := <-Message for _, s := range onlinemap { s.C <- msg } } } var Message = make(chan string) var onlinemap map[string]client = make(map[string]client) //主函數 func main() { listener, _ := net.Listen("tcp", "127.0.0.1:9876") defer listener.Close() //提前開啓全局Message的go程,防止被阻塞 go Manager() for { conn, err := listener.Accept() if err != nil { fmt.Println("accept err", err) continue } //每一個連接進來的用戶都會被分配進入一個子go程,用來處理上面我們提到的各種功能 go handleconn(conn) } } /*備註 1、 listener, _ := net.Listen("tcp", "127.0.0.1:9876") 監聽啓動 2、 go Manager()開啓全局Message的go程,防止被阻塞,沒有消息便被阻塞,有消息便會被喚起, 消息發送完畢後重新等待消息,有消息變發送沒消息便阻塞等待(Message 是一個字符串channel )。 接收到消息後,遍歷所有在線人員,並把消息發送給client的私人通道。 func Manager() { for { msg := <-Message for _, s := range onlinemap { s.C <- msg } } } 3、私人通道消息處理 這個函數是將私人管道中的內容發送給用戶,配合全局管道Message使用可以實現廣播的功能。 單獨使用可以實現私聊的功能(m|客戶端連接ip加端口|發送消息)(m|127.0.0.1:59700|hello)。 這個函數也是等待消息,收到消息後被喚醒執行,消息執行完畢後等待新消息,沒有阻塞,有就處理 func writemsg2client(clinet client, conn net.Conn) { for m := range clinet.C { conn.Write([]byte(m + "\n")) } } */
2、聊天室客戶端
package main import ( "bufio" "fmt" "net" "os" "strings" ) func readFromServer(conn net.Conn) { buf := make([]byte, 4096) for { n, err := conn.Read(buf) if err != nil { fmt.Println(err) os.Exit(1) } defer conn.Close() fmt.Println("接收到消息:", string(buf[:n])) fmt.Println("請輸入要發送的消息:") } } func main() { conn, err := net.Dial("tcp", "127.0.0.1:9876") if err != nil { fmt.Println(err) return } defer conn.Close() go readFromServer(conn) //fmt.Println("請輸入要發送的消息:") for { //strs :="" // fmt.Scanln(&strs) 空格有問題 //strs := make([]byte, 4096) //n, err := os.Stdin.Read(strs) str, err := bufio.NewReader(os.Stdin).ReadString('\n') if err != nil { fmt.Println(err) } str = strings.TrimSpace(str) //fmt.Println("發送前", , "展示") //fmt.Println("a", str, "b") if str == "Q" { fmt.Println("接收到退出命令,退出客戶端") break } conn.Write([]byte(str)) } }