• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

Go语言的TCP和HTTP网络服务基础

武飞扬头像
浮尘笔记
帮助1

目录

【TCP Socket 编程模型】

Socket读操作

【HTTP网络服务】

HTTP客户端

HTTP服务端


TCP/IP 网络模型实现了两种传输层协议:TCP 和 UDP,其中TCP 是面向连接的流协议,为通信的两端提供稳定可靠的数据传输服务;UDP 提供了一种无需建立连接就可以发送数据包的方法。实现网络编程,不仅可以基于应用层协议的HTTP,也可以直接基于传输层暴露给用户的网络编程接口:Socket(套接字)。socket是一种 IPC 方法。

IPC 是 Inter-Process Communication 的缩写,可以被翻译为进程间通信,主要定义的是多个进程之间相互通信的方法。这些方法主要包括:系统信号(signal)、管道(pipe)、套接字 (socket)、文件锁(file lock)、消息队列(message queue)、信号量(semaphore)等。

关于TCP和UDP以及HTTP的相关术语可以参考:HTTP协议中的响应码和实体数据

【TCP Socket 编程模型】

网络 I/O 模型指的是 应用线程与操作系统内核之间的交互模式,常用的有 阻塞 I/O(Blocking)、非阻塞 I/O(Non-Blocking)、I/O 多路复用(I/O Multiplexing)。

  • 阻塞I/O模型:内核一直等到全部数据就绪才返回给发起系统调用的应用线程。一个线程仅能处理一个网络连接上的数据通信,即便连接上没有数据,线程也只能阻塞在对 Socket 的读操作上。虽然这个模型对应用整体来说是低效的,但却是最容易实现和使用的,所以各大平台在默认情况下都将 Socket 设置为阻塞的。
  • 非阻塞I/O模型:内核查看数据就绪状态后,即便没有就绪也立即返回错误给发起系统调用的应用线程。位于用户空间的 I/O 请求发起者会通过轮询的方式,去一次次发起 I/O请求,直到读到所需的数据为止。不过这样的轮询会浪费很多CPU计算资源,因此非阻塞 I/O 模型单独应用于实际生产的比例并不高。
  • I/O多路复用模型:一个应用线程可以同时处理多个 Socket,并且由内核实现可读/可写事件的通知,避免了非阻塞模型中轮询导致的CPU计算资源浪费的问题。

学新通

Go语言的socket服务端程序通常采用一个 Goroutine 处理一个连接,主要关键词是Listen和Accept,实现方式如下:

  1.  
    package main
  2.  
     
  3.  
    import (
  4.  
    "fmt"
  5.  
    "net"
  6.  
    )
  7.  
     
  8.  
    func handleConn(c net.Conn) {
  9.  
    defer c.Close()
  10.  
    for {
  11.  
    //...
  12.  
    }
  13.  
    }
  14.  
     
  15.  
    func main() {
  16.  
    //使用net包的Listen函数绑定服务器端口8888,并将它转换为监听状态
  17.  
    l, err := net.Listen("tcp", ":8888")
  18.  
    if err != nil {
  19.  
    fmt.Println("listen error:", err)
  20.  
    return
  21.  
    }
  22.  
    for {
  23.  
    //Listen返回成功后,这个服务会进入一个循环,调用net.Listener 的 Accept 方法接收新客户端连接
  24.  
    //在没有新连接的时候,这个服务会阻塞在 Accept 调用上,直到有客户端连接上来,Accept 方法将返回一个 net.Conn 实例,用于和新连上的客户端进行通信。
  25.  
    c, err := l.Accept()
  26.  
    if err != nil {
  27.  
    fmt.Println("accept error:", err)
  28.  
    break
  29.  
    }
  30.  
     
  31.  
    //这个服务程序启动了一个新 Goroutine,并将 net.Conn 传给这个 Goroutine,这样这个Goroutine 就专门负责处理与这个客户端的通信了。
  32.  
    go handleConn(c)
  33.  
    }
  34.  
    }
学新通

运行上面的代码后,使用  netstat -an|grep 8888 可以查看端口的监听情况:

学新通

