通常聊天室的架構分為伺服器端和用戶端:
伺服器端:
接受來自於用戶端的串連請求並建立串連;
所有用戶端的串連會放進串連池中,用於廣播訊息;
用戶端:
串連伺服器;
向伺服器發送訊息;
接收伺服器的廣播訊息;
注意事項:
某一個用戶端中斷連線後需要從串連池中摘除,並不再接收廣播訊息;
某一個用戶端中斷連線後不能影響伺服器端或別的用戶端的串連;
詳細的代碼如下,文檔看注釋就好了,不再細說:
伺服器:
server.go
package mainimport ( "net" "log" "fmt")func main() { port := "9090" Start(port)}// 啟動伺服器func Start(port string) { host := ":" + port // 擷取tcp地址 tcpAddr, err := net.ResolveTCPAddr("tcp4", host) if err != nil { log.Printf("resolve tcp addr failed: %v\n", err) return } // 監聽 listener, err := net.ListenTCP("tcp", tcpAddr) if err != nil { log.Printf("listen tcp port failed: %v\n", err) return } // 建立串連池,用於廣播訊息 conns := make(map[string]net.Conn) // 訊息通道 messageChan := make(chan string, 10) // 廣播訊息 go BroadMessages(&conns, messageChan) // 啟動 for { fmt.Printf("listening port %s ...\n", port) conn, err := listener.AcceptTCP() if err != nil { log.Printf("Accept failed:%v\n", err) continue } // 把每個用戶端串連扔進串連池 conns[conn.RemoteAddr().String()] = conn fmt.Println(conns) // 處理訊息 go Handler(conn, &conns, messageChan) }}// 向所有串連上的鄉親們發廣播func BroadMessages(conns *map[string]net.Conn, messages chan string) { for { // 不斷從通道裡讀取訊息 msg := <-messages fmt.Println(msg) // 向所有的鄉親們發訊息 for key, conn := range *conns { fmt.Println("connection is connected from ", key) _, err := conn.Write([]byte(msg)) if err != nil { log.Printf("broad message to %s failed: %v\n", key, err) delete(*conns, key) } } }}// 處理用戶端發到服務端的訊息,將其扔到通道中func Handler(conn net.Conn, conns *map[string]net.Conn, messages chan string) { fmt.Println("connect from client ", conn.RemoteAddr().String()) buf := make([]byte, 1024) for { length, err := conn.Read(buf) if err != nil { log.Printf("read client message failed:%v\n", err) delete(*conns, conn.RemoteAddr().String()) conn.Close() break } // 把收到的訊息寫到通道中 recvStr := string(buf[0:length]) messages <- recvStr }}
用戶端:
client.go
package mainimport ( "net" "log" "fmt" "os")func main() { Start(os.Args[1])}func Start(tcpAddrStr string) { tcpAddr, err := net.ResolveTCPAddr("tcp4", tcpAddrStr) if err != nil { log.Printf("Resolve tcp addr failed: %v\n", err) return } // 向伺服器撥號 conn, err := net.DialTCP("tcp", nil, tcpAddr) if err != nil { log.Printf("Dial to server failed: %v\n", err) return } // 向伺服器發訊息 go SendMsg(conn) // 接收來自伺服器端的廣播訊息 buf := make([]byte, 1024) for { length, err := conn.Read(buf) if err != nil { log.Printf("recv server msg failed: %v\n", err) conn.Close() os.Exit(0) break } fmt.Println(string(buf[0:length])) }}// 向伺服器端發訊息func SendMsg(conn net.Conn) { username := conn.LocalAddr().String() for { var input string // 接收輸入訊息,放到input變數中 fmt.Scanln(&input) if input == "/q" || input == "/quit" { fmt.Println("Byebye ...") conn.Close() os.Exit(0) } // 只處理有內容的訊息 if len(input) > 0 { msg := username + " say:" + input _, err := conn.Write([]byte(msg)) if err != nil { conn.Close() break } } }}
測試方法:
編譯server.go和client.go;
開啟終端,啟動server,預設會監聽9090連接埠;
再開啟多個終端,啟動client,client啟動命令:client 伺服器IP:9090;
在client中輸入字元並斷行符號,可以看到別的終端都會收到訊息;