javaWeb01-HTTP讲解

发布于:2025-07-03 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、HTTP协议

在Web应用中,浏览器请求一个URL,服务器就把生成的HTML网页发送给浏览器,而浏览器和服务器之间的传输协议是HTTP,所以:

  • HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
  • HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。

HTTP协议是一个基于TCP协议之上的请求-响应协议,它非常简单,比如:我们先使用Chrome浏览器查看百度首页,然后选择View - Developer - Inspect Elements就可以看到HTML,切换到Network,重新加载页面,可以看到浏览器发出的每一个请求和响应:

对于Browser来说,请求页面的流程如下:

  1. 与服务器建立TCP连接
  2. 发送HTTP请求;
  3. 收取HTTP响应,然后把网页在浏览器中显示出来。

1-1、HTTP请求格式

浏览器发送的HTTP请求如下:

GET / HTTP/1.1
Host: www.sina.com.cn
User-Agent: Mozilla/5.0 xxx
Accept: */*
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8
Accept-Encoding: gzip, deflate, br, zstd

 其中,第一行表示使用GET请求获取路径为/的资源,并使用HTTP/1.1协议;

从第二行开始,每行都是以Header: Value形式表示的HTTP头,比较常用的HTTP Header包括:

  • Host: 表示请求的主机名,因为一个服务器上可能运行着多个网站,因此,Host表示浏览器正在请求的域名;
  • User-Agent: 标识客户端本身,例如Chrome浏览器的标识类似Mozilla/5.0 ... Chrome/79,IE浏览器的标识类似Mozilla/5.0 (Windows NT ...) like Gecko
  • Accept:表示浏览器能接收的资源类型,如text/*image/*或者*/*表示所有;
  • Accept-Language:表示浏览器偏好的语言,服务器可以据此返回不同语言的网页;
  • Accept-Encoding:表示浏览器可以支持的压缩类型,例如gzip, deflate, br

HTTP/1.0 和 HTTP/1.1 的主要区别:

1、HTTP/1.0:每次请求都建立一个新的连接

  • 短连接(non-persistent connection)

    • 客户端每发送一次请求,就要与服务器建立一次新的 TCP 连接。

    • 服务器响应完请求之后,立即关闭连接

    • 如果客户端要再发送请求,只能重新建立 TCP 连接

这种方式的问题:

  • 每次请求都要三次握手,效率低下。(建立连接,发送数据,关闭连接)

  • 建立和断开连接的开销大,影响性能,特别是在加载多个资源(如网页中的图片、CSS、JS)时更明显。


2、HTTP/1.1:默认使用连接复用(持久连接)

  • 长连接(persistent connection)

    • 默认启用了 Connection: keep-alive

    • 一个 TCP 连接在一次请求/响应之后不会立即关闭,可以复用这个连接来传输后续的请求数据。

    • 多个 HTTP 请求和响应可以复用一个 TCP 连接(串行发送,不能并发)。

优点:

  • 减少连接的建立与关闭次数

  • 提升性能和效率,特别适合网页资源加载。


简要对比总结表:

特性 HTTP/1.0 HTTP/1.1
默认连接类型 短连接(每次请求新建连接) 长连接(连接复用)
是否支持 Keep-Alive 需要客户端手动设置 默认支持(Connection: keep-alive)
性能 多请求时效率低,资源开销大 高效,适合加载多个资源

GET请求 VS POST请求

1-2、HTTP响应 

服务器的响应如下:

HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 21932
Content-Encoding: gzip
Cache-Control: max-age=300

<html>...网页数据...

服务器响应的第一行总是版本号+空格+数字+空格+文本,数字表示响应代码,其中:2xx表示成功,3xx表示重定向,4xx表示客户端引发的错误,5xx表示服务器端引发的错误

从第二行开始,服务器每一行均返回一个HTTP头。服务器经常返回的HTTP Header包括:

  • Content-Type:表示该响应内容的类型,例如text/htmlimage/jpeg
  • Content-Length:表示该响应内容的长度(字节数);
  • Content-Encoding:表示该响应压缩算法,例如gzip
  • Cache-Control:指示客户端应如何缓存,例如max-age=300表示可以最多缓存300秒。

