利用golang通道優化TCP Socket伺服器
阿新 • • 發佈:2018-11-05
前面的幾篇文章分別介紹了UDP和TCP進行Socket程式設計的方法,在TCP的文章中,我們除了傳統的阻塞型伺服器,還給出了多執行緒伺服器的實現方式。今天我們利用golang的通道,給出一種更加高效的伺服器設計。
package main import ( "fmt" "net" "os" "strconv" "strings" "go-study/socket/config" ) func main() { address := config.SERVER_IP + ":" + strconv.Itoa(config.SERVER_PORT) listener, err := net.Listen("tcp", address) if err != nil { fmt.Println(err) os.Exit(1) } defer listener.Close() for { conn, err := listener.Accept() if err != nil { fmt.Println(err) continue } go handleConn(conn) } }
main函式的部分和之前的TCP伺服器基本相同,目前還看不出什麼差別,主要是執行Listen和Accept,當有客戶端連線時,將連線交給handleConn協程處理。大家或許猜到了,差別就在handleConn上面。
func handleConn(conn net.Conn) { defer conn.Close() readChan := make(chan string) writeChan := make(chan string) stopChan := make(chan bool) go readConn(conn, readChan, stopChan) go writeConn(conn, writeChan, stopChan) for { select { case readStr := <-readChan: upper := strings.ToUpper(readStr) writeChan <- upper case stop := <-stopChan: if stop { break } } } }
handleConn函式中建立了三個通道,分別為讀,寫以及停止通道,之後啟動了兩個協程,分別進行讀操作和寫操作,兩個操作之間的聯絡我們就是利用上面的三個通道構造的。下面在for中我們使用select語句,select語句可以根據通道的狀態選擇性地執行不同的語句,上面的程式碼中,當readChan可讀時,會將字串轉為大寫,之後傳送給writeChan,當接收到stopChan的停止指令時,會跳出select,從而handleConn協程就會結束。
func readConn(conn net.Conn, readChan chan<- string, stopChan chan<- bool) { for { data := make([]byte, config.SERVER_RECV_LEN) _, err := conn.Read(data) if err != nil { fmt.Println(err) break } strData := string(data) fmt.Println("Received:", strData) readChan <- strData } stopChan <- true } func writeConn(conn net.Conn, writeChan <-chan string, stopChan chan<- bool) { for { strData := <-writeChan _, err := conn.Write([]byte(strData)) if err != nil { fmt.Println(err) break } fmt.Println("Send:", strData) } stopChan <- true }
readConn和writeConn就是把之前的讀寫操作分為了兩個協程,readConn協程會阻塞在Read函式,直到網路上有資料可讀,通過readChan傳送讀到的字串,writeConn協程會阻塞在讀取writeChan,直到在handleConn中向writeChan寫入資料。
通過使用通道和select,golang構造出了很好的非同步io機制,我們不需要等待任何的讀寫操作,只是在需要執行的時候,程式才會執行。避免了不必要的CPU消耗。