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

2024-08-27 17:10:12 浏览数 (2)

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: %vn", 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: %vn", 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: %vn", 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: %vn", err)
   break
  }
  str := string(bt)
  fmt.Printf("receive from client, data: %vn", 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: %vn", err)
  return
 }

 for i := 0; i < 100; i   {
  // 2. 发送数据
  _, err := conn.Write([]byte("hello server!"))
  if err != nil {
   fmt.Printf("send data failed, err: %vn", err)
   return
  }
  // 3. 接收数据
  result := make([]byte, 1024)
  n, remoteAddr, err := conn.ReadFromUDP(result)
  if err != nil {
   fmt.Printf("receive data failed, err: %vn", err)
   return
  }
  fmt.Printf("receive from addr: %v, data: %vn", 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: %vn", 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: %vn", addr, err)
   break
  }
  go func() {
   // todo sth
   // 3 . 回复数据
   fmt.Printf("addr: %v, data: %v, count: %vn", addr, string(data[:n]), n)
   _, err := listen.WriteToUDP([]byte("received success!"), addr)
   if err != nil {
    fmt.Printf("write udp failed, err: %vn", 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: %vn", 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: %vn", 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: %vn", 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: %vn", err)
  return
 }
 // 2. 建立套接字连接
 for {
  conn, err := listener.Accept()
  if err != nil {
   fmt.Printf("accept failed, err: %vn", 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: %vn", err)
   break
  }
  fmt.Printf("receive from client, data: %vn", 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 配置

分段超时时间预览图:

0 人点赞