Socket是网络编程的一个抽象概念,通常我们用一个 Socket 表示 “打开了一个网络连接”,在 Go 中主要使用 net 包。
使用 net 的 func Dial(network, address string) (Conn, error) 函数就可轻松建立一个 Socket 连接。Socket 创建成功后,我们可以对其进行 I/O 操作,最后不要记得对其进行关闭操作。
func Dial(network, address string) (Conn, error)
本章将从 TCP, UDP, Unix 入手,带领大家全面了解 Socket 在 Go 中的应用。
Socket 连接又分为客户端和服务端,如图:
核心步骤包括:
Dial(network, address string) (Conn, error)
注意, 这里的 network 可以为:
"tcp", "tcp4", "tcp6" "udp", "udp4", "udp6" "ip", "ip4", "ip6" "unix", "unixgram", "unixpacket"
conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
buf := make([]byte, 256) conn.Read(buf)
conn.Close()
注意: conn 是一个 IO 对象,我们主要使用 IO 相关的帮助方法来进行读写操作。
conn
package main import ( "fmt" "io/ioutil" "log" "net" ) func main() { // 尝试与 google.com:80 建立 tcp 连接 conn, err := net.Dial("tcp", "google.com:80") if err != nil { log.Fatal(err) } defer conn.Close() // 退出关闭连接 // 通过连接发送 GET 请求,访问首页 _, err = fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n") if err != nil { log.Fatal(err) } dat, err := ioutil.ReadAll(conn) if err != nil { log.Fatal(err) } fmt.Println(string(dat)) }
当运行代码,可以得到 google.com 的首页内容,如下:
google.com
HTTP/1.0 200 OK Date: Tue, 05 Jun 2018 14:45:30 GMT Expires: -1 Cache-Control: private, max-age=0 Content-Type: text/html; charset=ISO-8859-1 P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info." Server: gws X-XSS-Protection: 1; mode=block X-Frame-Options: SAMEORIGIN Set-Cookie: 1P_JAR=2018-06-05-14; expires=Thu, 05-Jul-2018 14:45:30 GMT; path=/; domain=.google.com Set-Cookie: NID=131=mqkJocXSsDCD6zdcMyc12DCUqt3X19HIoS0HGTsAzsiuvFx56rBsliga5Uj22QyA8p2IZ6E7lkMGzchqam0RQ58PT6WV5Csllv80MO0uauY9P-FvzCLdYYY9tT0KYtVv; expires=Wed, 05-Dec-2018 14:45:30 GMT; path=/; domain=.google.com; HttpOnly Accept-Ranges: none Vary: Accept-Encoding ....
说明:google.com 网站后端是一个 HTTP server, 因为 HTTP 建立在 TCP 协议基础上,所以我们这里可以使用 TCP 协议来进行访问。
在这个例子中,我们先使用 net 包创建一个 TCP Server ,然后尝试连接 Server, 最后再通过客户端发送 hello 到 Server,同时 Server 响应 word。
hello
word
我们来看完整例子:
package main import ( "bufio" "fmt" "log" "net" ) func main() { l, err := net.Listen("tcp", "127.0.0.1:8888") if err != nil { log.Fatal(err) } log.Printf("Start server with: %s", l.Addr()) defer l.Close() for { conn, err := l.Accept() if err != nil { log.Fatal(err) } go handleConnection(conn) } } func handleConnection(conn net.Conn) { reader := bufio.NewReader(conn) for { dat, _, err := reader.ReadLine() if err != nil { log.Println(err) return } fmt.Println("client:", string(dat)) _, err = conn.Write([]byte("word\n")) if err != nil { log.Println(err) return } } }
注意:
net.Listen("tcp", "127.0.0.1:8888")
l.Accept()
go handleConnection(c)
package main import ( "bufio" "fmt" "log" "net" "time" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8888") if err != nil { log.Fatal(err) } defer conn.Close() reader := bufio.NewReader(conn) for { _, err := conn.Write([]byte("hello\n")) if err != nil { log.Fatal(err) } dat, _, err := reader.ReadLine() if err != nil { log.Fatal(err) } fmt.Println("sever:", string(dat)) time.Sleep(5 * time.Second) } }
net.DialTCP("tcp", nil, addr)
conn.Write([]byte("hello\n"))
reader.ReadLine()
当我们运行代码的时候,可以在终端看到如下输入:
go run server/main.go 2018/06/08 08:12:23 Start server with: 127.0.0.1:8888 client: hello client: hello
go run client/main.go 2018/06/08 08:12:23 Start server with: 127.0.0.1:8888 sever: word sever: word
UDP 相较于 TCP 简单的多,它具有以下特点:
下面我们通过一个统计服务在线人数的例子来了解它:
package main import ( "log" "net" "time" ) func main() { // listen to incoming udp packets pc, err := net.ListenPacket("udp", "127.0.0.1:8888") if err != nil { log.Fatal(err) } log.Printf("Start server with: %s", pc.LocalAddr()) defer pc.Close() clients := make([]net.Addr, 0) go func() { for { for _, addr := range clients { _, err := pc.WriteTo([]byte("pong\n"), addr) if err != nil { log.Println(err) } } time.Sleep(5 * time.Second) } }() for { buf := make([]byte, 256) n, addr, err := pc.ReadFrom(buf) if err != nil { log.Println(err) continue } clients = append(clients, addr) log.Println(string(buf[0:n])) log.Println(addr.String(), "connecting...", len(clients), "connected") } }
127.0.0.1:8888
pc.ReadFrom(buf)
clients
pc.WriteTo([]byte("pong\n"), addr)
package main import ( "bufio" "log" "net" ) func main() { conn, err := net.Dial("udp", "127.0.0.1:8888") if err != nil { log.Fatal(err) } defer conn.Close() _, err = conn.Write([]byte("ping...")) if err != nil { log.Fatal(err) } reader := bufio.NewReader(conn) for { dat, _, err := reader.ReadLine() if err != nil { log.Fatal(err) } log.Println(string(dat)) } }
当我运行代码可以得到如下输出:
# 执行命令 go run server/main.go # 输出 2018/06/08 14:36:13 Start server with: 127.0.0.1:8888 2018/06/08 14:36:15 ping... 2018/06/08 14:36:15 127.0.0.1:61790 connecting... 1 connected 2018/06/08 14:36:18 ping... 2018/06/08 14:36:18 127.0.0.1:59989 connecting... 2 connected
# 启动 client1 go run client/main.go # 输出 2018/06/08 14:36:18 pong 2018/06/08 14:36:23 pong
# 启动 client2 go run client/main.go # 输出 2018/06/08 14:37:58 pong 2018/06/08 14:38:03 pong
Unix 和 TCP 很相似,只不过监听的地址是一个 Socket 文件,例如:
l, err := net.Listen("unix", "/tmp/echo.sock")
下面我们就通过一个实际的例子来练习:
package main import ( "log" "net" ) func main() { l, err := net.Listen("unix", "/tmp/unix.sock") if err != nil { log.Fatal("listen error:", err) } for { conn, err := l.Accept() if err != nil { log.Fatal("accept error:", err) } go helloServer(conn) } } func helloServer(c net.Conn) { for { buf := make([]byte, 512) nr, err := c.Read(buf) if err != nil { return } data := buf[0:nr] log.Println(string(data)) _, err = c.Write([]byte("hello")) if err != nil { log.Fatal("Write: ", err) } } }
说明:
使用 net.Listen("unix", "/tmp/unix.sock") 启动一个 Server。
net.Listen("unix", "/tmp/unix.sock")
使用 conn, err := l.Accept() 来接受客户端的连接。
conn, err := l.Accept()
使用 go helloServer(conn) 来处理客户端连接,并读取客户端发送的数据 hi 并返回 hello。
go helloServer(conn)
hi
client/main.go
package main import ( "io" "log" "net" "time" ) func reader(r io.Reader) { buf := make([]byte, 512) for { n, err := r.Read(buf[:]) if err != nil { return } log.Println(string(buf[0:n])) } } func main() { c, err := net.Dial("unix", "/tmp/unix.sock") if err != nil { log.Fatal(err) } defer c.Close() go reader(c) for { _, err := c.Write([]byte("hi")) if err != nil { log.Fatal("write error:", err) break } time.Sleep(3 * time.Second) } }
c, err := net.Dial("unix", "/tmp/unix.sock")
c.Write([]byte("hi"))
r.Read(buf)
当运行代码可以得到如下输出:
# go run server/main.go 2018/06/09 20:42:14 hi 2018/06/09 20:42:16 hi 2018/06/09 20:42:17 hi
# go run client/main.go 2018/06/09 20:41:47 hello 2018/06/09 20:41:50 hello 2018/06/09 20:41:53 hello
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8