使用tcpdump,netstat简单看下tcp连接握手与挥手的过程和内核缓冲区的变化

package main

import (
	"fmt"
	"net"
)

func main() {
	listener, err := net.Listen("tcp", "127.0.0.1:8080")
	if err != nil {
		fmt.Printf("listen failed err: %v", err)
		return
	}
	var a int
	// 阻塞在这里
	fmt.Scanf("%d", &a)
	fmt.Println("start accept")
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Printf("accept failed err: %v", err)
			break
		}
		go HandleConn(conn)
	}
}

func HandleConn(conn net.Conn) {
	defer conn.Close()
	buff := make([]byte, 1024)
	for {
		n, err := conn.Read(buff)
		if err != nil {
			fmt.Printf("read failed conn:%v err:%v\n", conn, err)
			break
		}
		msg := string(buff[0:n])
		fmt.Println(msg)
	}
}
内核缓冲区的变化

上面的代码listen了一个端口之后,并没有去accept,因为被scanf阻塞住了。我们在linux下面跑上面代码之后,用netstat -antp 查看下网络状态:

tcp        0      0 127.0.0.1:8080          0.0.0.0:*               LISTEN      36535/server 

然后我们用tcpdump抓个包,使用nc连接下服务(也可使用telnet),命令分别如下:

tcpdump -nn -i lo port 8080
nc 127.0.0.1 8080

此时看到tcpdump抓的包为:

00:22:53.528171 IP 127.0.0.1.47150 > 127.0.0.1.8080: Flags [S], seq 4152235494, win 43690, options [mss 65495,sackOK,TS val 44901801 ecr 0,nop,wscale 7], length 0
09:56:09.032928 IP 127.0.0.1.8080 > 127.0.0.1.47150: Flags [S.], seq 3160960910, ack 4152235495, win 43690, options [mss 65495,sackOK,TS val 44901801 ecr 44901801,nop,wscale 7], length 0
00:22:53.528194 IP 127.0.0.1.47150 > 127.0.0.1.8080: Flags [.], ack 1, win 342, options [nop,nop,TS val 44901801 ecr 44901801], length 0

表示三次握手成功,使用netstat -antp查看网络状态结果为:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:8080          127.0.0.1:47150         ESTABLISHED -

我们可以看到虽然我们的程序并没有accept,但是连接已经在内核里面建立好,但是连接的资源还没交给进程(PID为空)。
我们随便传输几个字符(例如:123456),发现网络状态变为:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program namel
tcp        7      0 127.0.0.1:8080          127.0.0.1:47151         ESTABLISHED -

发现Recv-Q为7,表示内核缓冲区有7个字节的数据没有被读出去(网上看一些资料说,如果内核缓冲被打满,会有数据包丢失,我本地测下来发现,缓冲区满了之后,接收端会告诉发送端 win为0,此时发送端不在发送携带数据的包,但是这个点还是值得注意下的,业务处理的能力很慢,导致数据包无法及时处理,会有包丢失的情况)。
使用lsof 命令可以看到进程打开的文件描述符,命令为 lsof -p ${PID},结果为:

COMMAND   PID USER   FD      TYPE DEVICE SIZE/OFF      NODE NAME
server  37671 kang  cwd       DIR  253,2       22 105990016 /home/kang/test-tcp/server
server  37671 kang  rtd       DIR  253,0     4096       128 /
server  37671 kang  txt       REG  253,0  2252800 135546735 /tmp/go-build605900202/b001/exe/server
server  37671 kang  mem       REG  253,0  2107816 201328826 /usr/lib64/libc-2.17.so
server  37671 kang  mem       REG  253,0   142296 201404660 /usr/lib64/libpthread-2.17.so
server  37671 kang  mem       REG  253,0   164432 201328819 /usr/lib64/ld-2.17.so
server  37671 kang    0u      CHR  136,0      0t0         3 /dev/pts/0
server  37671 kang    1u      CHR  136,0      0t0         3 /dev/pts/0
server  37671 kang    2u      CHR  136,0      0t0         3 /dev/pts/0
server  37671 kang    3u     IPv4 113803      0t0       TCP localhost:webcache (LISTEN)
server  37671 kang    5u  a_inode    0,9        0      6835 [eventpoll]
server  37671 kang    6r     FIFO    0,8      0t0    113804 pipe
server  37671 kang    7w     FIFO    0,8      0t0    113804 pipe

看到37671 这个进程并没有接收socket连接,这时我们让程序开始accpet,输出结果为:

start accept
123456

然后netstat看下网络状态为:

Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:8080          127.0.0.1:47151         ESTABLISHED 37671/server

发现连接已经交给了 37671这个进程,并且内核的Recv-Q数据也被程序读出
执行lsof -p 37671结果为:

