TCP

一、 服务端

1. 解析地址

在TCP服务端我们需要监听一个TCP地址,因此建立服务端前我们需要生成一个正确的TCP地址,这就需要用到下面的函数了

  1. // ResolveTCPAddr函数会输出一个TCP连接地址和一个错误信息
  2. func ResolveTCPAddr(network, address string) (*TCPAddr, error)
  3. // 解析IP地址
  4. func ResolveIPAddr(net, addr string) (*IPAddr, error)
  5. // 解析UDP地址
  6. func ResolveUDPAddr(net, addr string) (*UDPAddr, error)
  7. // 解析Unix地址
  8. func ResolveUnixAddr(net, addr string) (*UnixAddr, error)

2. 监听请求

我们可以通过 Listen方法监听我们解析后的网络地址。

  1. // 监听net类型,地址为laddr的地址
  2. func Listen(net, laddr string) (Listener, error)
  3. // 监听TCP地址
  4. func ListenTCP(network string, laddr *TCPAddr) (*TCPListener, error)
  5. // 监听IP地址
  6. func ListenIP(netProto string, laddr *IPAddr) (*IPConn, error)
  7. // 监听UDP地址
  8. func ListenMulticastUDP(net string, ifi *Interface, gaddr *UDPAddr) (*UDPConn, error)
  9. func ListenUDP(net string, laddr *UDPAddr) (*UDPConn, error)
  10. // 监听Unix地址
  11. func ListenUnixgram(net string, laddr *UnixAddr) (*UnixConn, error)
  12. func ListenUnix(net string, laddr *UnixAddr) (*UnixListener, error)

3. 接收请求

TCPAddr 实现了两个接受请求的方法,两者代码实现其实是一样的,唯一的区别是第一种返回了一个对象,第二种返回了一个接口。

  1. func (l *TCPListener) AcceptTCP() (*TCPConn, error)
  2. func (l *TCPListener) Accept() (Conn, error)

其他类型也有类似的方法,具体请参考go语言标准库文档。

4. 连接配置

配置监听器超时时间

  1. // 超过t之后监听器自动关闭,0表示不设置超时时间
  2. func (l *TCPListener) SetDeadline(t time.Time) error

关闭监听器

  1. // 关闭监听器
  2. func (l *TCPListener) Close() error

二、 TCP客户端

1. 解析TCP地址

在TCP服务端我们需要监听一个TCP地址,因此建立服务端前我们需要生成一个正确的TCP地址,这就需要用到下面的函数了。

  1. // ResolveTCPAddr函数会输出一个TCP连接地址和一个错误信息
  2. func ResolveTCPAddr(network, address string) (*TCPAddr, error)

2. 发送连接请求

net包提供了多种连接方法

  1. // DialIP的作用类似于IP网络的拨号
  2. func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error)
  3. // Dial 连接到指定网络上的地址,涵盖
  4. func Dial(network, address string) (Conn, error)
  5. // 这个方法只是在Dial上面设置了超时时间
  6. func DialTimeout(network, address string, timeout time.Duration) (Conn, error)
  7. // DialTCP 专门用来进行TCP通信的
  8. func DialTCP(network string, laddr, raddr *TCPAddr) (*TCPConn, error)
  9. // DialUDP 专门用来进行UDP通信的
  10. func DialUDP(network string, laddr, raddr *UDPAddr) (*UDPConn, error)
  11. // DialUnix 专门用来进行 Unix 通信
  12. func DialUnix(network string, laddr, raddr *UnixAddr) (*UnixConn, error)

编写服务端与客户端

