前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于Go的网络基础知识笔记

基于Go的网络基础知识笔记

作者头像
Lemon黄
发布2024-08-27 17:10:12
1380
发布2024-08-27 17:10:12
举报
文章被收录于专栏:Lemon黄

OSI 七层网络协议

经典协议与数据包

图一

图二

HTTP 协议

Websocket 握手协议

Websocket Data 协议

三次握手与四次挥手

TCP 为什么需要三次握手、四次挥手

  • 三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的。
  • 因为连接是全双工的,双方必须都收到对方的 FIN 包及确认才可关闭。

三次握手连接

四次挥手

抓包分析三次握手和四次挥手

TCP 拥塞控制

为啥 time_wait 需要等待 2MSL?

  • MSL:Maximum Segment Lifetime,30 秒~1 分钟。
  • 保证 TCP 协议的全双工连接能够可靠关闭。
  • 保证这次连接的重复数据段从网络中消失。

为啥会出现大量 close_wait?

  • 首先 close_wait 一般会出现在被动关闭方。
  • 并发请求太多导致。
  • 被动关闭方未及时释放端口资源导致。

TCP 为啥需要流量控制?

  • 由于通讯双方,网速不同。通讯方任一方发送过快都会导致对方消息处理不过来,所以就需要把数据放到缓冲区中。
  • 如果缓冲区满了,发送方还在疯狂发送,那接收方只能把数据包丢弃。因此我们需要控制发送速率。
  • 我们缓冲区剩余大小称之为接收窗口,用变量 win 表示。如果 win=0,则发送方停止发送。

TCP 为啥需要拥塞控制?

  • 流量控制与拥塞控制是两个概念,拥塞控制是调解网络的负载。
  • 接收方网络资源繁忙,因未及时响应 ACK 导致发送发重传大量数据,这样将会导致网络更加拥堵。
  • 拥塞控制是动态调整 win 大小,不只是依赖缓冲区大小确定窗口大小。

TCP 拥塞控制

慢开始和拥塞避免

慢开始:

拥塞避免:

快速重传和快速恢复(拥塞避免优化)

为啥会出现粘包、拆包、如何处理?

为什么会出现粘包/拆包?

  • 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
  • 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
  • 进行 MSS(最大报文长度)大小的 TCP 分段,当(TCP 报文长度 - TCP 头部长度)> MSS 的时候将发生拆包。
  • 接收方法不及时读取套接字缓冲区数据,这将发生粘包。

如何获取完整应用数据报文?

  • 使用带消息头的协议,头部写入包长度,然后再读取内容。
  • 设置定长消息,每次读取定长内容,长度不够时空位补固定字符。
  • 设置消息边界,服务端从网络流中按消息边界分离出消息内容,一般使用 \n
  • 更为复杂的协议,例如 json、protobuf。
代码实现
思维导图
完整代码实现

demo/unpack/unpack.go

代码语言:javascript
复制
package unpack

import (
 "encoding/binary"
 "errors"
 "io"
)

const Msg_Header = "12345678"

// Encode 消息编码
func Encode(bytesBuffer io.Writer, content string) error {
 // 消息格式:msg_header + content_length + content
 // 8+4+content_length
 // 写入头部 Header
 if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(Msg_Header)); err != nil {
  return err
 }
 // 写入内容长度
 clen := int32(len([]byte(content)))
 if err := binary.Write(bytesBuffer, binary.BigEndian, clen); err != nil {
  return err
 }
 // 写入内容
 if err := binary.Write(bytesBuffer, binary.BigEndian, []byte(content)); err != nil {
  return err
 }
 return nil
}

// Decode 消息解码
func Decode(bytesBuffer io.Reader) (bodyBuf []byte, err error) {
 // 读取头部
 magicBuf := make([]byte, len(Msg_Header))
 if _, err := io.ReadFull(bytesBuffer, magicBuf); err != nil {
  return nil, err
 }
 if string(magicBuf) != Msg_Header {
  return nil, errors.New("msg_header error")
 }
 // 读取内容长度
 lengthBuf := make([]byte, 4)
 if _, err := io.ReadFull(bytesBuffer, lengthBuf); err != nil {
  return nil, err
 }

 // 读取内容
 length := binary.BigEndian.Uint32(lengthBuf)
 bodyBuf = make([]byte, length)
 if _, err := io.ReadFull(bytesBuffer, bodyBuf); err != nil {
  return nil, err
 }
 return bodyBuf, nil
}

demo/tcp_client/main.go

代码语言:javascript
复制
package main

import (
 "fmt"
 "gateway-demo/unpack/unpack"
 "net"
)