COMMAND   PID USER   FD      TYPE DEVICE SIZE/OFF      NODE NAME
server  37671 kang  cwd       DIR  253,2       22 105990016 /home/kang/test-tcp/server
server  37671 kang  rtd       DIR  253,0     4096       128 /
server  37671 kang  txt       REG  253,0  2252800    816394 /tmp/go-build283231499/b001/exe/server
server  37671 kang  mem       REG  253,0  2107816 201328826 /usr/lib64/libc-2.17.so
server  37671 kang  mem       REG  253,0   142296 201404660 /usr/lib64/libpthread-2.17.so
server  37671 kang  mem       REG  253,0   164432 201328819 /usr/lib64/ld-2.17.so
server  37671 kang    0u      CHR  136,0      0t0         3 /dev/pts/0
server  37671 kang    1u      CHR  136,0      0t0         3 /dev/pts/0
server  37671 kang    2u      CHR  136,0      0t0         3 /dev/pts/0
server  37671 kang    3u     IPv4 115369      0t0       TCP localhost:webcache (LISTEN)
server  37671 kang    4u     IPv4 115372      0t0       TCP localhost:webcache->localhost:47151 (ESTABLISHED)
server  37671 kang    5u  a_inode    0,9        0      6835 [eventpoll]
server  37671 kang    6r     FIFO    0,8      0t0    115370 pipe
server  37671 kang    7w     FIFO    0,8      0t0    115370 pipe
close wait状态的出现

我们把上述代码 defer conn.Close() 注释掉,使用nc发起一个连接,主动关闭,使用tcpdump抓包情况如下:

01:26:31.145725 IP 127.0.0.1.47153 > 127.0.0.1.8080: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 48719418 ecr 48713789], length 0
01:26:31.146179 IP 127.0.0.1.8080 > 127.0.0.1.47153: Flags [.], ack 2, win 342, options [nop,nop,TS val 48719419 ecr 48719418], length 0

这时候我们发现 127.0.0.1.8080(被动关闭的一端)收到FIN的包之后,回了ack但是并没有发送FIN包告诉主动关闭的一端可以关闭了,然后通过netstat看到状态后发现,被动关闭的一端处于 close wait的状态:

tcp        0      0 127.0.0.1:8080          127.0.0.1:47153         CLOSE_WAIT  39634/server

主动关闭的一端处于FIN_WAIT2的状态

tcp        0      0 127.0.0.1:47153         127.0.0.1:8080          FIN_WAIT2   - 

close wait的状态代表,收到了对方的关闭请求,自己这边没有主动发FIN包的导致的。

time wait的状态

我们把上述代码 defer conn.Close() 注释掉,按上述流程抓到tcpdump的包为:

02:25:50.976745 IP 127.0.0.1.47161 > 127.0.0.1.8080: Flags [.], ack 1, win 342, options [nop,nop,TS val 52279249 ecr 52279249], length 0
02:25:54.424235 IP 127.0.0.1.47161 > 127.0.0.1.8080: Flags [F.], seq 1, ack 1, win 342, options [nop,nop,TS val 52282697 ecr 52279249], length 0
02:25:54.424466 IP 127.0.0.1.8080 > 127.0.0.1.47161: Flags [F.], seq 1, ack 2, win 342, options [nop,nop,TS val 52282697 ecr 52282697], length 0
02:25:54.424476 IP 127.0.0.1.47161 > 127.0.0.1.8080: Flags [.], ack 2, win 342, options [nop,nop,TS val 52282697 ecr 52282697], length 0

发现数据又完整的四次挥手的状态,此时使用netstat 看下状态发现发起关闭端处于time wait的状态

tcp        0      0 127.0.0.1:47161         127.0.0.1:8080          TIME_WAIT   - 

这个time wait的状态出现在发起关闭的一端,是为了防止对方可能因为未收到最后发出的ack包而重发fin,主动发起的一端好进行重发ack,而time wait的持续时长为2MSL。

为什么需要四次挥手

这是由tcp的半关闭造成的,既然一个tcp连接时全双工的(即数据在两个方向上能同时传递),因此每个方向都必须单独地进行关闭挥手过程中(被动关闭的一方的 fin跟ack是可以放到一起同时发送的,也就是四次挥手流程中的第二次跟第三次的数据包可以合并为一个数据包)。需要三次握手同理。

TCP三次握手与四次挥手流程图:

在这里插入图片描述

两端同时打开

两个应用程序同时彼此执行主动打开的情况是有可能的,尽管发生的可能性极小。例如,主机A中的一个应用程序使用本地端口7777,并于主机B的端口8888执行主动打开。主机B中的应用程序则使用本地端口8888,并与主机A的端口7777执行主动打开。TCP特意设计为了可以处理同时打开,对于同时打开它仅建立一条连接而不是两条连接。连接流程图如下:
在这里插入图片描述

两端同时关闭

两端同时关闭的流程图如下:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值