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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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
:
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 配置
分段超时时间预览图: