用Go写个TCP服务器其实没那么难
最近在家折腾智能家居,想着能不能自己写个小工具,让手机和树莓派之间传点状态信息。试了HTTP,发现太重,每次都要握手,延迟有点高。后来想到用TCP直接通信,简单又稳定。正好最近在学Go,就顺手用它写了第一个TCP服务端和客户端。
先跑通一个最简单的例子
Go的net包把网络编程封装得很干净。下面这个例子启动一个TCP服务器,监听本地8888端口,只要有人连上来,就发一句“欢迎”:
package main
import (
"net"
"log"
)
func main() {
listener, err := net.Listen("tcp", ":8888")
if err != nil {
log.Fatal(err)
}
defer listener.Close()
log.Println("服务器已启动,监听 :8888")
for {
conn, err := listener.Accept()
if err != nil {
log.Print(err)
continue
}
go handleConnection(conn)
}
}
func handleConnection(conn net.Conn) {
defer conn.Close()
message := "欢迎来到我的小服务器!\n"
conn.Write([]byte(message))
}写个客户端试试看
光有服务端不够,还得有个客户端去连。写个简单的,连上去就读服务端发来的消息:
package main
import (
"net"
"io/ioutil"
"log"
)
func main() {
conn, err := net.Dial("tcp", "localhost:8888")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
response, err := ioutil.ReadAll(conn)
if err != nil {
log.Print(err)
}
log.Printf("收到:%s", response)
}升级成双向聊天模式
上面只能单向发消息,不够意思。改成双方都能说话的模式更实用。比如你家的温控设备可以主动上报温度,手机也能发指令让它调整风速。
关键是在连接处理里开两个goroutine,一个读一个写:
func handleChat(conn net.Conn) {
defer conn.Close()
go func() {
scanner := bufio.NewScanner(conn)
for scanner.Scan() {
log.Printf("收到消息:%s", scanner.Text())
}
}()
reader := bufio.NewReader(os.Stdin)
for {
fmt.Print("> ")
text, _ := reader.ReadString('\n')
conn.Write([]byte(text))
}
}当然,真实场景里不会让用户手动输入,而是自动读取传感器数据或解析控制命令。但原理是一样的——通过TCP连接持续交换字节流。
Go的并发模型特别适合这种一对多的连接管理。每个客户端来一条连接,go一下就扔后台跑着,不占主线程。写网络服务的时候,根本不用操心线程池或者回调地狱。
前几天我把这个小程序改了改,部署到家里的服务器上,现在用手机连过去就能看到阳台温湿度,还能远程开关加湿器。虽然功能简单,但全是自己一行行写出来的,用起来格外踏实。