HTTP请求和响应都由HTTP Header和HTTP Body构成,其中HTTP Header每行都以\r\n结束。如果遇到两个连续的\r\n,那么后面就是HTTP Body。浏览器读取HTTP Body,并根据Header信息中指示的Content-TypeContent-Encoding等解压后显示网页、图像或其他内容。

通常浏览器获取的第一个资源是HTML网页,在网页中,如果嵌入了JavaScript、CSS、图片、视频等其他资源浏览器会根据资源的URL再次向服务器请求对应的资源

HTTP响应状态码

数字是给程序识别,文本则是给开发者调试使用的。

HTTP 的响应状态码(HTTP Status Code)是服务器在响应客户端请求时返回的 3 位数字,用于表示响应的结果。

HTTP 状态码分为 5 大类,根据第一位数字来区分:

分类 数字范围 含义
1xx 100–199 信息性:请求已接收,继续处理
2xx 200–299 成功:请求成功被处理
3xx 300–399 🔁 重定向:需要进一步操作
4xx 400–499 客户端错误:请求有问题
5xx 500–599 💥 服务器错误:服务器出错

1、常见状态码详细讲解:
📘 1xx 信息性
  • 100 Continue:继续发送请求的其余部分(常用于大文件上传前探测)。

  • 101 Switching Protocols:协议切换(如 HTTP → WebSocket)。


🟢 2xx 成功
  • 200 OK:请求成功(最常见)。

  • 201 Created:资源已成功创建(如 POST 创建数据)。

  • 204 No Content:请求成功但无返回内容(如删除成功后)。


🔄 3xx 重定向
  • 301 Moved Permanently:永久重定向。

  • 302 Found:临时重定向(原请求方法仍保留)。(浏览器会自动重新访问)

  • 303 See Other:建议用 GET 获取新地址资源。

  • 304 Not Modified:缓存命中,资源未更改。(让浏览器直接用自己本地的缓存)


❌ 4xx 客户端错误
  • 400 Bad Request:请求语法错误。

  • 401 Unauthorized:未认证(需要登录/Token)

  • 403 Forbidden:服务器拒绝访问,权限不足

  • 404 Not Found:找不到资源(最常见)。

  • 405 Method Not Allowed:请求方法不被允许(如 应该用GET请求,却用了POST请求)。


🔥 5xx 服务器错误
  • 500 Internal Server Error:服务器内部出错。

  • 502 Bad Gateway:网关错误(如 nginx 连接不上后端)。

  • 503 Service Unavailable:服务器暂时不可用(可能是维护或过载)。

  • 504 Gateway Timeout:网关超时。


前端调试 Ajax 请求或 RESTful 接口时,状态码是判断结果的第一参考。

1-3、HTTP协议的特点

二、Socket

2-1、什么是 Socket?

Socket 就像是“电话插口”或者“插座”,用来连接两台电脑(或手机等设备),让它们之间可以通过网络 传递信息


举个例子:你和朋友打电话

想象你和朋友打电话聊天,需要满足几个条件:

  1. 你俩都有电话(设备)。

  2. 电话都插在电话线(网络)上。

  3. 有电话号码(IP + 端口号)来找到对方。

  4. 一个人拨号(客户端),一个人接听(服务器端)。

  5. 连通之后,就可以说话(收发数据)了。

这个过程就是 Socket 通信的简化版


2-2、Socket 的基本概念

概念 通俗解释
IP 地址 就像你家的地址,用来找到哪台设备
端口号 就像你家不同的房间编号,用来找具体的“应用”(application)
客户端(Client) 打电话的人
服务器(Server) 接电话的人
套接字(Socket) 插口,用来建立连接,收发数据
连接 电话通了,双方可以交谈
数据传输 双方说话/听话的过程(发送/接收)

📢【注意】:

1、一个客户端一个socket

2、客户端有几个socket,服务器就需要对应数量的socket! 


2-3、一个简单流程(服务器和客户端的单向通行)

1. 服务端干什么?

public class SocketServer {

