😫 痛点引入 :UDP 发出去就不管了,万一丢包怎么办?文件上传必须每字节都不能少!
TCP 协议应运而生------面向连接、可靠传输、三次握手确认!☎️
下篇手写 TCP 客户端-服务端、文件上传、多线程并发服务器,最后揭秘网页背后的 HTTP 协议!
一、TCP 协议------面向连接的「电话」☎️
1.1 回顾:UDP vs TCP
| 对比项 | UDP(上篇) | TCP(本篇) |
|---|---|---|
| 连接性 | 无连接 ❌ | 面向连接 ✅ |
| 可靠性 | 不可靠(可能丢包) | 可靠(确认机制)✅ |
| 效率 | 高 | 较低 |
| 类比 | 发短信、寄信 📮 | 打电话 ☎️ |
| 区分 | 发送端 / 接收端 | 客户端 / 服务端 |
1.2 TCP 三次握手(面试必问!📝)
客户端 服务端
| |
| ① SYN (我想连接) |
| -----------------------------> |
| |
| ② SYN+ACK (可以,我也准备好了) |
| <----------------------------- |
| |
| ③ ACK (收到,开始传数据!) |
| -----------------------------> |
| |
✅ 三次握手完成,连接建立!
💡 为什么是三次? 两次可能死锁(服务端以为连上了,客户端其实没收到确认),三次才能保证双方都说清楚!
二、TCP 核心类 🔧
2.1 两个套接字
| 类名 | 角色 | 作用 | 获取方式 |
|---|---|---|---|
| Socket | 客户端套接字 | 连接服务端、发送/接收数据 | new Socket(ip, port) |
| ServerSocket | 服务端套接字 | 监听端口、接收客户端连接 | new ServerSocket(port) |
2.2 核心方法速查
Socket 常用方法:
| 方法 | 功能 |
|---|---|
getInputStream() |
获取输入流,读取对方发来的数据 📥 |
getOutputStream() |
获取输出流,向对方发送数据 📤 |
shutdownOutput() |
关闭输出流(发送结束标记)⚠️ |
close() |
关闭连接 |
ServerSocket 常用方法:
| 方法 | 功能 |
|---|---|
accept() |
接收客户端连接,返回客户端 Socket(阻塞) |
2.3 数据交互方式
客户端发送 → 服务端读取:
客户端:getOutputStream().write(...)
服务端:getInputStream().read(...)
客户端读取 ← 服务端发送:
客户端:getInputStream().read(...)
服务端:getOutputStream().write(...)
三、TCP 基本通信 💬
3.1 客户端代码
java
import java.net.Socket;
import java.io.OutputStream;
public class TCP_Client {
public static void main(String[] args) throws Exception {
// ⚠️ new Socket() 就会触发三次握手!
// 成功说明连接建立 ✅,失败抛出异常
Socket s = new Socket("192.168.26.23", 8888);
// 发送消息给服务端
OutputStream os = s.getOutputStream();
os.write("江总你好!".getBytes());
s.close();
System.out.println("客户端发送完成!☎️");
}
}
3.2 服务端代码
java
import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStream;
public class TCP_Server {
public static void main(String[] args) throws Exception {
System.out.println("服务端启动,等待连接...📞");
// 1. 创建服务端,指定端口
ServerSocket ss = new ServerSocket(8888);
// 2. accept() 阻塞等待客户端连接
Socket client = ss.accept();
System.out.println("客户端已连接:" + client.getInetAddress());
// 3. 读取客户端消息
InputStream is = client.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf); // read() 阻塞,直到读完
String msg = new String(buf, 0, len);
System.out.println("收到:" + msg);
client.close();
}
}
3.3 ⚠️ TCP 编程注意点
- 服务端必须先启动!否则客户端连接失败
new Socket()触发三次握手,服务端没启动就抛异常accept()阻塞,直到有客户端连接read()阻塞,直到读到数据或对方关闭流
四、TCP 双向通信 💬
4.1 服务端(收消息 + 回复)
java
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class TCP_ServerPro {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(8888);
Scanner sc = new Scanner(System.in);
System.out.println("服务端启动...📞");
Socket client = ss.accept();
System.out.println("客户端连接:" + client.getInetAddress());
while (true) {
// 1. 读取客户端消息
InputStream is = client.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
String msg = new String(buf, 0, len);
System.out.println("客户端:" + msg);
// 2. 回复客户端
System.out.print("请输入回复:");
String reply = sc.next();
OutputStream os = client.getOutputStream();
os.write(reply.getBytes());
}
}
}
4.2 客户端(发消息 + 收回复)
java
import java.net.*;
import java.io.*;
import java.util.Scanner;
public class TCP_ClientPro {
public static void main(String[] args) throws Exception {
Socket s = new Socket("192.168.26.23", 8888);
Scanner sc = new Scanner(System.in);
while (true) {
// 1. 发送消息
System.out.print("请输入消息:");
String msg = sc.next();
OutputStream os = s.getOutputStream();
os.write(msg.getBytes());
// 2. 接收服务端回复
InputStream is = s.getInputStream();
byte[] buf = new byte[1024];
int len = is.read(buf);
System.out.println("服务端回复:" + new String(buf, 0, len));
}
}
}
五、TCP 文件上传 📤
5.1 需求
客户端上传图片到服务端,服务端保存后给客户端响应。
5.2 客户端(读文件 + 上传)
java
import java.net.*;
import java.io.*;
public class TCP_FileClient {
public static void main(String[] args) throws Exception {
Socket s = new Socket("127.0.0.1", 9999);
// 1. 读取本地文件
FileInputStream fis = new FileInputStream("D:/1.jpg");
OutputStream os = s.getOutputStream();
// 2. 循环写出(上传)文件数据
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
// ⚠️ 关键!告诉服务端"我传完了"
s.shutdownOutput();
fis.close();
// 3. 读取服务端响应
InputStream is = s.getInputStream();
byte[] respBuf = new byte[1024];
int respLen = is.read(respBuf);
System.out.println("服务端:" + new String(respBuf, 0, respLen));
s.close();
}
}
5.3 服务端(收文件 + 保存 + 响应)
java
import java.net.*;
import java.io.*;
import java.util.Random;
public class TCP_FileServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(9999);
System.out.println("文件服务器启动...📤");
Socket client = ss.accept();
System.out.println("客户端上传:" + client.getInetAddress());
// 1. 读取客户端上传数据
InputStream is = client.getInputStream();
// 2. 生成随机文件名(防止覆盖)
Random r = new Random();
FileOutputStream fos = new FileOutputStream(
"D:/upload/" + r.nextInt(Integer.MAX_VALUE) + ".jpg"
);
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len); // 保存到磁盘
}
fos.close();
// 3. 给客户端响应
OutputStream os = client.getOutputStream();
os.write("上传成功!✅".getBytes());
client.close();
}
}
5.4 ⚠️ shutdownOutput() ------ 文件上传的灵魂
不用 shutdownOutput():
服务端 read() 永远阻塞,不知道客户端传完了 😱
用了 shutdownOutput():
客户端调用后,发送一个"结束标记"
服务端 read() 收到 -1,跳出循环 ✅
一句话 :shutdownOutput() = 告诉对方"我说完了,你可以处理了"!
六、TCP 多线程并发服务器 🧵
6.1 为什么需要多线程?
单线程服务器:
客户端A 连接 → 服务器处理A → 处理完才能处理B
→ 客户端B 等着,体验极差 ❌
多线程服务器:
客户端A 连接 → 开线程1 处理A
客户端B 连接 → 开线程2 处理B
→ 同时处理,互不影响 ✅
6.2 多线程服务端代码
java
import java.net.*;
import java.io.*;
import java.util.Random;
public class TCP_MultiThreadServer {
public static void main(String[] args) throws Exception {
ServerSocket ss = new ServerSocket(9999);
System.out.println("多线程服务器启动...🧵");
while (true) {
Socket client = ss.accept(); // 等待客户端
System.out.println("新客户端:" + client.getInetAddress());
// 为每个客户端开启独立线程!
new Thread(() -> {
try {
// 接收文件
InputStream is = client.getInputStream();
Random r = new Random();
FileOutputStream fos = new FileOutputStream(
"D:/upload/" + r.nextInt(Integer.MAX_VALUE) + ".jpg"
);
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.close();
// 响应客户端
OutputStream os = client.getOutputStream();
os.write("上传成功!✅".getBytes());
client.close();
System.out.println("客户端上传完成!");
} catch (Exception e) {
e.printStackTrace();
}
}).start(); // 启动线程!
}
}
}
6.3 启动多个客户端测试
java
public class TCP_MultiClientTest {
public static void main(String[] args) {
// 同时启动 3 个客户端,并发上传
for (int i = 0; i < 3; i++) {
new Thread(() -> {
try {
Socket s = new Socket("127.0.0.1", 9999);
FileInputStream fis = new FileInputStream("D:/1.jpg");
OutputStream os = s.getOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = fis.read(buf)) != -1) {
os.write(buf, 0, len);
}
fis.close();
s.shutdownOutput();
// 接收响应
InputStream is = s.getInputStream();
byte[] resp = new byte[1024];
int respLen = is.read(resp);
System.out.println(Thread.currentThread().getName()
+ ":" + new String(resp, 0, respLen));
s.close();
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
💡 多线程上传优势:
- 多个客户端同时上传 ✅
- 每个客户端独占线程,互不干扰
- 服务端持续运行,不用重启
七、HTTP 协议入门------网页背后的原理 🌐
7.1 HTTP 是什么
HTTP(HyperText Transfer Protocol):超文本传输协议,应用层最常用的协议。
7.2 HTTP 请求格式
GET /index.html HTTP/1.1 ← 请求行(方法 + 路径 + 版本)
Host: www.example.com ← 请求头
User-Agent: Mozilla/5.0
← 空行(必须!)
[请求体] ← GET 请求通常没有
7.3 HTTP 响应格式
HTTP/1.1 200 OK ← 状态行(版本 + 状态码 + 消息)
Content-Type: text/html ← 响应头
Content-Length: 1234
← 空行(必须!)
<html>...</html> ← 响应体(网页内容)
7.4 常见状态码
| 状态码 | 含义 |
|---|---|
| 200 | 请求成功 ✅ |
| 301 | 永久重定向 |
| 302 | 临时重定向 |
| 404 | 资源未找到 ❌ |
| 500 | 服务器内部错误 ⚠️ |
7.5 💡 TCP 与 HTTP 的关系
TCP 是传输层协议 → 负责可靠传输数据 ☎️
↓
HTTP 是应用层协议 → 定义数据格式(请求头/响应头)🌐
↓
HTTP 底层使用 TCP 传输!
一句话:HTTP = 带格式的 TCP!
本篇总结 📝
- TCP 协议 ☎️:面向连接、可靠传输、三次握手确认
- 三次握手 📝:SYN → SYN+ACK → ACK,保证双方都确认连接
- Socket vs ServerSocket 🔧:客户端
new Socket(ip,port)、服务端new ServerSocket(port)+accept() - 数据交互 📥📤:
getInputStream()读、getOutputStream()写 - TCP 双向通信 💬:客户端发→服务端收→服务端回→客户端收
- 文件上传 📤:客户端读文件写服务端 +
shutdownOutput()发送结束标记 - 多线程服务器 🧵:
while(true) { accept(); new Thread(...).start(); }支持并发 - HTTP 协议 🌐:应用层协议,定义请求/响应格式,底层用 TCP 传输
作者 :书源丶
发布平台:CSDN