在Go语言中编写网络程序时,我们将看不到传统的编码形式。以前我们使用Socket编程时,会按照如下步骤展开:
(1)建立Socket:使用socket()函数。
(2)绑定Socket:使用bind()函数。
(3)监听:使用listen()函数。或者连接:使用connect()函数。
(4)接受连接:使用accept()函数。
(5)接收:使用receive()函数。或者发送:使用send()函数。

Go语言标准库对此过程进行了抽象和封装。无论我们期望使用什么协议建立什么形式的连接,都只需要调用net.Dial()即可。

Dial()函数

Dial()函数的原型如下:
Go网络编程之Socket编程 - 图1
其中net参数是网络协议的名字,addr参数是IP地址或域名,而端口号以“:”的形式跟随在地址或域名的后面,端口号可选。如果连接成功,返回连接对象,否则返回error。

TCP链接:
Go网络编程之Socket编程 - 图2

UDP链接:
Go网络编程之Socket编程 - 图3

ICMP链接(使用协议名称):
Go网络编程之Socket编程 - 图4

ICMP链接(使用协议编号):
Go网络编程之Socket编程 - 图5

这里我们可以通过以下链接查看协议编号的含义: http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xml。

目前, Dial()函数支持如下几种网络协议: “tcp”、 “tcp4”(仅限IPv4)、 “tcp6”(仅限IPv6)、 “udp”、 “udp4”(仅限IPv4)、 “udp6”(仅限IPv6)、 “ip”、 “ip4”(仅限IPv4)和”ip6”(仅限IPv6)。

在成功建立连接后,我们就可以进行数据的发送和接收。发送数据时,使用conn的Write()成员方法,接收数据时使用Read()方法。

ICMP示例程序

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net"
  7. "os"
  8. )
  9. func main() {
  10. if len(os.Args) != 2 {
  11. fmt.Println("Usage: ", os.Args[0], "host")
  12. os.Exit(1)
  13. }
  14. service := os.Args[1]
  15. conn, err := net.Dial("ip4:icmp", service)
  16. checkError(err)
  17. var msg [512]byte
  18. msg[0] = 8 // echo
  19. msg[1] = 0 // code 0
  20. msg[2] = 0 // checksum
  21. msg[3] = 0 // checksum
  22. msg[4] = 0 // identifier[0]
  23. msg[5] = 13 //identifier[1]
  24. msg[6] = 0 // sequence[0]
  25. msg[7] = 37 // sequence[1]
  26. len := 8
  27. check := checkSum(msg[0:len])
  28. msg[2] = byte(check >> 8)
  29. msg[3] = byte(check & 255)
  30. _, err = conn.Write(msg[0:len])
  31. checkError(err)
  32. _, err = conn.Read(msg[0:])
  33. checkError(err)
  34. fmt.Println("Got response")
  35. if msg[5] == 13 {
  36. fmt.Println("Identifier matches")
  37. }
  38. if msg[7] == 37 {
  39. fmt.Println("Sequence matches")
  40. }
  41. os.Exit(0)
  42. }
  43. func checkSum(msg []byte) uint16 {
  44. sum := 0
  45. // 先假设为偶数
  46. for n := 1; n < len(msg)-1; n += 2 {
  47. sum += int(msg[n])*256 + int(msg[n+1])
  48. }
  49. sum = (sum >> 16) + (sum & 0xffff)
  50. sum += (sum >> 16)
  51. var answer uint16 = uint16(^sum)
  52. return answer
  53. }
  54. func checkError(err error) {
  55. if err != nil {
  56. fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
  57. os.Exit(1)
  58. }
  59. }
  60. func readFully(conn net.Conn) ([]byte, error) {
  61. defer conn.Close()
  62. result := bytes.NewBuffer(nil)
  63. var buf [512]byte
  64. for {
  65. n, err := conn.Read(buf[0:])
  66. result.Write(buf[0:n])
  67. if err != nil {
  68. if err == io.EOF {
  69. break
  70. }
  71. return nil, err
  72. }
  73. }
  74. return result.Bytes(), nil
  75. }

执行结果如下:
Go网络编程之Socket编程 - 图6

TCP示例程序

  1. package main
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net"
  7. "os"
  8. )
  9. func main() {
  10. if len(os.Args) != 2 {
  11. fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
  12. os.Exit(1)
  13. }
  14. service := os.Args[1]
  15. conn, err := net.Dial("tcp", service)
  16. checkError(err)
  17. _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
  18. checkError(err)
  19. result, err := readFully(conn)
  20. checkError(err)
  21. fmt.Println(string(result))
  22. os.Exit(0)
  23. }
  24. func checkError(err error) {
  25. if err != nil {
  26. fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
  27. os.Exit(1)
  28. }
  29. }
  30. func readFully(conn net.Conn) ([]byte, error) {
  31. defer conn.Close()
  32. result := bytes.NewBuffer(nil)
  33. var buf [512]byte
  34. for {
  35. n, err := conn.Read(buf[0:])
  36. result.Write(buf[0:n])
  37. if err != nil {
  38. if err == io.EOF {
  39. break
  40. }
  41. return nil, err
  42. }
  43. }
  44. return result.Bytes(), nil
  45. }

执行这段程序并查看执行结果:
Go网络编程之Socket编程 - 图7

更丰富的网络通信

实际上, Dial()函数是对DialTCP()、 DialUDP()、 DialIP()和DialUnix()的封装。我们也可以直接调用这些函数,它们的功能是一致的。这些函数的原型如下:
Go网络编程之Socket编程 - 图8


Go网络编程之Socket编程 - 图9