Http---详细格式介绍

一个真实的 HTTP POST 请求长这样

java 复制代码
POST /api/user/login HTTP/1.1
Host: api.xxx.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36
Accept: application/json, text/plain, */*
Content-Type: application/json; charset=UTF-8
Content-Length: 56
Connection: keep-alive
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Cookie: SESSIONID=abc123456; userId=1001
Origin: https://www.xxx.com
Referer: https://www.xxx.com/login

{"username":"zhangsan","password":"123456","rememberMe":true}

HTTP 请求固定由 3 部分组成:

1. 请求行(第一行)

复制代码
GET /user HTTP/1.1
  • 方法:GET / POST / PUT / DELETE
  • 路径:/user
  • 协议版本:HTTP/1.1

2. 请求头(Header)

复制代码
Host: api.xxx.com
Content-Type: application/json
Authorization: Bearer xxxxx

键值对格式,告诉服务器:我是谁、我要什么、我发什么格式、长度多少...

3. 空行(必须有!)

复制代码
(这里空一行)

HTTP 规定:头和体之间必须空一行,服务器读到空行才开始读 body。

4. 请求体(Body)

复制代码
{"id":1,"name":"test"}

GET 一般没有 BodyPOST/PUT 才有

常见问题总结:

1 Accept与Content-Type区别
  1. Accept: application/json, text/plain, */*
  • 作用:客户端向服务器声明「我能理解的响应数据格式优先级」

    • 优先要 application/json 格式的响应

    • 其次可以接受 text/plain 纯文本

    • */* 兜底:任何格式我都能兼容

  • 它只管「响应 」,完全不涉及「请求体」的格式。

  1. Content-Type: application/json; charset=UTF-8
  • 作用:客户端向服务器声明「我这次请求的请求体(body)是什么格式」

    • 明确告诉服务器:我发的 body 是 JSON 格式,编码是 UTF-8

    • 服务器收到后,会用对应的解析器(比如 JSON 解析器)来处理请求体,而不是用表单、XML 等其他解析器

  • 它只管「请求体」,和服务器返回什么格式的响应完全无关。

2 http中请求头(Request Headers)组成主要有什么?

Host

复制代码
Host: api.xxx.com
  • 必须有

  • 表示你要访问的服务器域名

  • 一台服务器可以有多个网站,靠 Host 区分

User-Agent

复制代码
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) ...
  • 客户端身份标识

  • 服务器知道你是:浏览器 / Java / 小程序 / App / 爬虫

Accept

复制代码
Accept: application/json
  • 告诉服务器:我希望你返回 JSON 格式给我

  • 不写也可以,但规范要写

Content-Type(最重要之一)

复制代码
Content-Type: application/json; charset=UTF-8
  • 表示请求体 body 是什么格式

  • POST 必须带,否则后端不知道怎么解析

常见 3 种:

  • application/json → JSON 格式(现在最常用)

  • application/x-www-form-urlencoded → 普通表单

  • multipart/form-data → 文件上传

Content-Length

复制代码
Content-Length: 56
  • 请求体 body 的字节长度

  • 服务器读到这么多字节就知道 body 结束了

  • 你用 Hutool/OkHttp 会自动计算,不用手写

Connection

复制代码
Connection: keep-alive
  • keep-alive:复用 TCP 连接,不立即断开

  • close:一次请求完就关闭 TCP

  • HTTP/1.1 默认 keep-alive

Authorization

复制代码
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
  • 身份凭证

  • 登录令牌、Token

  • 接口鉴权专用

Cookie

复制代码
Cookie: SESSIONID=abc123; userId=1001
  • 浏览器 / 客户端存储的会话信息

  • 用来维持登录状态

Origin

复制代码
Origin: https://www.xxx.com
  • 表示当前请求来自哪个域名

  • 跨域 CORS 核心判断依据

Referer

复制代码
Referer: https://www.xxx.com/login
  • 从哪个页面跳过来的

  • 防盗链、日志统计用

3 开发中最常用的 MIME 类型分类详解

文本类(最常用,接口开发核心)

MIME 类型 说明 典型场景
application/json JSON 格式数据,前后端接口的「标准格式」 RESTful 接口的请求 / 响应体、AJAX 数据传输
text/plain 纯文本,无格式 简单字符串响应、日志、纯文本文件
text/html HTML 网页 浏览器打开网页、服务端渲染页面返回
application/xml / text/xml XML 格式数据 传统 SOAP 接口、银行 / 金融系统的老接口
application/x-www-form-urlencoded 表单默认编码格式 普通 HTML 表单提交(key=value 格式)
multipart/form-data 表单二进制编码 文件上传、带文件的表单提交
text/css CSS 样式表 网页样式文件
application/javascript JavaScript 脚本 网页 JS 文件

二进制 / 文件类(文件传输、下载常用)

MIME 类型 说明 典型场景
application/octet-stream 通用二进制流 任意未知格式的文件下载、强制浏览器下载
application/pdf PDF 文件 PDF 预览 / 下载
image/jpeg / image/png / image/gif 图片格式 图片展示、上传
audio/mpeg / video/mp4 音视频格式 音视频播放
application/zip / application/x-rar-compressed 压缩包 压缩文件下载
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Excel (.xlsx) Excel 文件下载
application/msword Word (.doc) Word 文件下载

其他高频实用类型

MIME 类型 说明 典型场景
*/* 通配符,代表「所有格式都接受」 Accept 头的兜底配置,兼容任意响应
application/problem+json JSON 格式的错误详情 REST 接口的标准化错误响应(RFC 7807)
application/json;charset=UTF-8 带编码的 JSON 明确指定 JSON 的字符编码,避免乱码
4 在head与body中间必须要有一个空行
复制代码
(这里是空行,不能少)