    public static void main(String[] args) throws Exception {
        System.out.println("Server is started...");
        ServerSocket ss = new ServerSocket(9999);

        System.out.println("Server is waiting for client request...");
        // 服务器只有accept the requests from the client,才会创建socket!
        Socket s = ss.accept();

        System.out.println("client connected");
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        String msg = br.readLine();

        System.out.println("client data: " + msg);

    }

}

【注意】:

服务器只有accept the requests from the client,才会创建socket!

2. 客户端干什么?

public class SocketClient {

    public static void main(String[] args) throws Exception {
        String ip = "localhost";
        int port = 9999; // 0-1023 to 65535
        Socket so = new Socket(ip, port);

        String s = "navin ready";

        OutputStreamWriter ow = new OutputStreamWriter(so.getOutputStream());
        PrintWriter out = new PrintWriter(ow);
        out.write(s);
        out.flush();
    }

}

结果展示:

2-4、一个简单流程(服务器和客户端的双向通行) 

客户端:

public class SocketClient {

    public static void main(String[] args) throws Exception {
        String ip = "localhost";
        int port = 9999; // 0-1023 to 65535
        Socket so = new Socket(ip, port);

        String s = "navin ready";

        OutputStreamWriter ow = new OutputStreamWriter(so.getOutputStream());
        PrintWriter out = new PrintWriter(ow);
        out.println(s); // 自动添加换行符
        out.flush();
        System.out.println("C : data is send to server...");

        // 客户端接收数据
        BufferedReader br = new BufferedReader(new InputStreamReader(so.getInputStream()));
        String ServerMsg = br.readLine();

        System.out.println("C : data from server " + ServerMsg);
    }

}

服务器端:

public class SocketServer {

    public static void main(String[] args) throws Exception {
        System.out.println("S : Server is started...");
        ServerSocket ss = new ServerSocket(9999);

        System.out.println("S : Server is waiting for client request...");
        // 服务器只有accept the requests from the client,才会创建socket!
        Socket s = ss.accept();

        System.out.println("S : client connected");
        BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
        // readLine() 会一直等到读到一个换行符(\n),才返回读取的内容。否则它会一直阻塞在这行,不往下走。
        String msg = br.readLine();

        System.out.println("S : client data: " + msg);

        // 服务器接受数据
        OutputStreamWriter ow = new OutputStreamWriter(s.getOutputStream());
        PrintWriter out = new PrintWriter(ow);
        String nickName = msg.substring(0, 3);
        out.println(nickName);
        ow.flush();
        System.out.println("S: data send from server to client...");
    }

}

结果:


2-5、Socket 的几个特点

  • 双向通信:可以发送和接收

  • 阻塞模型(默认):比如 accept() 会等客户端连上才继续,否则一直阻塞。

  • 如果一方断了,连接就会中断

  • 使用流(InputStream / OutputStream)读写数据


2-6、用在哪里?

Socket 技术是很多网络应用的基础,比如:

  • 网页浏览器访问服务器(底层是 HTTP over Socket)

  • 微信聊天

  • 文件传输

  • 网络游戏


总结一句话:

Socket 是让两台设备通过网络“对话”的工具,就像打电话一样,一端拨号(客户端),一端接听(服务端),打通之后可以收发数据。

2-7、简单的双向聊天系统

  • 一个 服务器端(Server)

  • 一个 客户端(Client)

实现功能如下:

  • 客户端连接服务器后,双方可以互相发消息

  • 使用多线程实现服务端可以同时收和发,不阻塞;


1. 服务端代码:ChatServer.java

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class ChatServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("🔵 Server started. Waiting for client...");
        Socket socket = serverSocket.accept();
        System.out.println("✅ Client connected.");

        // 接收客户端消息的线程
        Thread receiveThread = new Thread(() -> {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg;
                while ((msg = reader.readLine()) != null) {
                    System.out.println("👤 Client: " + msg);
                }
            } catch (IOException e) {
                System.out.println("❌ Client disconnected.");
            }
        });

        // 发送消息给客户端的线程
        Thread sendThread = new Thread(() -> {
            try {
                PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    String msgToSend = scanner.nextLine();
                    writer.println(msgToSend);
                }
            } catch (IOException e) {
                System.out.println("❌ Failed to send message.");
            }
        });

        receiveThread.start();
        sendThread.start();
    }
}