当服务端按照上面的Listen Accept结构成功启动后,客户端就可以使用 net.Dial net.DialTimeout 向服务端发起建立连接的请求。net.Dial函数会接受两个参数,分别名为network和address,都是string类型的。

参数network常用的可选值一共有 9 个,分别代表了程序底层创建的 socket 实例可使用的不同通信协议:

  • "tcp":代表 TCP 协议,其基于的 IP 协议的版本根据参数address的值自适应。
  • "tcp4":代表基于 IP 协议第四版的 TCP 协议。
  • "tcp6":代表基于 IP 协议第六版的 TCP 协议。
  • "udp":代表 UDP 协议,其基于的 IP 协议的版本根据参数address的值自适应。
  • "udp4":代表基于 IP 协议第四版的 UDP 协议。
  • "udp6":代表基于 IP 协议第六版的 UDP 协议。
  • "unix":代表 Unix 通信域下的一种内部 socket 协议,以 SOCK_STREAM 为 socket 类型。
  • "unixgram":代表 Unix 通信域下的一种内部 socket 协议,以 SOCK_DGRAM 为 socket 类型。
  • "unixpacket":代表 Unix 通信域下的一种内部 socket 协议,以 SOCK_SEQPACKET 为 socket 类型。
  1.  
    //Dial 函数向服务端发起 TCP 连接,这个函数会一直阻塞,直到连接成功或失败后,才会返回。
  2.  
    conn, err := net.Dial("tcp", "localhost:8888")
  3.  
    //DialTimeout 带有超时机制,如果连接耗时大于超时时间,这个函数会返回超时错误。
  4.  
    conn, err := net.DialTimeout("tcp", "localhost:8888", 2 * time.Second)

客户端建立连接的几种特殊情况:

  • 如果传给Dial的服务端地址是网络不可达,或者服务地址中端口对应的服务并没有启动,端口未被监听(Listen),Dial会立即返回这样的错误:dial error: dial tcp :8888: getsockopt: connection refused
  • 当对方服务器很忙,瞬间有大量客户端尝试向服务端建立连接时,服务端可能会出现 listen backlog 队列满,接收连接(accept)不及时的情况,这就会导致客户端的Dial调用阻塞,直
    到服务端进行一次 accept,从 backlog 队列中腾出一个槽位,客户端的 Dial 才会返回成功。如果服务端一直不执行accept操作,那么客户端会阻塞大约1分钟左右才会返回超时错误:dial error: dial tcp :8888: getsockopt: operation timed out
  • 如果网络延迟较大,TCP的三次握手过程会经历各种丢包,时间消耗也会更长,这种情况下Dial函数会阻塞。如果经过长时间阻塞后依旧无法建立连接,那么Dial也会返回 getsockopt: operation timed out的错误。

当客户端调用 Dial 成功,就会在客户端与服务端之间建立起一条通信通道,双方通过各自获得的 Socket可以向对方发送数据包,也可以接收来自对方的数据包,双方都会为已建立的连接分配一个发送缓冲区和一个接收缓冲区。

学新通

Socket读操作