func main() {
 conn, err := net.Dial("tcp", "localhost:9090")
 defer conn.Close()
 if err != nil {
  fmt.Printf("connect failed, err: %v\n", err)
  return
 }
 unpack.Encode(conn, "hello world 0!!!")
}

demo/tcp_server/main.go

代码语言:javascript
复制
package main

import (
 "fmt"
 "gateway-demo/unpack/unpack"

 "net"
)

func main() {
 // simple tcp server
 // 1. listen ip+port
 listener, err := net.Listen("tcp", "0.0.0.0:9090")
 if err != nil {
  fmt.Printf("listen failed, err: %v\n", err)
  return
 }
 // 2. accept client request
 // 3. create goroutine for each request
 for {
  conn, err := listener.Accept()
  if err != nil {
   fmt.Printf("accept failed, err: %v\n", err)
   continue
  }
  // create goroutine for each request
  go process(conn)
 }
}

func process(conn net.Conn) {
 defer conn.Close()
 for {
  bt, err := unpack.Decode(conn)
  if err != nil {
   fmt.Printf("read from connect failed, err: %v\n", err)
   break
  }
  str := string(bt)
  fmt.Printf("receive from client, data: %v\n", str)
 }
}

基于 Golang 实现 TCP、UDP、HTTP 服务器与客户端

golang 创建 UPD 服务端和客户端

思维导图
代码实现

demo/udp_client/main.go

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
)

func main() {
 // 1. 连接服务器
 conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
  IP:   net.IPv4(127, 0, 0, 1),
  Port: 9090,
 })

 if err != nil {
  fmt.Printf("connect failed, err: %v\n", err)
  return
 }

 for i := 0; i < 100; i++ {
  // 2. 发送数据
  _, err := conn.Write([]byte("hello server!"))
  if err != nil {
   fmt.Printf("send data failed, err: %v\n", err)
   return
  }
  // 3. 接收数据
  result := make([]byte, 1024)
  n, remoteAddr, err := conn.ReadFromUDP(result)
  if err != nil {
   fmt.Printf("receive data failed, err: %v\n", err)
   return
  }
  fmt.Printf("receive from addr: %v, data: %v\n", remoteAddr, string(result[:n]))
 }
}

demo/udp_server/main.go

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
)

func main() {
 // 1. 监听服务器
 listen, err := net.ListenUDP("udp", &net.UDPAddr{
  IP:   net.IPv4(0, 0, 0, 0),
  Port: 9090,
 })
 if err != nil {
  fmt.Printf("listen failed, err: %v\n", err)
  return
 }

 // 2. 循环读取消息内容
 for {
  var data [1024]byte
  n, addr, err := listen.ReadFromUDP(data[:])
  if err != nil {
   fmt.Printf("read udp failed from addr: %v, err: %v\n", addr, err)
   break
  }
  go func() {
   // todo sth
   // 3 . 回复数据
   fmt.Printf("addr: %v, data: %v, count: %v\n", addr, string(data[:n]), n)
   _, err := listen.WriteToUDP([]byte("received success!"), addr)
   if err != nil {
    fmt.Printf("write udp failed, err: %v\n", err)
   }
  }()
 }
}

Golang 创建 TCP 服务器和客户端

思维导图
代码实现

demo/tcp_client/main.go

代码语言:javascript
复制
package main

import (
 "bufio"
 "fmt"
 "net"
 "os"
 "strings"
)

func main() {
 // 1. 连接服务器
 conn, err := net.Dial("tcp", "localhost:9090")
 if err != nil {
  fmt.Printf("connect failed, err: %v\n", err)
 }
 defer conn.Close()
 // 2. 读取命令行输入
 inputReader := bufio.NewReader(os.Stdin)
 for {
  // 3. 一直读取直到读到 \n
  input, err := inputReader.ReadString('\n')
  if err != nil {
   fmt.Printf("read from console failed, err: %v\n", err)
   break
  }
  // 4. 读取Q时停止
  trimmedInput := strings.TrimSpace(input)
  if trimmedInput == "Q" {
   break
  }
  // 5. 回复服务器信息
  _, err = conn.Write([]byte(trimmedInput))
  if err != nil {
   fmt.Printf("send data failed, err: %v\n", err)
   break
  }
 }
}

demo/tcp_server/main.go

代码语言:javascript
复制
package main

import (
 "fmt"
 "net"
)