2. 客户端代码:ChatClient.java

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class ChatClient {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("localhost", 9999);
        System.out.println("✅ Connected to server.");

        // 接收服务端消息的线程
        Thread receiveThread = new Thread(() -> {
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String msg;
                while ((msg = reader.readLine()) != null) {
                    System.out.println("👤 Server: " + msg);
                }
            } catch (IOException e) {
                System.out.println("❌ Server disconnected.");
            }
        });

        // 发送消息到服务端的线程
        Thread sendThread = new Thread(() -> {
            try {
                PrintWriter writer = new PrintWriter(socket.getOutputStream(), true);
                Scanner scanner = new Scanner(System.in);
                while (true) {
                    String msgToSend = scanner.nextLine();
                    writer.println(msgToSend);
                }
            } catch (IOException e) {
                System.out.println("❌ Failed to send message.");
            }
        });

        receiveThread.start();
        sendThread.start();
    }
}

3、使用说明

  1. 先运行 ChatServer.java
    显示:🔵 Server started. Waiting for client...

  2. 再运行 ChatClient.java
    显示:✅ Connected to server.

  3. 然后你可以:

    • 在客户端窗口输入消息发送给服务器;

    • 在服务端窗口输入消息发送给客户端;

    • 双向实时通信!

4、为什么要用一个接受线程和一个发送线程呢?

因为 接收和发送是两个独立的、可能会阻塞的操作放在同一个线程里会导致程序卡住,无法实现“边发边收”的实时通信。

举个例子

如果你写的是下面这样的代码(没有用线程):

void chat() {
    while (true) {
        sendMessage();    // 发送给对方
        receiveMessage(); // 接收对方消息
    }
}

发送或接收其中一个阻塞了,另一个就没法运行!

Socket 的 readLine()阻塞的

BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = br.readLine(); // 如果对方没发消息,会一直卡住!

如果你把这个放在主线程里,程序就被卡死在这里了,无法再去读取你的键盘输入、也不能再发消息了。

功能线程 作用 是否会阻塞? 如果阻塞会影响谁?
接收线程 一直监听对方消息 只影响自己(接收)
发送线程 等待你输入消息 可能阻塞 只影响自己(发送)

分线程后,即使你一直在等输入,对方发来的消息也能实时收到 —— 这就是并发的好处

了解了socket以后,就可以编写一个简单的HTTP服务器。 读取HTTP请求,发送响应。

HTTP目前有多个版本,1.0是早期版本,浏览器每次建立TCP连接后,只发送一个HTTP请求并接收一个HTTP响应,然后就关闭TCP连接。

由于创建TCP连接本身就需要消耗一定的时间,因此,HTTP 1.1允许浏览器和服务器在同一个TCP连接上反复发送、接收多个HTTP请求和响应,这样就大大提高了传输效率。

我们注意到HTTP协议是一个请求-响应协议,它总是发送一个请求,然后接收一个响应。能不能一次性发送多个请求,然后再接收多个响应呢?

HTTP 2.0可以支持浏览器同时发出多个请求,但每个请求需要唯一标识,服务器可以不按请求的顺序返回多个响应,由浏览器自己把收到的响应和请求对应起来。

可见,HTTP 2.0进一步提高了传输效率,因为浏览器发出一个请求后,不必等待响应,就可以继续发下一个请求。

HTTP 3.0为了进一步提高速度,将抛弃TCP协议,改为使用无需创建连接的UDP协议,目前 HTTP/3 已成为正式标准,且被主流浏览器和大公司广泛支持。

协议 底层传输 特点
HTTP/1.1 TCP 每次请求一个连接,慢
HTTP/2 TCP 支持多路复用,但头部压缩、阻塞问题
HTTP/3 UDP + QUIC 支持 0-RTT 连接、无队头阻塞,传输更快

三、Socket、Servlet与Tomcat 

3-1、servlet是什么

Servlet 是 Java Web 服务器上的一个小程序,用来处理客户端的请求,并返回响应
它运行在 Tomcat 这样的 Servlet 容器中。