开发人员只需要采用 Goroutine 阻塞 I/O 模型,就可以满足大部分场景需求。Dial 连接成功后,会返回一个 net.Conn 接口类型的变量值,这个接口变量的底层类型为一个 *TCPConn,TCPConn“继承”了conn类型的Read和Write方法,后续通过Dial函数返回值调用的Read和Write方法都是net.conn 的方法,它们分别代表了对 socket 的读和写。

  1.  
    //$GOROOT/src/net/tcpsock.go
  2.  
    type TCPConn struct {
  3.  
    conn //这里的conn是一个非导出类型,封装了底层的 socket
  4.  
    }

 Go 从 socket 读取数据的几种场景:

  • Socket 中无数据:连接建立后,如果客户端未发送数据,服务端会阻塞在 Socket 的读操作上,执行这个操作的 Goroutine 也会被挂起。Go 运行时会监视这个 Socket,直到它有数据读事件才会重新调度这个 Socket 对应的 Goroutine 完成读操作。
  • Socket 中有部分数据:如果 Socket 中有部分数据就绪,且数据数量小于一次读操作期望读出的数据长度,那么读操作将会成功读出这部分数据并返回,而不是等待期望长度数据全部读取后再返回。
  • Socket 中有足够数据:如果连接上有数据,且数据长度大于等于一次Read操作期望读出的数据长度,那么Read将会成功读出这部分数据并返回。
  • 设置读操作超时:有些场合对 socket 的读操作的阻塞时间有严格限制的,但由于 Go 使用的是阻塞 I/O 模型,如果没有可读数据,Read 操作会一直阻塞在对 Socket 的读操作上。这时可以通过 net.Conn 提供的 SetReadDeadline 方法,设置读操作的超时时间,当超时后仍然没有数据可读的情况下,Read 操作会解除阻塞并返回超时错误,这就给 Read 方法的调用者提供了处理其他业务逻辑的机会。

 下面代码是结合 SetReadDeadline 设置的服务端一般处理逻辑:

  1.  
    func handleConn(c net.Conn) {
  2.  
    defer c.Close()
  3.  
    for {
  4.  
    //从连接中读取数据
  5.  
    var buf = make([]byte, 128)
  6.  
    //SetReadDeadline 方法接收一个绝对时间作为超时的 deadline,一旦设置了,
  7.  
    //那么无论后续的Read操作是否超时,后面与这个socket有关的所有读操作都会返回超时失败错误。
  8.  
    //如果要取消超时设置,可以使用 SetReadDeadline(time.Time{})
  9.  
    c.SetReadDeadline(time.Now().Add(time.Second))
  10.  
    n, err := c.Read(buf)
  11.  
    if err != nil {
  12.  
    fmt.Printf("conn read %d bytes, error: %s", n, err)
  13.  
    if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
  14.  
    // 进行其他业务逻辑的处理
  15.  
    continue
  16.  
    }
  17.  
    return
  18.  
    }
  19.  
    fmt.Printf("read %d bytes, content is %s\n", n, string(buf[:n]))
  20.  
    }
  21.  
    }
学新通

【HTTP网络服务】

HTTP客户端

HTTP 协议是基于 TCP/IP 协议的,如果只是访问基于 HTTP 协议的网络服务,那么使用net/http代码包中的程序实体会更加便捷。比如使用 http.Get函数获取一个HTTP请求的返回信息,在调用它的时候只需要传给它一个 URL 就可以了:

  1.  
    func testGet() {
  2.  
    url1 := "https://www.百度.com"
  3.  
    //http.Get函数会返回两个结果值:第一个结果值的类型是*http.Response,表示网络服务传回来的响应内容的结构化表示;
  4.  
    //第二个结果值是error类型的,表示在创建和发送HTTP请求以及接收和解析HTTP响应的过程中可能发生的错误。
  5.  
    resp1, err := http.Get(url1)
  6.  
    if err != nil {
  7.  
    fmt.Printf("请求发送失败: %v\n", err)
  8.  
    }
  9.  
    defer resp1.Body.Close()
  10.  
    line1 := resp1.Proto " " resp1.Status
  11.  
    fmt.Printf("返回内容:%s\n", line1) //HTTP/1.1 200 OK
  12.  
    }
  13.  
    func main() {
  14.  
    testGet()
  15.  
    }
学新通

HTTP服务端

http.Server类型是基于 HTTP 协议的服务端,其中ListenAndServe方法的功能是:监听一个基于 TCP 协议的网络地址,并对接收到的 HTTP 请求进行处理。该方法会一直执行,直到有严重的错误发生或者被外界关掉。

  1.  
    func httpServer1() {
  2.  
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  3.  
    fmt.Fprintf(w, "用户名: %s", r.URL.Path[1:])
  4.  
    })
  5.  
    http.ListenAndServe(":8080", nil)
  6.  
    }
  7.  
    func main() {
  8.  
    httpServer1()
  9.  
    }

启动后,浏览器输入 http://localhost:8080/zhangsan

学新通

源代码:https://gitee.com/rxbook/go-demo-2023/tree/master/basic/go04/net

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgbibfj
系列文章
更多 icon
同类精品
更多 icon
继续加载