server.go

  1. package main
  2. import (
  3. "log"
  4. "net"
  5. )
  6. func main() {
  7. // 解析服务端监听地址
  8. addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8000")
  9. if err != nil {
  10. log.Panic(err)
  11. }
  12. // 创建监听器
  13. listen, err := net.ListenTCP("tcp", addr)
  14. if err != nil {
  15. log.Panic(err)
  16. }
  17. for {
  18. // 监听客户端连接请求
  19. conn, err := listen.AcceptTCP()
  20. if err != nil {
  21. continue
  22. }
  23. // 处理客户端请求
  24. go handleConnectionForServer(conn)
  25. }
  26. }
  27. // handleConnection 读取数据, 在这里我们可以编写自己的交互程序
  28. func handleConnectionForServer(conn net.Conn) {
  29. for flag := false; ; {
  30. // 设置消息长度为1024比特
  31. buf := make([]byte, 1024)
  32. if !flag {
  33. // 客户端连接成功,提示可以操作的内容
  34. if _, err := conn.Write([]byte(Usage())); err != nil {
  35. log.Println("Error: ", err)
  36. }
  37. flag = true
  38. continue
  39. }
  40. /* 读取客户端发送的数据,数据会保存到buf
  41. 这里有一个知识点:
  42. conn.Read会返回接收到的值的长度,如果不指定长度,通过string转换的时候你会活得一个1024字节的字符串
  43. 但我们不需要后面的初始化的值,因此通过buf[:length]提取我们想要的值。
  44. */
  45. if length, err := conn.Read(buf); err != nil {
  46. // 读取失败
  47. writeResponse(parseRequest(""), conn)
  48. } else {
  49. // 读取成功
  50. req := string(buf[:length])
  51. if req == "exit" {
  52. break
  53. }
  54. writeResponse(parseRequest(req), conn)
  55. }
  56. }
  57. }
  58. func Usage() string {
  59. return `
  60. ---------------------------------------------------------------
  61. Hello, my name is randow_w, I'm glad to serve you.
  62. I can provide you with the following services:
  63. 1.查工资
  64. 2.猜年龄
  65. 3.查天气
  66. ----------------------------------------------------------------`
  67. }
  68. // writeResponse 返回信息给客户端
  69. func writeResponse(resp string, conn net.Conn) {
  70. if _, err := conn.Write([]byte(resp)); err != nil {
  71. log.Println("Error: ", err)
  72. }
  73. }
  74. // parseRequest 解析客户端输入的信息
  75. func parseRequest(req string) (resp string) {
  76. switch req {
  77. case "查工资":
  78. resp = checkSalary()
  79. case "猜年龄":
  80. resp = guessAge()
  81. case "查天气":
  82. resp = chat()
  83. default:
  84. resp = "对不起,我爸爸还没有教我怎么回答你,能不能换一个问题(*^_^*)"
  85. }
  86. return
  87. }
  88. // 查工资
  89. func checkSalary() string {
  90. return "据权威机构推测,你未来有机会冲刺福布斯排行榜,加油哦(ง •_•)ง"
  91. }
  92. // 猜年龄
  93. func guessAge() string {
  94. return "永远18岁"
  95. }
  96. // 聊天
  97. func chat() string {
  98. return "你好,主人,今天是晴天,空气质量优,适合去爬山。"
  99. }