func main() {
 // 1. 监听端口
 listener, err := net.Listen("tcp", "0.0.0.0:9090")
 if err != nil {
  fmt.Printf("listen failed, err: %v\n", err)
  return
 }
 // 2. 建立套接字连接
 for {
  conn, err := listener.Accept()
  if err != nil {
   fmt.Printf("accept failed, err: %v\n", err)
   continue
  }
  // 3. 创建处理协程
  go process(conn)
 }
}

func process(conn net.Conn) {
 defer conn.Close()
 for {
  buf := make([]byte, 128)
  n, err := conn.Read(buf)
  if err != nil {
   fmt.Printf("read from connect failed, err: %v\n", err)
   break
  }
  fmt.Printf("receive from client, data: %v\n", string(buf[:n]))
 }
}

Golang 常见 HTTP 服务器和客户端

思维导图
代码实现

demo/http_server/main.go

代码语言:javascript
复制
package main

import (
 "log"
 "net/http"
 "time"
)

var (
 Addr = ":1210"
)

func main() {
 // 创建路由去
 mux := http.NewServeMux()
 // 设置路由规则
 mux.HandleFunc("/bye", sayBye)
 // 创建服务器
 server := &http.Server{
  Addr:         Addr,
  WriteTimeout: time.Second * 3,
  Handler:      mux,
 }
 // 监听端口并提供服务
 log.Println("Starting httpserver at " + Addr)
 log.Fatal(server.ListenAndServe())
}

func sayBye(w http.ResponseWriter, r *http.Request) {
 time.Sleep(1 * time.Second)
 w.Write([]byte("bye bye, this is httpserver"))
}

demo/http_client/main.go

代码语言:javascript
复制
package main

import (
 "fmt"
 "io"
 "net"
 "net/http"
 "time"
)

func main() {
 // 创建连接池
 transport := &http.Transport{
  DialContext: (&net.Dialer{
   Timeout:   30 * time.Second, // 连接超时
   KeepAlive: 30 * time.Second, // 长连接超时时间
  }).DialContext,
  MaxIdleConns:          100,              // 最大空闲连接数
  IdleConnTimeout:       90 * time.Second, // 空闲连接超时时间
  TLSHandshakeTimeout:   10 * time.Second, // tls握手超时时间
  ExpectContinueTimeout: 1 * time.Second,  // 100-continue状态码超时时间
 }
 // 创建客户端
 client := &http.Client{
  Timeout:   time.Second * 30, // 请求超时时间
  Transport: transport,
 }
 // 请求数据
 rsp, err := client.Get("http://127.0.0.1:1210/bye")
 if err != nil {
  panic(err)
 }
 defer rsp.Body.Close()
 // 读取数据
 b, err := io.ReadAll(rsp.Body)
 if err != nil {
  panic(err)
 }
 fmt.Println(string(b))
}

http 服务器源码解读

思维导图
函数是一等公民
注册路由
开启服务
处理请求

http 客户端源码解读

思维导图
Transport RoundTrip 流程
Client,Transport 配置

分段超时时间预览图:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2024-08-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 莫奈黄 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • OSI 七层网络协议
  • 经典协议与数据包
    • 图一
      • 图二
        • HTTP 协议
          • Websocket 握手协议
            • Websocket Data 协议
            • 三次握手与四次挥手
              • TCP 为什么需要三次握手、四次挥手
                • 三次握手连接
                  • 四次挥手
                    • 抓包分析三次握手和四次挥手
                    • TCP 拥塞控制
                      • 为啥 time_wait 需要等待 2MSL?
                        • 为啥会出现大量 close_wait?
                          • TCP 为啥需要流量控制?
                            • TCP 为啥需要拥塞控制?
                              • TCP 拥塞控制
                                • 慢开始和拥塞避免
                                • 快速重传和快速恢复(拥塞避免优化)
                            • 为啥会出现粘包、拆包、如何处理?
                              • 为什么会出现粘包/拆包?
                                • 如何获取完整应用数据报文?
                                  • 代码实现
                                  • 思维导图
                                  • 完整代码实现
                              • 基于 Golang 实现 TCP、UDP、HTTP 服务器与客户端
                                • golang 创建 UPD 服务端和客户端
                                  • 思维导图
                                  • 代码实现
                                • Golang 创建 TCP 服务器和客户端
                                  • 思维导图
                                  • 代码实现
                                • Golang 常见 HTTP 服务器和客户端
                                  • 思维导图
                                  • 代码实现
                                • http 服务器源码解读
                                  • 思维导图
                                  • 函数是一等公民
                                  • 注册路由
                                  • 开启服务
                                  • 处理请求
                                • http 客户端源码解读
                                  • 思维导图
                                  • Transport RoundTrip 流程
                                  • Client,Transport 配置
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档