作用:

  • 头和体的分隔符

  • 服务器读到空行,就知道:接下来全是 body 内容

5 Java 原生 Socket 手动拼接 HTTP POST 报文 + 生产级代码
java 复制代码
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * 原生 Socket 手动拼接 HTTP POST 请求
 * 这就是所有 HTTP 工具的底层原理!
 */
public class ManualHttpPostBySocket {

    // 生产环境必须配置的超时
    private static final int CONNECT_TIMEOUT = 3000;
    private static final int SO_TIMEOUT = 5000;

    public static String sendPost(String host, int port, String path, String jsonBody) {
        // 1. 拼接完整 HTTP POST 报文(最关键!)
        String httpRequest = buildHttpRequest(host, path, jsonBody);

        // 2. 通过 Socket(TCP) 发送
        try (Socket socket = new Socket()) {
            // 连接超时
            socket.connect(new java.net.InetSocketAddress(host, port), CONNECT_TIMEOUT);
            // 读取超时
            socket.setSoTimeout(SO_TIMEOUT);

            try (OutputStream out = socket.getOutputStream();
                 InputStream in = socket.getInputStream()) {

                // 发送 HTTP 协议报文
                out.write(httpRequest.getBytes(StandardCharsets.UTF_8));
                out.flush();

                // 读取服务器返回的 HTTP 响应
                byte[] buffer = new byte[8192];
                int len = in.read(buffer);
                if (len > 0) {
                    return new String(buffer, 0, len, StandardCharsets.UTF_8);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 手动拼接完整 HTTP POST 报文
     * 这里就是 HTTP 协议的真面目!
     */
    private static String buildHttpRequest(String host, String path, String jsonBody) {
        // HTTP 请求规定:头和体之间必须空一行 \r\n\r\n
        return "POST " + path + " HTTP/1.1\r\n" +
                "Host: " + host + "\r\n" +
                "User-Agent: Java-Manual-Http\r\n" +
                "Content-Type: application/json; charset=UTF-8\r\n" +
                "Content-Length: " + jsonBody.getBytes(StandardCharsets.UTF_8).length + "\r\n" +
                "Connection: close\r\n" +
                "\r\n" + // 必须空行!分割 Header 和 Body
                jsonBody;
    }

    public static void main(String[] args) {
        // 测试:发送 POST 请求
        String host = "jsonplaceholder.typicode.com"; // 免费测试接口
        int port = 80;
        String path = "/posts";
        String json = "{\"title\":\"手动拼HTTP\",\"body\":\"这是Socket发的\",\"userId\":1}";

        String response = sendPost(host, port, path, json);
        System.out.println("=== 服务器返回的完整 HTTP 响应 ===");
        System.out.println(response);
    }
}

你代码发送出去的 HTTP 长这样:

复制代码
POST /posts HTTP/1.1
Host: jsonplaceholder.typicode.com
User-Agent: Java-Manual-Http
Content-Type: application/json; charset=UTF-8
Content-Length: 62
Connection: close

{"title":"手动拼HTTP","body":"这是Socket发的","userId":1}

服务器返回的 HTTP 响应长这样:

复制代码
HTTP/1.1 201 Created
Date: ...
Content-Type: application/json
Content-Length: ...

{
  "id": 101,
  "title": "手动拼HTTP"
}

HTTP 就是一段严格格式的文本

你完全手写拼出来,发给服务器。

HTTP 底层就是 TCP (Socket)

没有任何魔法

  • Socket = TCP 连接

  • 你写的 HTTP 报文 = 数据

Hutool/HttpClient 只是帮你拼报文而已

它们底层和这段代码一模一样

  • 拼接请求头

  • 处理 Content-Length

  • 处理空行

  • 解析响应


6 TCP 和 HTTP 是什么关系

HTTP 是跑在 TCP 上面的 "业务协议",TCP 只是底层传输通道,本身没有任何格式要求。可以只用 TCP 裸奔,也可以在 TCP 上跑 HTTP、MQTT、Redis、MySQL 等各种协议。TCP 本身没有「请求 / 响应」的概念 ,它只是一个可靠的字节流传输管道

① TCP(底层通道)

  • 只管把字节流可靠地从 A 传到 B

  • 不关心你传的是文字、图片、JSON 还是乱码

  • 没有格式要求:你发 123abchello 都行

  • 这叫裸 TCP 通信 / 自定义协议

② HTTP(上层应用协议)

  • 大家约定好的一种消息格式

  • 必须长这样:

    plaintext

    复制代码
    POST /xxx HTTP/1.1
    Host: xxx
    Content-Type: xxx
    
    内容
  • 服务器按这个格式解析,才叫 HTTP

  • HTTP 底层一定是 TCP

  • 总结就是: HTTP 是「应用层协议」,TCP 是「传输层协议」;HTTP 必须基于 TCP(或 TLS/SSL 封装的 TCP,即 HTTPS)来传输,永远不能脱离 TCP 单独存在。

  • TCP 通讯代码示例(银行 8583 风格)

    java 复制代码
    // 银行核心系统 TCP 服务端(简化版)
    @Slf4j
    public class TcpServer {
        public static void main(String[] args) throws IOException {
            ServerSocket serverSocket = new ServerSocket(8080);
            log.info("银行TCP服务端启动,端口8080");
            while (!Thread.currentThread().isInterrupted()) {
                Socket socket = serverSocket.accept();
                // 长连接,每个连接一个线程处理
                new Thread(() -> handleClient(socket)).start();
            }
        }
    
        private static void handleClient(Socket socket) {
            try (InputStream in = socket.getInputStream();
                 OutputStream out = socket.getOutputStream()) {
                // 银行标准:长度域解决粘包,前4字节存报文长度
                byte[] lengthBuf = new byte[4];
                while (in.read(lengthBuf) == 4) {
                    int length = ByteBuffer.wrap(lengthBuf).getInt();
                    byte[] dataBuf = new byte[length];
                    in.readFully(dataBuf); // 读完整报文
                    // 处理8583报文:解密、验签、业务处理
                    byte[] response = processIso8583(dataBuf);
                    // 回写响应:先写长度,再写数据
                    out.write(ByteBuffer.allocate(4).putInt(response.length).array());
                    out.write(response);
                    out.flush();
                }
            } catch (Exception e) {
                log.error("TCP连接异常", e);
            } finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    log.error("关闭TCP连接失败", e);
                }
            }
        }
    
        private static byte[] processIso8583(byte[] data) {
            // 银行8583报文处理逻辑:SM2解密、验签、业务处理
            return new byte[0];
        }
    }

案例二 与银行前置机采取NC模式TCP交互代码

生产级 TCP 客户端(银行前置 / 银联前置专用)

java 复制代码
import lombok.extern.slf4j.Slf4j;
import java.io.*;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.nio.ByteBuffer;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 银行前置 / 银联前置 TCP 客户端(生产级)
 * 支持:长连接、自动重连、4字节长度域、线程安全、超时控制
 */
@Slf4j
public class BankFrontTcpClient {

    // 基础配置
    private final String ip;
    private final int port;
    private final int connectTimeout = 5000;
    private final int readTimeout = 15000;

    // TCP 连接
    private Socket socket;
    private DataOutputStream out;
    private DataInputStream in;

    // 线程安全(高并发必须加锁)
    private final ReentrantLock lock = new ReentrantLock();

    // 连接状态
    private volatile boolean connected = false;

    public BankFrontTcpClient(String ip, int port) {
        this.ip = ip;
        this.port = port;
    }

    // -------------------------------------------------------------------------
    // 建立连接(长连接)
    // -------------------------------------------------------------------------
    public void connect() throws Exception {
        if (connected && socket != null && socket.isConnected() && !socket.isClosed()) {
            return;
        }

        close();

        // 创建 TCP Socket
        socket = new Socket();
        socket.setTcpNoDelay(true);          // 银行前置必须开(低延迟)
        socket.setKeepAlive(true);           // 长连接保活
        socket.setSoTimeout(readTimeout);    // 读取超时

        // 连接银行前置
        socket.connect(new java.net.InetSocketAddress(ip, port), connectTimeout);

        // 获取流
        out = new DataOutputStream(socket.getOutputStream());
        in = new DataInputStream(socket.getInputStream());

        connected = true;
        log.info("银行前置 TCP 连接成功 → {}:{}", ip, port);
    }

    // -------------------------------------------------------------------------
    // 发送报文 + 接收响应(核心方法)
    // 规则:4字节长度头 + 报文
    // -------------------------------------------------------------------------
    public byte[] sendAndReceive(byte[] requestData) throws Exception {
        lock.lock(); // 高并发必须加锁
        try {
            // 1. 确保连接可用
            connect();

            // 2. 发送:先发送4字节长度,再发送内容
            byte[] lengthBytes = ByteBuffer.allocate(4).putInt(requestData.length).array();
            out.write(lengthBytes);
            out.write(requestData);
            out.flush();

            log.info("发送成功,报文长度:{}", requestData.length);

            // 3. 接收:先读4字节长度,再读内容
            byte[] lenBuf = new byte[4];
            int readLen = in.read(lenBuf);
            if (readLen != 4) {
                throw new IOException("读取长度头失败,连接已断开");
            }

            // 解析报文总长度
            int msgLen = ByteBuffer.wrap(lenBuf).getInt();
            if (msgLen <= 0 || msgLen > 1024 * 1024 * 10) { // 10M 限制
                throw new IOException("报文长度非法:" + msgLen);
            }

            // 读取完整报文
            byte[] response = new byte[msgLen];
            in.readFully(response);

            log.info("接收成功,响应长度:{}", msgLen);
            return response;

        } catch (SocketTimeoutException e) {
            log.error("接收银行前置响应超时");
            close();
            throw e;
        } catch (Exception e) {
            log.error("通讯异常,准备重连", e);
            close();
            throw e;
        } finally {
            lock.unlock();
        }
    }

    // -------------------------------------------------------------------------
    // 关闭连接
    // -------------------------------------------------------------------------
    public void close() {
        connected = false;
        try {
            if (in != null) in.close();
            if (out != null) out.close();
            if (socket != null) socket.close();
        } catch (Exception e) {
            log.error("关闭连接异常", e);
        }
    }
}

三、使用示例(对接银联 / 银行前置)

java 复制代码
public class TestBankFront {
    public static void main(String[] args) throws Exception {
        // 1. 创建客户端(银行前置地址)
        BankFrontTcpClient client = new BankFrontTcpClient("192.168.1.100", 8888);

        try {
            // 2. 你的业务报文(8583 / 加密后报文 / 自定义二进制)
            byte[] sendData = "你的银行报文或加密串".getBytes("UTF-8");

            // 3. 发送并接收
            byte[] respData = client.sendAndReceive(sendData);

            // 4. 处理响应
            String resp = new String(respData, "UTF-8");
            System.out.println("银行前置响应:" + resp);

        } finally {
            client.close();
        }
    }
}
7 TCP 概述 -- 三次握手 四次挥手

一、TCP 概述:

TCP 是面向连接、可靠、全双工、字节流的传输层协议。核心靠三样保证可靠:

  1. 序列号 + 确认号

  2. 超时重传

  3. 滑动窗口、流量控制、拥塞控制

连接过程分为三部分:

  1. 三次握手(建立连接)

  2. 数据传输

  3. 四次挥手(断开连接)


二、TCP 头部关键字段

  • SYN:同步序列号,用于建立连接

  • ACK:确认号有效

  • FIN:发送方无数据要发,请求关闭

  • RST:强制断开连接(异常关闭)

  • Seq:本方发送数据的起始字节编号

  • Ack:期望收到对方下一个字节的编号 = 对方 Seq + 数据长度

  • Window:滑动窗口大小,流量控制

  • 源端口 / 目的端口:对应快递的「寄件人电话 / 收件人电话」,用来区分同一个主机上的不同应用(比如浏览器、微信、游戏)。

  • 校验和:检查数据在传输中有没有损坏,就像快递的「防伪码」,坏了就丢弃。

  • PSH:推送位,告诉对方「立刻把数据交给应用层,别缓存」,比如 Telnet 交互。


三、三次握手(Connection Establish)

  1. 目的
  • 同步双方初始序列号 ISN

  • 确认双方发送能力、接收能力都正常

  • 防止已失效的连接请求报文再次建立连接,浪费资源

  1. 详细过程

初始状态:

  • 客户端:CLOSED

  • 服务端:LISTEN

第一次握手(客户端 → 服务端)

  • 客户端发送:SYN=1, Seq=client_isn

  • 不携带数据

  • 客户端状态:CLOSED → SYN_SENT

第二次握手(服务端 → 客户端)

  • 服务端回复:SYN=1, ACK=1, Seq=server_isn, Ack=client_isn+1

  • 服务端状态:LISTEN → SYN_RCVD

  • 含义:

    • ACK:我收到你的 SYN

    • SYN:我也同步我的序列号

第三次握手(客户端 → 服务端)

  • 客户端发送:ACK=1, Seq=client_isn+1, Ack=server_isn+1

  • 客户端状态:SYN_SENT → ESTABLISHED

  • 服务端收到后:SYN_RCVD → ESTABLISHED

连接建立完成,双方进入数据传输状态。

  1. 为什么必须是三次?不能两次?
  • 两次只能确认:客户端能发、服务端能收能发

  • 服务端不知道客户端能不能正常接收

  • 若只有两次握手,网络延迟导致旧 SYN 到达,服务端直接建立连接,会造成资源浪费

  • 如果是「两次握手」:服务器收到 SYN 就直接建连

  • 服务器收到这个迟到的SYN_旧,会直接认为是新的连接请求 ,立刻分配内核资源(socket 缓冲区、进程 / 线程、定时器等),给客户端回一个 SYN+ACK 包,然后就把这个连接设为ESTABLISHED(已建立)状态,等待客户端发数据。

  • 但问题来了:

    • 客户端 A 早就完成了上一次连接,并且已经关闭了,根本不会再给这个SYN_旧回 ACK 包

    • 服务器就会一直等,直到超时才会释放这个连接。

    • 如果有大量这种延迟的旧 SYN 包(比如网络抖动、恶意攻击),服务器会被无数个「半开连接」占满资源,正常的新连接请求就会被拒绝,这就是资源浪费 ,严重时会导致拒绝服务(DoS)

  1. 半连接队列 / 全连接队列
  • SYN 队列(半连接):收到 SYN 但未完成三次握手

  • Accept 队列(全连接):已完成三次握手,等待应用 accept

  • SYN 攻击:大量发送 SYN 不回 ACK,占满半连接队列,导致正常请求无法入队


四、数据传输过程(核心机制)

连接建立后双方都是 ESTABLISHED

  1. 序列号与确认号规则
  • 每发送一字节数据,Seq 自动 +1

  • Ack = 对方 Seq + 数据长度

  • 收到 Ack 表示之前所有数据都已被正确接收

  1. 可靠传输机制

  2. 超时重传发送后未收到 ACK,超时则重传

  3. 快速重传收到 3 个重复 ACK,不等超时立即重传

  4. 滑动窗口允许连续发送多个包,提高吞吐量

  5. **流量控制(滑动窗口)**接收方通过 Window 字段告诉发送方 "我还能收多少"

  6. 拥塞控制慢启动、拥塞避免、快重传、快恢复防止网络拥塞崩溃

  7. 正常传输流程

发送方发数据 → 接收方回 ACK → 窗口滑动 → 继续发送无丢包则平稳传输;丢包则触发重传。


五、四次挥手(Connection Termination)

  1. 为什么是四次?

TCP 是全双工的,也就是:

客户端 → 服务器 是一条路

服务器 → 客户端 是另一条路

要断开连接,两条路都要分别关,所以要挥手 4 次。

简单说就是四步:

  1. 客户端说:我发完了,我要关我的发送通道

  2. 服务器说:收到,我知道你关了

  3. 服务器说:我也发完了,我也要关我的发送通道

  4. 客户端说:收到,我知道你关了

  5. 详细过程

第一次挥手:客户端 → 服务器(FIN 包)

发送方:客户端

核心动作:发起关闭请求,告诉服务器「我发完了」

字段 取值 含义
FIN 1 核心标志位:表示「我(客户端)已经发完所有数据,要关闭我的发送通道,不再给你发数据了」
ACK 1(强制) 表示这个包的确认号有效,是对服务器之前发的最后一个数据的确认
SEQ(序号) X(例:1000) 客户端当前的发送序号,这个 FIN 包本身占 1 个序号,所以客户端下一个要发的序号是 X+1
ACK_NUM(确认号) Y(例:5000) 表示「我已经收到你(服务器)到序号 Y-1 为止的所有数据,下一个期望收到 Y」,是对服务器之前数据的确认

状态变化:

  • 客户端进入 FIN_WAIT_1 状态:等待服务器确认我的 FIN

  • 服务器收到 FIN 后,进入 CLOSE_WAIT 状态:等待自己发完数据,再关闭


第二次挥手:服务器 → 客户端(ACK 包)

发送方:服务器

核心动作:确认收到客户端的 FIN,告诉客户端「我知道你要关了」

字段 取值 含义
FIN 0 表示我(服务器)还没发完数据,暂时不关闭我的发送通道
ACK 1(强制) 表示确认号有效,专门用来确认客户端的 FIN
SEQ(序号) Y(例:5000) 服务器当前的发送序号
ACK_NUM(确认号) X+1(例:1001) 核心确认:表示「我已经收到你(客户端)的 FIN(序号 X),下一个期望收到 X+1」,正式确认客户端的关闭请求

状态变化:

  • 服务器进入 CLOSE_WAIT 状态:继续给客户端发剩余数据,发完后再发起自己的 FIN

  • 客户端收到 ACK 后,进入 FIN_WAIT_2 状态:等待服务器发完数据,发起 FIN


第三次挥手:服务器 → 客户端(FIN 包)

发送方:服务器

核心动作:服务器发完数据,发起自己的关闭请求,告诉客户端「我也发完了」

字段 取值 含义
FIN 1 核心标志位:表示「我(服务器)也发完所有数据,要关闭我的发送通道,不再给你发数据了」
ACK 1(强制) 表示确认号有效,是对客户端之前数据的确认
SEQ(序号) Y(例:5000) 服务器当前的发送序号,这个 FIN 包本身占 1 个序号,服务器下一个要发的序号是 Y+1
ACK_NUM(确认号) X+1(例:1001) 再次确认客户端的 FIN,保证客户端知道服务器收到了关闭请求

状态变化:

  • 服务器进入 LAST_ACK 状态:等待客户端确认我的 FIN

  • 客户端收到 FIN 后,进入 TIME_WAIT 状态:等待 2MSL(最长报文寿命),确保服务器收到 ACK


第四次挥手:客户端 → 服务器(ACK 包)

发送方:客户端

核心动作:确认收到服务器的 FIN,告诉服务器「我知道你关了,连接彻底断开」

字段 取值 含义
FIN 0 表示我(客户端)已经关闭发送通道,不再发数据
ACK 1(强制) 表示确认号有效,专门用来确认服务器的 FIN
SEQ(序号) X+1(例:1001) 客户端当前的发送序号(第一次挥手的 FIN 占了 1 个序号,所以 + 1)
ACK_NUM(确认号) Y+1(例:5001) 核心确认:表示「我已经收到你(服务器)的 FIN(序号 Y),下一个期望收到 Y+1」,正式确认服务器的关闭请求

状态变化:

  • 客户端收到服务器的 FIN 后,发完这个 ACK,进入 TIME_WAIT 状态,等待 2MSL 后彻底关闭

  • 服务器收到这个 ACK 后,直接进入 CLOSED 状态,彻底断开连接


补充 2 个零基础必懂的关键问题

  1. 为什么第二次和第三次挥手不能合并?
  • 理论上可以合并(服务器发 ACK+FIN 一起发,看起来像 3 次挥手),但逻辑上还是 4 次

  • 不能强制合并的原因:服务器收到客户端的 FIN 后,可能还有数据要发给客户端(比如文件还没传完),必须等数据发完,才能发自己的 FIN所以必须先回 ACK 确认,等数据发完,再发 FIN,分成两步

  1. 为什么客户端要等 2MSL?
  • MSL 是「最长报文寿命」,网络里的包最多存活 MSL 时间

  • 等 2MSL 的原因:

    1. 确保服务器收到最后一个 ACK:如果 ACK 丢了,服务器会重发 FIN,客户端在 2MSL 内会收到,重新发 ACK

    2. 防止旧连接的包干扰新连接:等 2MSL,旧连接的所有包都消失了,新连接不会收到旧数据


六、完整状态流转

客户端状态

CLOSED → SYN_SENT → ESTABLISHED → FIN_WAIT1 → FIN_WAIT2 → TIME_WAIT → CLOSED

服务端状态

CLOSED → LISTEN → SYN_RCVD → ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED

TCP 连接的核心状态(三次握手涉及的)

状态 全称 核心含义 对应阶段
CLOSED Closed 连接完全关闭,无任何连接 初始状态
LISTEN Listen 服务器监听中,等待客户端连接请求 服务器初始状态
SYN_SENT SYN Sent 客户端已发送 SYN,等待服务器的 SYN+ACK 第一次握手后(客户端)
SYN_RCVD SYN Received 服务器已收到客户端 SYN,发送了 SYN+ACK,等待客户端 ACK 第二次握手后(服务器)
ESTABLISHED Established 连接完全建立,可正常收发数据 三次握手完成(双方)

8 什么是粘包、什么是拆包

TODO 下次再补充

9 全双工是什么意思

两边可以同时说话,互不干扰。

用最通俗的话讲

  • 单工:只能单向说话例:广播、电视 → 只能它发,你听。

  • 半双工 :同一时间只能一方说话例:对讲机你说完 "收到",松开按键,对方才能说话。不能同时说

  • 全双工 :两边可以同时 收发例:打电话、微信语音通话你说话的同时,也能听到对方说话,双向同时进行


放到 TCP 里是什么意思?

TCP 是全双工协议

  • 客户端 → 服务器 可以发数据

  • 服务器 → 客户端 也可以发数据

  • 而且这两个方向完全独立、可以同时进行

这就是为什么:

  • 客户端有一套自己的序号(发往服务器)

  • 服务器有另一套自己的序号(发往客户端)

  • 两套序号互不影响

因为是两条独立的 "单向通道" 绑在一起,变成一条全双工连接。

相关推荐
网硕互联的小客服2 小时前
CentOS 7 系统开通后如何修改数据盘挂载目录?
运维·服务器·网络·安全·自动化
@土豆2 小时前
混合云组网-基于公有云产品实现(非开源方法)
运维·网络·开源
CHANG_THE_WORLD2 小时前
PDF交叉引用表解析:极致详解
服务器·网络·pdf
白毛大侠12 小时前
理解 Go 接口:eface 与 iface 的区别及动态性解析
开发语言·网络·golang
萌萌哒草头将军16 小时前
CloudDock(云仓):新一代开源NAS网络代理工具
服务器·网络协议·docker
黄筱筱筱筱筱筱筱16 小时前
HCIA网络设备管理
网络
s19134838482d17 小时前
vlan实验报告
运维·服务器·网络
米丘17 小时前
Connect 深度解析:Node.js 中间件框架的基石
javascript·http·node.js
2601_9495394518 小时前
家用新能源 SUV 核心技术科普:后排娱乐、空间工程与混动可靠性解析
大数据·网络·人工智能·算法·机器学习