为啥 time_wait 要等待 2MSL

  • MSL: maximum segment lifetime, 30秒 – 1分钟
  • 保证 TCP协议全双工主动关闭

image-20220123155210743

为啥会出现大量的close_wait

  • 首先 close_wait 一般出现在 被动关闭方
  • 并发请求太多导致
  • 被动关闭方未及时释放端口资源导致
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
	"fmt"
	"net"
)

func main() {
	//1、监听端口
	listener, err := net.Listen("tcp", "0.0.0.0:9090")
	if err != nil {
		fmt.Printf("listen fail, err: %v\n", err)
		return
	}
	//2.建立套接字连接
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Printf("accept fail, err: %v\n", err)
			continue
		}
		//3. 创建处理协程
		go func(conn net.Conn) {
			//defer conn.Close() //思考题:这里不填写会有啥问题?
			for {
				var buf [128]byte
				n, err := conn.Read(buf[:])
				if err != nil {
					fmt.Printf("read from connect failed, err: %v\n", err)
					break
				}
				str := string(buf[:n])
				fmt.Printf("receive from client, data: %v\n", str)
			}
		}(conn)
	}
}

tcp粘包、拆包

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

如何能获得完整的应用数据报文:

  1. 带消息头的协议,头部写入长度【根据头部来获取内容】
  2. 设置定长消息,长度不够,补充固定的 padding字符
  3. 设置消息边界 \r, \r\n 等等
  4. 使用其他更复杂协议, json,protobuf

实现简单的 tcp server

服务端代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main

import (
	"fmt"
	"net"
)

func main() {
	//1、监听端口
	listener, err := net.Listen("tcp", "0.0.0.0:9090")
	if err != nil {
		fmt.Printf("listen fail, err: %v\n", err)
		return
	}

	//2.建立套接字连接
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Printf("accept fail, err: %v\n", err)
			continue
		}

		//3. 创建处理协程
		go process(conn)
	}
}

func process(conn net.Conn) {
	defer conn.Close()	//思考题:这里不填写会有啥问题?
	for {
		var buf [128]byte
		n, err := conn.Read(buf[:])

		if err != nil {
			fmt.Printf("read from connect failed, err: %v\n", err)
			break
		}
		str := string(buf[:n])
		fmt.Printf("receive from client, data: %v\n", str)
	}
}

客户端代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package main

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

func main()  {
	doSend()
	fmt.Print("doSend over")
	doSend()
	fmt.Print("doSend over")
	//select {}
}


func doSend() {
	//1、连接服务器
	conn, err := net.Dial("tcp", "localhost:9090")
	defer conn.Close()	//思考题:这里不填写会有啥问题?
	if err != nil {
		fmt.Printf("connect failed, err : %v\n", err.Error())
		return
	}
	//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("write failed , err : %v\n", err)
			break
		}
	}
}
  • 如果server端退出协程的时候,不调用 conn.Close() 会有什么问题呢?

    • 答案: 会进入 close_wait 状态, 服务端 接收到了 fin 断开连接的请求, 但是 没有回发 fin 给客户端,这样就会一直在 close_wait状态 , 具体可以查看上面的 图片。
    • 客户端 一直等待服务端的 fin ,等不到,只能一直 在 fin_wait_2 状态
  • 如果 client 端 断开连接的时候,不调用 conn.Close() 会发生什么问题?

    • 答案: 一直卡在 establish状态 【因为没有调用 close方法,不会发 fin】, 只能等待 服务端 发送探测报文段,然后服务端关闭

实现简单的udp server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package main

import (
	"fmt"
	"net"
)

func main() {
	//step 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++ {
		//step 2 发送数据
		_, err = conn.Write([]byte("hello server!"))
		if err != nil {
			fmt.Printf("send data failed, err : %v\n", err)
			return
		}

		//step 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]))
	}
}