Servlet 的作用(你只要记住这两个)

  1. 接收请求(通常是 HTTP 请求)

  2. 生成响应(比如返回 HTML 或 JSON)

Servlet 是运行在 Web 服务器(如 Tomcat)上的 Java 类,用来处理浏览器的请求并返回数据,是 Java Web 的核心技术之一。

3-2、servlet、socket和Tomcat的关系

Servlet 是基于 Socket 实现的,它建立在 Socket 通信之上,用来处理 HTTP 协议 的请求和响应。 

1、为什么已经有了 Socket,还需要 Servlet?

因为Socket 太底层,如果你不用 Servlet,Web 开发要这么写:

Socket socket = serverSocket.accept();
InputStream in = socket.getInputStream();
byte[] buf = new byte[1024];
in.read(buf);
// 然后你自己去解析 HTTP 请求头、请求体...

你需要:

  • 手动读字节;

  • 手动解析请求头;

  • 手动拼接 HTTP 响应字符串;

  • 手动处理 Content-Type、Cookie、Session...

 太麻烦、太容易出错、不适合开发者写业务逻辑。

Servlet 就是解决这个问题的“Java 规范”(接口)

它说:“HTTP 这个活我帮你干了,你专心写处理逻辑就行。”

2、那 Tomcat 又是干嘛的?

Servlet 是一个规范(接口),不是你能直接运行的代码。 

Tomcat 就是帮你跑这些 Servlet 的软件,它负责:

  • 管理 Socket;

  • 管理 Servlet 生命周期(创建、调用、销毁);

  • 调用你写的 doGet() / doPost()

  • 维护 Session、线程池、连接池...

3、Servlet 底层是这样运作的:

浏览器发起 HTTP 请求 → 被 Tomcat 的 Socket 接收(底层是 TCP)
               ↓
Tomcat 解析 HTTP 请求(请求行、请求头、请求体)
               ↓
创建 HttpServletRequest 对象
               ↓
调用 Servlet 的 doGet()/doPost() 方法
               ↓
生成响应 → 封装成 HTTP 响应格式 → 通过 Socket 返回给客户端

也就是说,Servlet 不直接操作 Socket,而是 Tomcat 等服务器帮你用 Socket 把 HTTP 请求“翻译”好,然后交给 Servlet 来处理业务逻辑

4、servlet处理动态请求

Servlet 主要用于处理客户端发送的“动态请求”,比如表单提交、登录、数据库查询等。
静态请求(如 HTML、CSS、图片)不需要 Servlet,Tomcat 等服务器可以直接处理并返回资源


(1)、什么是“动态请求” vs “静态请求”?
类型 示例 是否走 Servlet? 举例说明
✅ 动态请求 /login/query?user=tom 需要 Servlet 要执行 Java 代码,处理参数、查数据库等
❌ 静态请求 /index.html/style.css/logo.png 不需要 Servlet Tomcat 直接读文件,从硬盘返回给浏览器

(2)、Servlet 的职责:
  • 接收 HTTP 请求

  • 分析请求参数(如 GET/POST 参数)

  • 调用业务逻辑(比如数据库)

  • 动态生成响应内容(如 HTML、JSON)

  • 返回给客户端


(3)、静态资源的请求流程:

你在浏览器输入:

http://localhost:8080/static/index.html

Tomcat 会:

  1. 在 web 项目的 webapp/static/ 文件夹里找到 index.html

  2. 自动读取文件内容

  3. 用 HTTP 协议把文件返回给浏览器

这个过程不需要 Servlet 参与,因为没有动态处理逻辑。


(4)、配置角度(默认行为)

web.xml 或 Spring Boot 配置中,一般会设置哪些路径交给 Servlet 处理,哪些路径交给静态资源处理器,比如:

<servlet-mapping>
  <url-pattern>/api/*</url-pattern>  <!-- 动态请求走 Servlet -->
</servlet-mapping>

而静态资源路径(如 /static//public/)默认直接由 Tomcat 的 DefaultServlet 处理。

3-3、小结


网站公告

今日签到

点亮在社区的每一天
去签到