一、TCP和UDP的区别: 1. 连接方式 TCP(Transmission Control Protocol,传输控制协议) 面向连接,传输前必须先建立连接(三次握手),通信结束后要断开连接(四次挥手)。
UDP(User Datagram Protocol,用户数据报协议) 无连接,直接发数据,不需要建立或断开连接。
2. 可靠性 TCP 提供可靠传输:有确认机制、重传机制、流量控制、拥塞控制。
能保证数据 不丢失、不重复、按顺序到达 。
UDP 不保证可靠性:没有确认和重传机制。
可能丢包、乱序。
3. 传输效率 TCP 开销大(需要建立连接、维护状态、做校验、重传等)。
传输效率相对较低。
UDP 头部开销小(8 字节,对比 TCP 的 20 字节起步)。
传输效率高,延迟低。
4. 应用场景 TCP 适合可靠传输场景 如:HTTP/HTTPS、FTP、SMTP、数据库连接。
(需要保证数据完整性和顺序性) UDP 适合实时场景 如:视频直播、语音通话、在线游戏、DNS 查询。
(容忍少量丢包,但需要低延迟) 一句话 : TCP = 慢但可靠(适合“文件传输”类) UDP = 快但不可靠(适合“实时传输”类) 实例代码: TCP: TCP服务端:
import java.io.*;
import java.net.*;
public class TcpServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(6666); // 监听端口
System.out.println("TCP 服务端启动,等待连接...");
Socket socket = serverSocket.accept(); // 阻塞等待客户端连接
System.out.println("客户端已连接: " + socket.getInetAddress());
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String msg = in.readLine();
System.out.println("收到客户端消息: " + msg);
out.println("服务端回复: 已收到 -> " + msg);
socket.close();
serverSocket.close();
}
}
TCP客户端:
import java.io.*;
import java.net.*;
public class TcpClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 6666); // 连接服务端
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println("Hello TCP!"); // 发送消息
String reply = in.readLine(); // 接收回复
System.out.println("收到服务端回复: " + reply);
socket.close();
}
}
运行效果: 1、先启动TcpServer,它会阻塞等待连接 2、再运行TcpServer,服务端会打印受到的消息,并回复客户端 UDP示例 UDP服务端:
import java.net.*;
public class UdpServer {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket(8888); // 监听端口
byte[] buf = new byte[1024];
System.out.println("UDP 服务端启动,等待数据...");
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet); // 阻塞等待数据
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println("收到客户端消息: " + msg);
// 回复消息
String reply = "服务端收到 -> " + msg;
byte[] data = reply.getBytes();
DatagramPacket replyPacket = new DatagramPacket(
data, data.length, packet.getAddress(), packet.getPort());
socket.send(replyPacket);
socket.close();
}
}
UDP客户端:
import java.net.*;
public class UdpClient {
public static void main(String[] args) throws Exception {
DatagramSocket socket = new DatagramSocket();
String msg = "Hello UDP!";
byte[] data = msg.getBytes();
InetAddress address = InetAddress.getByName("localhost");
DatagramPacket packet = new DatagramPacket(data, data.length, address, 8888);
socket.send(packet); // 发送消息
// 接收服务端回复
byte[] buf = new byte[1024];
DatagramPacket replyPacket = new DatagramPacket(buf, buf.length);
socket.receive(replyPacket);
String reply = new String(replyPacket.getData(), 0, replyPacket.getLength());
System.out.println("收到服务端回复: " + reply);
socket.close();
}
}
二、TCP三次握手
1. 为什么需要三次握手?
TCP 是 面向连接的、可靠的 协议,建立连接前要确保 双方都具备收发能力 。
一次握手:只能确认 客户端 → 服务端 的发送没问题。
两次握手:只能确认 双向传输通路 ,但不能防止 旧连接请求 造成混乱。
三次握手:既能确认收发能力,也能避免历史报文影响,最终保证连接可靠。
2. 三次握手过程 假设:客户端 (Client),服务端 (Server)。
第一次握手 Client → Server Client 发送
SYN = 1
,选择一个初始序列号
Seq = x
。
表示: “我要建立连接,这是我的起始序号。
” 第二次握手 Server → Client Server 收到请求后,回复
SYN = 1, ACK = 1
,确认号
Ack = x + 1
,并生成自己的初始序列号
Seq = y
。
表示: “我收到了你的请求,也要建立连接,这是我的起始序号。
” 第三次握手 Client → Server Client 收到回复后,再发
ACK = 1
,确认号
Ack = y + 1
,序号
Seq = x + 1
。
表示: “我确认收到你的序号,连接可以正式建立。
” 此时, 双方连接建立成功 ,进入 ESTABLISHED 状态,可以开始传输数据。
三次握手保证了 双方序列号的同步 。
避免了 历史连接请求报文 导致的错误连接。
握手完成后,通信双方进入 全双工通信 (可以同时收发)。
一句话总结 : TCP 三次握手是 确保客户端和服务端都有收发能力 、并且 序列号同步 的过程,是可靠传输的前提。
三、TCP四次挥手 TCP 四次挥手是 TCP 协议中断开连接时的过程。
它比“三次握手”要多一次,是因为 TCP 需要确保双方的数据都已经传输完成。
1. 四次挥手步骤 第一步:主动关闭方 → 发送 FIN 主动关闭连接的一方(通常是客户端)发送 FIN 报文段 ,表示自己已经没有数据要发送了。
进入 FIN_WAIT_1 状态。
第二步:被动关闭方 → 回复 ACK 被动关闭的一方(通常是服务器)收到 FIN 后,发送 ACK 确认报文段 。
此时,主动方知道对方已经收到了“我要关闭”的请求。
主动方进入 FIN_WAIT_2 状态。
被动方进入 CLOSE_WAIT 状态(说明它还可能有数据没发完)。
第三步:被动关闭方 → 发送 FIN 等被动关闭方的数据发完,它会主动发送 FIN 报文段 ,告诉对方“我也没有数据要发了”。
被动方进入 LAST_ACK 状态。
第四步:主动关闭方 → 回复 ACK 主动关闭方收到 FIN 后,回复一个 ACK 报文段 ,确认对方的关闭请求。
主动方进入 TIME_WAIT 状态。
最后主动方进入 CLOSED 状态,连接彻底断开。
2. 为什么需要四次?
因为 TCP 全双工 ,双方必须分别关闭发送通道。
如果只有“三次”,一方可能还没发完数据就被迫关闭了。
所以要多一步,保证双方数据传输彻底完成。
3. 代码示例 Client主动关闭端
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8888);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
// 发送数据
out.write("你好,服务器".getBytes());
// 接收服务器响应
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println("收到服务端数据: " + new String(buffer, 0, len));
// 等一会儿再关闭,模拟 TIME_WAIT 观察
Thread.sleep(2000);
System.out.println("客户端准备关闭 (FIN)");
socket.close();
}
}
Server端:
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServer {
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服务器启动,等待连接...");
Socket socket = serverSocket.accept();
System.out.println("客户端连接来自: " + socket.getInetAddress());
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println("收到客户端数据: " + new String(buffer, 0, len));
// 回复客户端
out.write("服务端回复: 收到数据".getBytes());
System.out.println("等待客户端关闭...");
// 如果客户端先关闭,read 会返回 -1
while ((len = in.read(buffer)) != -1) {
System.out.println("额外数据: " + new String(buffer, 0, len));
}
System.out.println("检测到客户端 FIN,服务器准备关闭");
socket.close();
serverSocket.close();
}
}