TCP 客户端:

  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. "os"
  7. "os/signal"
  8. "strings"
  9. "syscall"
  10. )
  11. var sig = make(chan os.Signal)
  12. func main() {
  13. // 解析服务端地址
  14. RemoteAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8000")
  15. if err != nil {
  16. panic(err)
  17. }
  18. // 解析本地连接地址
  19. LocalAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:8001")
  20. if err != nil {
  21. panic(err)
  22. }
  23. // 连接服务端
  24. conn, err := net.DialTCP("tcp", LocalAddr, RemoteAddr)
  25. if err != nil {
  26. panic(err)
  27. }
  28. // 连接管理
  29. HandleConnectionForClient(conn)
  30. }
  31. // handleConnection 读取数据, 在这里我们可以编写自己的交互程序
  32. func HandleConnectionForClient(conn net.Conn) {
  33. // 监控系统信号
  34. go signalMonitor(conn)
  35. // 初始化一个缓存区
  36. Stdin := bufio.NewReader(os.Stdin)
  37. for {
  38. // 接收服务端返回的消息
  39. getResponse(conn)
  40. // 读取用户输入的信息,遇到换行符结束。
  41. fmt.Print("[ random_w ]# ")
  42. input, err := Stdin.ReadString('\n')
  43. if err != nil {
  44. fmt.Println(err)
  45. }
  46. // 删除字符串前后的空格,主要是删除换行符。
  47. input = strings.TrimSpace(input)
  48. // 空行不做处理
  49. if len(input) == 0 {
  50. continue
  51. }
  52. // 是否接收到退出指令
  53. switch input {
  54. case "quit", "exit":
  55. sig <- syscall.SIGQUIT
  56. default:
  57. // 发送消息给服务端
  58. sendMsgToServer(conn, input)
  59. }
  60. }
  61. }
  62. // sendMsgToServer 发送消息给服务端
  63. func sendMsgToServer(conn net.Conn, msg string) {
  64. for {
  65. _, err := conn.Write([]byte(msg))
  66. if err == nil {
  67. break
  68. }
  69. }
  70. }
  71. // getResponse 接收服务端返回的消息
  72. func getResponse(conn net.Conn) {
  73. // 初始化一个1024字节的内存,用来接收服务端的消息
  74. respByte := make([]byte, 1024)
  75. // 接收服务端返回的消息
  76. length, err := conn.Read(respByte)
  77. if err != nil {
  78. fmt.Println("[ server ]# 接收消息失败")
  79. }
  80. for line, str := range strings.Split(string(respByte[:length]), "\n") {
  81. if len(str) != 0 {
  82. if line == 1 {
  83. fmt.Print(fmt.Sprintf("[ server ]# \n%s\n", str))
  84. continue
  85. }
  86. fmt.Println(str)
  87. }
  88. }
  89. }
  90. // signalMonitor 监听系统信号,如果程序收到退出到的信号通过 Goroutine 通知 server 端,关闭连接后退出。
  91. func signalMonitor(conn net.Conn) {
  92. signal.Notify(sig, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGINT)
  93. // 接收到结束信号退出此程序
  94. select {
  95. case <-sig:
  96. // 通知服务端断开连接
  97. _, _ = conn.Write([]byte("exit"))
  98. fmt.Println("\nGood Bye !!!!!")
  99. os.Exit(0)
  100. }
  101. }
  1. go run server.go
  2. go run client.go
  3. [ server ]#
  4. ---------------------------------------------------------------
  5. Hello, my name is randow_w, I'm glad to serve you.
  6. I can provide you with the following services:
  7. 1.查工资
  8. 2.猜年龄
  9. 3.查天气
  10. ----------------------------------------------------------------
  11. [ random_w ]# 查工资
  12. 据权威机构推测,你未来有机会冲刺福布斯排行榜,加油哦(ง •_•)ง
  13. [ random_w ]# 猜年龄
  14. 永远18岁
  15. [ random_w ]# 查天气
  16. 你好,主人,今天是晴天,空气质量优,适合去爬山。
  17. [ random_w ]# 你好
  18. 对不起,我还在爸爸没有教我怎么回答你,能不能换一个问题(*^_^*)
  19. [ random_w ]# quit
  20. Good Bye !!!!!

UDP

  1. package main
  2. import (
  3. "fmt"
  4. "log"
  5. "net"
  6. )
  7. func main() {
  8. // 解析服务端监听地址
  9. addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8000")
  10. if err != nil {
  11. log.Panic(err)
  12. }
  13. // 创建监听器
  14. listen, err := net.ListenUDP("udp", addr)
  15. if err != nil {
  16. log.Panic(err)
  17. }
  18. for {
  19. // 设置消息长度为1024比特
  20. buf := make([]byte, 1024)
  21. // 读取消息,UDP不是面向连接的因此不需要等待连接
  22. length, udpAddr, err := listen.ReadFromUDP(buf)
  23. if err != nil {
  24. log.Println("Error: ", err)
  25. continue
  26. }
  27. fmt.Println("[ server ]# UdpAddr: ", udpAddr, "Data: ", string(buf[:length]))
  28. }
  29. }
  1. package main
  2. import (
  3. "bufio"
  4. "fmt"
  5. "net"
  6. "os"
  7. "os/signal"
  8. "strings"
  9. "syscall"
  10. )
  11. var sig = make(chan os.Signal)
  12. func main() {
  13. // 解析服务端地址
  14. RemoteAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8000")
  15. if err != nil {
  16. panic(err)
  17. }
  18. // 解析本地连接地址
  19. LocalAddr, err := net.ResolveUDPAddr("udp", "127.0.0.1:8001")
  20. if err != nil {
  21. panic(err)
  22. }
  23. // 连接服务端
  24. conn, err := net.DialUDP("udp", LocalAddr, RemoteAddr)
  25. if err != nil {
  26. panic(err)
  27. }
  28. // 连接管理
  29. HandleConnectionForClient(conn)
  30. }
  31. // handleConnection 读取数据, 在这里我们可以编写自己的交互程序
  32. func HandleConnectionForClient(conn net.Conn) {
  33. // 监控系统信号
  34. go signalMonitor(conn)
  35. // 初始化一个缓存区
  36. Stdin := bufio.NewReader(os.Stdin)
  37. for {
  38. // 读取用户输入的信息,遇到换行符结束。
  39. fmt.Print("[ random_w ]# ")
  40. input, err := Stdin.ReadString('\n')
  41. if err != nil {
  42. fmt.Println(err)
  43. }
  44. // 删除字符串前后的空格,主要是删除换行符。
  45. input = strings.TrimSpace(input)
  46. // 空行不做处理
  47. if len(input) == 0 {
  48. continue
  49. }
  50. // 是否接收到退出指令
  51. switch input {
  52. case "quit", "exit":
  53. sig <- syscall.SIGQUIT
  54. default:
  55. // 发送消息给服务端
  56. sendMsgToServer(conn, input)
  57. }
  58. }
  59. }
  60. // sendMsgToServer 发送消息给服务端
  61. func sendMsgToServer(conn net.Conn, msg string) {
  62. for {
  63. _, err := conn.Write([]byte(msg))
  64. if err == nil {
  65. break
  66. }
  67. }
  68. }
  69. // signalMonitor 监听系统信号,如果程序收到退出到的信号通过 Goroutine 通知 server 端,关闭连接后退出。
  70. func signalMonitor(conn net.Conn) {
  71. signal.Notify(sig, syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGINT)
  72. // 接收到结束信号退出此程序
  73. select {
  74. case <-sig:
  75. // 通知服务端断开连接
  76. _, _ = conn.Write([]byte("exit"))
  77. fmt.Println("\nGood Bye !!!!!")
  78. os.Exit(0)
  79. }
  80. }

