TCP三次握手过程详解与案例演示

什么是TCP三次握手?

所谓的“三次握手”:为了对每次发送的数据量进行跟踪与协商,确保数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。

简单来理解就是因为建立的是虚拟连接,所以为了确保客户端和服务端之间的通信而进行的一系列互相确认的过程。

分别是哪三次握手?

第一次握手

建立连接时,客户端发送syn包(seq=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

SYN_SENT表示请求连接,当你要访问其它的计算机的服务时首先要发个同步信号给该端口,此时状态为SYN_SENT,如果连接成功了就变为ESTABLISHED,此时SYN_SENT状态非常短暂。

第二次握手

服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(seq=k),即SYN+ACK包,此时服务器进入SYN_RECV状态。

SYN_RECV是指服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态。再进一步接收到客户端的ACK就进入ESTABLISHED状态。

第三次握手

客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

ESTABLISHED即表示连接成功。

三次握手图解

在这里插入图片描述

服务端先经历socket、bind、listen,这是一套标准流程,socket生成并返回一个FD,绑定端口,并开启监听。客户端调用connect便开始了三次握手的过程,注意此时并不需要服务端accept客户端请求。

完整的TCP连接过程:建立连接、传输数据、关闭连接。

在这里插入图片描述

案例演示

概念比较生疏,直接先通过一小段代码看一下效果

服务端代码创建socket并绑定9090端口,先通过System.in.read();阻塞,键盘随便输入任何内容,服务端就开始执行accept,接受客户端请求,当收到客户端请求后,交给一个新的线程处理,也就是说服务端只负责处理accept请求。

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSocketBIO {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(9090);
            System.out.println("---服务端启动---");
            System.in.read();
            while (true) {
                Socket client = serverSocket.accept();
                System.out.println("---接收客户端请求---");
                new Thread(() -> {
                    InputStream inputStream;
                    try {
                        inputStream = client.getInputStream();
                        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
                        byte[] bytes = new byte[1024];
                        int read;
                        while ((read = bufferedInputStream.read(bytes)) > 0) {
                            String s = new String(bytes, 0, read);
                            System.out.println("收到客户端发来的数据:" + s);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            client.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                serverSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端只管发送数据

import java.io.*;
import java.net.Socket;

public class SocketBIOCli {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 9090);
        System.out.println("客户端启动...");
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        while (true) {
            String readLine = reader.readLine();
            bufferedWriter.write(readLine);
            bufferedWriter.flush();
        }
    }
}

1、启动服务端

在这里插入图片描述

2、查看网络连接情况,进程ID1864,开始监听9090端口

在这里插入图片描述

3、此时9090端口上未抓取到任何数据

在这里插入图片描述

4、启动客户端,注意此时服务端处于阻塞状态(System.in.read()),并没有接受客户端连接。

在这里插入图片描述

5、三次握手完成

15:52:55.843293 IP 192.168.70.113.41691 > 192.168.70.114.9090: Flags [S], seq 222719304, win 14600, options [mss 1460,sackOK,TS val 8547774 ecr 0,nop,wscale 6], length 0
15:52:55.843312 IP 192.168.70.114.9090 > 192.168.70.113.41691: Flags [S.], seq 3048072190, ack 222719305, win 14480, options [mss 1460,sackOK,TS val 20217172 ecr 8547774,nop,wscale 7], length 0
15:52:55.843461 IP 192.168.70.113.41691 > 192.168.70.114.9090: Flags [.], ack 3048072191, win 229, options [nop,nop,TS val 8547774 ecr 20217172], length 0


6、再看服务端连接情况,此时已经完成连接,并且状态为ESTABLISHED,只是还没有分配给具体进程。

在这里插入图片描述

7、客户端同时也完成了连接

在这里插入图片描述

三次握手数据包分析

客户端192.168.70.113.41689,发送 Flags [S]表示SYN包,seq 222719304, win 14600,到服务端地址192.168.70.114.9090。

15:52:55.843293 IP 192.168.70.113.41691 > 192.168.70.114.9090: Flags [S], seq 222719304, win 14600, options [mss 1460,sackOK,TS val 8547774 ecr 0,nop,wscale 6], length 0

服务端接受到请求,发送Flags [S.],多了一个点,这个点就表示 ACK,所以服务端发送了SYN+ACK包,seq 3048072190, ack 222719305, win 14480,到客户端地址,ack = 客户端的seq+1。

15:52:55.843312 IP 192.168.70.114.9090 > 192.168.70.113.41691: Flags [S.], seq 3048072190, ack 222719305, win 14480, options [mss 1460,sackOK,TS val 20217172 ecr 8547774,nop,wscale 7], length 0

最后客户端回复ACK包,ack 3048072191, win 229,ack=服务端的seq+1

15:52:55.843461 IP 192.168.70.113.41691 > 192.168.70.114.9090: Flags [.], ack 3048072191, win 229, options [nop,nop,TS val 8547774 ecr 20217172], length 0

数据包中的其他参数

mss

maximum segment size 最大报文段长度(单位:字节),用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包含TCP及IP的协议头长度)。

win

滑动窗口大小,用户动态调节两台主机之间的数据传输。

常问面试题

1、为什么是三次握手,二次握手有什么问题?四次握手有什么问题?

  • 二次握手的问题:TCP作为一种可靠传输控制协议,既要保证数据可靠传输,又要提高传输的效率,而用三次恰恰可以满足以上两方面的需求,三次握手过程中一共产生了2个seq,即客户端首先告诉服务端自己的seq,并且表示将来数据也会按照这个seq编号递增发送,第二次服务端对seq进行加1确认,并告诉客户端自己的seq,如果此时服务端就认为可以建立接连了,那么假设服务端回复最终并没有到达客户端,那么客户端就无法确认服务端的序列号,所以也无法建立连接,那么服务端就会白白建立了这个无效的连接。
  • 四次握手的问题:客户端最后一次向服务端回复的是ACK报文,并没有携带任何数据,所以服务端无需再回复,否则这样回复将无止境下去。

2、如果已经建立了连接,但是客户端突然出现了故障,怎么办?

TCP保活机制,一般都会通过心跳来试探对方是否存活。

3、什么是TCP半连接?

处于SYN_RECV状态的服务端,完成了第二次握手,正在等待客户端回复ACK确认。

4、什么是SYN攻击?

利用三次握手的漏洞,客户端大量的向服务端发送连接请求,但在第三次握手时却不回复服务端ACK,导致服务端中存在大量的半连接请求,耗费CPU和内存资源。

防护手段

  • 降低SYN超时时间,使得主机可以快速丢弃半连接请求
  • 增大半连接队列
  • 启动syn cookies

5、怎么检测SYN攻击?

查看半连接请求:netstat -natp | grep SYN_RECV

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码拉松

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值