前面的几篇文章分别介绍了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消耗。