Socket

    使用 net 的 函数就可轻松建立一个 Socket 连接。Socket 创建成功后,我们可以对其进行 I/O 操作,最后不要记得对其进行关闭操作。

    本章将从 TCP, UDP, Unix 入手,带领大家全面了解 Socket 在 Go 中的应用。

    Socket 连接又分为客户端和服务端,如图:

    核心步骤包括:

    • 创建连接:

    注意, 这里的 network 可以为:

    1. "tcp", "tcp4", "tcp6"
    2. "udp", "udp4", "udp6"
    3. "ip", "ip4", "ip6"
    4. "unix", "unixgram", "unixpacket"
    • 通过连接发送数据:
    1. conn.Write([]byte("GET / HTTP/1.0\r\n\r\n"))
    • 通过连接读取数据:
    1. buf := make([]byte, 256)
    2. conn.Read(buf)
    • 关闭连接:
    1. conn.Close()
    1. package main
    2. import (
    3. "fmt"
    4. "io/ioutil"
    5. "log"
    6. "net"
    7. )
    8. func main() {
    9. // 尝试与 google.com:80 建立 tcp 连接
    10. conn, err := net.Dial("tcp", "google.com:80")
    11. if err != nil {
    12. log.Fatal(err)
    13. }
    14. defer conn.Close() // 退出关闭连接
    15. // 通过连接发送 GET 请求,访问首页
    16. _, err = fmt.Fprintf(conn, "GET / HTTP/1.0\r\n\r\n")
    17. if err != nil {
    18. log.Fatal(err)
    19. }
    20. dat, err := ioutil.ReadAll(conn)
    21. if err != nil {
    22. log.Fatal(err)
    23. }
    24. fmt.Println(string(dat))
    25. }
    1. HTTP/1.0 200 OK
    2. Date: Tue, 05 Jun 2018 14:45:30 GMT
    3. Expires: -1
    4. Cache-Control: private, max-age=0
    5. Content-Type: text/html; charset=ISO-8859-1
    6. P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
    7. Server: gws
    8. X-XSS-Protection: 1; mode=block
    9. X-Frame-Options: SAMEORIGIN
    10. Set-Cookie: 1P_JAR=2018-06-05-14; expires=Thu, 05-Jul-2018 14:45:30 GMT; path=/; domain=.google.com
    11. Set-Cookie: NID=131=mqkJocXSsDCD6zdcMyc12DCUqt3X19HIoS0HGTsAzsiuvFx56rBsliga5Uj22QyA8p2IZ6E7lkMGzchqam0RQ58PT6WV5Csllv80MO0uauY9P-FvzCLdYYY9tT0KYtVv; expires=Wed, 05-Dec-2018 14:45:30 GMT; path=/; domain=.google.com; HttpOnly
    12. Accept-Ranges: none
    13. Vary: Accept-Encoding
    14. ....

    说明:google.com 网站后端是一个 HTTP server, 因为 HTTP 建立在 TCP 协议基础上,所以我们这里可以使用 TCP 协议来进行访问。

    在这个例子中,我们先使用 net 包创建一个 TCP Server ,然后尝试连接 Server, 最后再通过客户端发送 hello 到 Server,同时 Server 响应 word

    我们来看完整例子:

    • server/main.go
    1. package main
    2. import (
    3. "bufio"
    4. "fmt"
    5. "log"
    6. "net"
    7. )
    8. func main() {
    9. l, err := net.Listen("tcp", "127.0.0.1:8888")
    10. if err != nil {
    11. log.Fatal(err)
    12. }
    13. log.Printf("Start server with: %s", l.Addr())
    14. defer l.Close()
    15. for {
    16. conn, err := l.Accept()
    17. if err != nil {
    18. log.Fatal(err)
    19. }
    20. go handleConnection(conn)
    21. }
    22. }
    23. func handleConnection(conn net.Conn) {
    24. reader := bufio.NewReader(conn)
    25. for {
    26. dat, _, err := reader.ReadLine()
    27. if err != nil {
    28. log.Println(err)
    29. return
    30. }
    31. fmt.Println("client:", string(dat))
    32. _, err = conn.Write([]byte("word\n"))
    33. if err != nil {
    34. log.Println(err)
    35. return
    36. }
    37. }
    38. }

    注意:

    • client/main.go
    1. package main
    2. import (
    3. "bufio"
    4. "fmt"
    5. "time"
    6. )
    7. func main() {
    8. conn, err := net.Dial("tcp", "127.0.0.1:8888")
    9. if err != nil {
    10. log.Fatal(err)
    11. }
    12. defer conn.Close()
    13. reader := bufio.NewReader(conn)
    14. for {
    15. _, err := conn.Write([]byte("hello\n"))
    16. if err != nil {
    17. log.Fatal(err)
    18. }
    19. dat, _, err := reader.ReadLine()
    20. if err != nil {
    21. log.Fatal(err)
    22. }
    23. fmt.Println("sever:", string(dat))
    24. time.Sleep(5 * time.Second)
    25. }
    26. }

    注意:

    1. 1. 通过 `net.DialTCP("tcp", nil, addr)` 尝试创建到 TCP Sever 的连接。
    2. 2. 通过 `conn.Write([]byte("hello\n"))` 向服务端发送数据。
    3. 3. 通过 `reader.ReadLine()` 读取服务端响应数据。

    当我们运行代码的时候,可以在终端看到如下输入:

    1. go run server/main.go
    2. 2018/06/08 08:12:23 Start server with: 127.0.0.1:8888
    3. client: hello
    4. client: hello
    1. go run client/main.go
    2. 2018/06/08 08:12:23 Start server with: 127.0.0.1:8888
    3. sever: word
    4. sever: word

    UDP 相较于 TCP 简单的多,它具有以下特点:

    • 无连接的
    • 要求系统资源较少
    • UDP 程序结构较简单
    • 基于数据报模式(UDP)
    • UDP 可能丢包
    • UDP 不保证数据顺序性
    • server/main.go
    1. package main
    2. import (
    3. "log"
    4. "net"
    5. "time"
    6. )
    7. func main() {
    8. // listen to incoming udp packets
    9. pc, err := net.ListenPacket("udp", "127.0.0.1:8888")
    10. if err != nil {
    11. log.Fatal(err)
    12. }
    13. log.Printf("Start server with: %s", pc.LocalAddr())
    14. defer pc.Close()
    15. clients := make([]net.Addr, 0)
    16. go func() {
    17. for {
    18. for _, addr := range clients {
    19. _, err := pc.WriteTo([]byte("pong\n"), addr)
    20. if err != nil {
    21. log.Println(err)
    22. }
    23. }
    24. time.Sleep(5 * time.Second)
    25. }
    26. }()
    27. for {
    28. buf := make([]byte, 256)
    29. n, addr, err := pc.ReadFrom(buf)
    30. if err != nil {
    31. log.Println(err)
    32. continue
    33. }
    34. clients = append(clients, addr)
    35. log.Println(string(buf[0:n]))
    36. log.Println(addr.String(), "connecting...", len(clients), "connected")
    37. }
    38. }

    注意:

    1. 1. 监听本地 UDP `127.0.0.1:8888`
    2. 2. 使用 `pc.ReadFrom(buf)` 方法读取客户端发送的消息。
    3. 3. 使用 `clients` 来保存所有连上的客户端连接。
    4. 4. 通过 `pc.WriteTo([]byte("pong\n"), addr)` 向所有客户端发送消息。
    • client/main.go
    1. package main
    2. import (
    3. "bufio"
    4. "log"
    5. "net"
    6. )
    7. func main() {
    8. conn, err := net.Dial("udp", "127.0.0.1:8888")
    9. if err != nil {
    10. log.Fatal(err)
    11. }
    12. defer conn.Close()
    13. if err != nil {
    14. log.Fatal(err)
    15. reader := bufio.NewReader(conn)
    16. for {
    17. dat, _, err := reader.ReadLine()
    18. if err != nil {
    19. log.Fatal(err)
    20. }
    21. log.Println(string(dat))
    22. }
    23. }

    当我运行代码可以得到如下输出:

    1. # 启动 client1
    2. go run client/main.go
    3. # 输出
    4. 2018/06/08 14:36:18 pong
    5. 2018/06/08 14:36:23 pong
    1. # 启动 client2
    2. go run client/main.go
    3. # 输出
    4. 2018/06/08 14:37:58 pong
    5. 2018/06/08 14:38:03 pong

    Unix 和 TCP 很相似,只不过监听的地址是一个 Socket 文件,例如:

    1. l, err := net.Listen("unix", "/tmp/echo.sock")

    下面我们就通过一个实际的例子来练习:

    • server/main.go
    1. package main
    2. import (
    3. "log"
    4. "net"
    5. )
    6. func main() {
    7. l, err := net.Listen("unix", "/tmp/unix.sock")
    8. if err != nil {
    9. log.Fatal("listen error:", err)
    10. }
    11. for {
    12. conn, err := l.Accept()
    13. if err != nil {
    14. log.Fatal("accept error:", err)
    15. }
    16. go helloServer(conn)
    17. }
    18. }
    19. func helloServer(c net.Conn) {
    20. for {
    21. buf := make([]byte, 512)
    22. nr, err := c.Read(buf)
    23. if err != nil {
    24. return
    25. }
    26. data := buf[0:nr]
    27. log.Println(string(data))
    28. _, err = c.Write([]byte("hello"))
    29. if err != nil {
    30. log.Fatal("Write: ", err)
    31. }
    32. }
    33. }

    说明:

    1. - 使用 `net.Listen("unix", "/tmp/unix.sock")` 启动一个 Server
    2. - 使用 `conn, err := l.Accept()` 来接受客户端的连接。
    3. - 使用 `go helloServer(conn)` 来处理客户端连接,并读取客户端发送的数据 `hi` 并返回 `hello`
    • client/main.go
    1. package main
    2. import (
    3. "io"
    4. "log"
    5. "net"
    6. "time"
    7. )
    8. func reader(r io.Reader) {
    9. buf := make([]byte, 512)
    10. for {
    11. n, err := r.Read(buf[:])
    12. if err != nil {
    13. return
    14. }
    15. log.Println(string(buf[0:n]))
    16. }
    17. }
    18. func main() {
    19. c, err := net.Dial("unix", "/tmp/unix.sock")
    20. if err != nil {
    21. log.Fatal(err)
    22. }
    23. defer c.Close()
    24. go reader(c)
    25. for {
    26. _, err := c.Write([]byte("hi"))
    27. if err != nil {
    28. log.Fatal("write error:", err)
    29. break
    30. }
    31. time.Sleep(3 * time.Second)
    32. }
    33. }

    注意:

    1. - 使用 `c, err := net.Dial("unix", "/tmp/unix.sock")` 来连接服务端。
    2. - 使用 `c.Write([]byte("hi"))` 向服务端发送 `hi` 消息。
    3. - 使用 `r.Read(buf)` 读取客户端发送的消息。

    当运行代码可以得到如下输出:

    1. # go run client/main.go
    2. 2018/06/09 20:41:47 hello
    3. 2018/06/09 20:41:50 hello