使用socket實現類似微信單聊自由發送或接收消息的功能.
server端:
func main() { listener, err := net.Listen("tcp", ":8080") if err != nil { panic(err) } log.Println("Listening... ...") for { conn, err := listener.Accept() if err != nil { panic(err) } log.Println("connect success") go handFunc(conn) } }
server端主函數監聽8080端口,為了實現多用戶連接,使用for循環使Accept方法能接收多個客戶端發送的連接請求,handFunc函數是處理連接成功后要進行的交互操作,使用goroutine開起一個協程去操作.
func handFunc(conn net.Conn) { recv, send := make(chan string), make(chan string) for { go func(r chan string) { buf := make([]byte, 1024) cnt, err := conn.Read(buf) if err != nil { panic(err) } r <- fmt.Sprintf("recv : %v", string(buf[:cnt])) }(recv) go func(s chan string) { reader := bufio.NewReader(os.Stdin) line, _, _ := reader.ReadLine() cnt, err := conn.Write(line) if err != nil { panic(err) } s <- fmt.Sprintf("send : %v", string(line[:cnt])) }(send) select { case accept := <-recv: log.Println(accept) case to := <-send: log.Println(to) } } }
handFunc中接收主函數連接成功后建立的net.Conn參數,函數中主要實現服務端信息的接收和發送功能.for循環為了實現多次交互,handFunc中又開起了兩個goroutine,第一個用於接收消息,第二個用於輸入內容發送給客戶端,使用channel和select實現自由發送和接收.
先定義兩個字符串類型的管道,分別將兩個管道傳遞給兩個匿名函數,第一個匿名函數中在conn.Read()時發生阻塞,直到有客戶端發來消息,把處理后的消息寫入管道中;第二個匿名函數在reader.ReadLine()處發生阻塞,直到控制台有消息輸入,通過conn.Write()方法將控制台輸入的消息發送給客戶端並將提示信息寫入對應管道.在select處我們監聽這兩個管道,兩個管道中只要有一個獲取到數據就會打印相應管道中的消息並結束select的阻塞.(注意:此時無論是接收了消息還是發送了消息,handFunc中的兩個goroutine只是關閉了一個,另一個還在阻塞狀態,下邊我們還會提起未關閉的goroutine)
client端:
func main() { conn, err := net.Dial("tcp", ":8080") if err != nil { panic(err) } log.Println("connect success") recv, send := make(chan string), make(chan string) for { go func(s chan string) { reader := bufio.NewReader(os.Stdin) line, _, _ := reader.ReadLine() cnt, err := conn.Write(line) if err != nil { panic(err) } s <- fmt.Sprintf("send: %v", string(line[:cnt])) }(send) go func(r chan string) { buf := make([]byte, 1024) cnt, err := conn.Read(buf) if err != nil { panic(err) } r <- fmt.Sprintf("recv: %v", string(buf[:cnt])) }(recv) select { case accept := <-recv: log.Println(accept) case to := <-send: log.Println(to) } } }
客戶端的方法和服務端如出一轍,同樣是使用for循環使程序可以接收和發送多次,使用管道和select實現控制台輸入和服務端消息的監聽.
結果展示:
server端: client端:
可以看到,服務啟動后顯示連接成功,服務端連續發送兩條消息,客戶端接收兩條消息成功;客戶端隨后發送兩條,服務端正常接收.此時類似微信單聊的簡單效果已經完成了.
現在我們回頭看上文中提到的handFunc中另一個goroutine還處於阻塞狀態的問題.如果你只用一個客戶端也發現不了什么問題,但如果同時開啟了多個客戶端會怎么樣呢?下邊我們測試一下:
我們同時啟動兩個client端並給server發消息,發現一切都正常,server端能正常接收兩個client發送的消息(通過conn.RemoteAddr()方法可以獲取到客戶端的ip和端口).但當服務端發送請求時,卻出現消息輪番發送給client1和client2的情況,這是因為在handFunc中,在服務端接收到消息時,另一個goroutine還在阻塞中,只有當server端在客戶端輸入內容並發送后,goroutine才會結束.也就是說,服務端的reader.ReadLine()一直處於阻塞狀態,如果client1先連接了server端,那么在server端的終端輸入內容時,會先回復client1,這樣就解除了client1在server端發生的寫入阻塞,接着client2發生的寫阻塞會在server的終端中等待,直到終端有消息寫入,這樣就出現client1和client2輪番接收數據的情況,具體解決辦法這里就不說了,歡迎大家提供建議.