1. 程式人生 > 其它 >go socket實現靈活傳送接收訊息

go socket實現靈活傳送接收訊息

使用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輪番接收資料的情況,具體解決辦法這裡就不說了,歡迎大家提供建議.