net.Dial函数

  1. func Dial(network, address string) (Conn, error) {
  2. var d Dialer
  3. return d.Dial(network, address)
  4. }

第一个参数network:(string类型)决定着Go程序在底层会创建什么样的socket实例,并使用什么样的协议与其它程序通信。

network常用的9个可选值:

  1. tcp:代表TCP协议,其基于的IP协议的版本根据参数address的值自适应;
  2. tcp4:代表基于IP协议第四版的TCP协议;
  3. tcp6:代表基于IP协议第六版的TCP协议;
  4. udp:代表UDP协议,其基于的IP协议的版本根据参数address的值自适应;
  5. udp4:代表基于IP协议第四版的UDP协议;
  6. udp6:代表基于IP协议第六版的UDP协议;
  7. unix:代表Unix通信域下的一种内部socket协议,以SOCK_STREAM为socket类型。
  8. unixgram:代表Unix通信域下的一种内部socket协议,以SOCK_DGRAM 为socket类型;“unixgram” 表示 Unix 域数据报套接字,用于在进程之间传输不连续的数据包(类似于 UDP 协议)。
  9. unixpacket :代表Unix通信域下的一种内部socket协议,以SOCK_SEQPACKET为socket类型;“unixpacket” 表示 Unix 域数据包套接字,与 Unix 域流套接字类似,但是提供原子性的数据包传输,即保证在发送的数据包全部到达接收端之前,接收端不会看到任何数据包。

network不常用的3个可选项:

  1. ip:IP协议,可以用于任何网络类型。
  2. ip4:仅限IPv4的IP协议,可以用于任何网络类型。
  3. ip6:仅限IPv6的IP协议,可以用于任何网络类型。

需要注意的是,““unix”、unixgram” 和 “unixpacket” 这三个可选值只能在 Unix 系统上使用。如果在非 Unix 系统上使用这两个值,Dial 函数将返回错误。

net.DialTimeout函数

net.DialTimeout函数给定的超时时间,代表着函数为网络连接建立完成而等待的最长时间。

这是一个相对时间,由这个函数的参数timeout的值表示。

开始的时间几乎是我们调用net.DialTimeout函数的那一刻。在这之后,时间会花费在“解析参数network和address的值”,以及“创建socket实例并建立网络连接”这两件事情上。

不论执行到哪一步,只要在绝对的超时时间达到的那一刻,网络连接还没建立完成,该函数就会返回一个代表了I/O操作超时的错误值。

net.DialTimeout是利用net.Dialer 结构体实现超时功能的。

参考: