本文更新于 2026-03-26
常见架构
| 维度 | C/S (客户端模式) | B/S (浏览器模式) |
|---|---|---|
| 硬件要求 | 较高(利用本地资源) | 较低(主要靠服务器) |
| 部署难度 | 困难(需逐个安装) | 极易(地址一输即用) |
| 升级维护 | 整体升级,成本高 | 无缝升级,用户无感 |
| 网络要求 | 部分功能可离线运行 | 必须全程联网 |
| 典型案例 | 英雄联盟、Photoshop | 知乎、GitHub、1Panel |
网络编程三要素
1. IP 地址 (Internet Protocol Address) —— “收货地址”
设备在网络中的唯一标识。 如果没有 IP 地址,数据包就不知道该发给哪台电脑。
IPv4:目前主流,如
192.168.1.1。它是 32 位地址,通常用“点分十进制”表fff示。IPv6:为了解决 IPv4 地址枯竭问题,位数为 128 位,采用“冒分十六进制”。
特殊地址:
127.0.0.1或localhost(回环地址,代表本机)。
| 方法名 | 作用 | 返回值示例 |
|---|---|---|
getHostName() | 获取主机名 | www.baidu.com 或 LAPTOP-XXXX |
getHostAddress() | 获取字符串格式的 IP 地址 | 110.242.68.4 |
isReachable(int timeout) | 测试在指定时间内是否能连接上该地址 | true / false(类似 ping) |
2. 端口 (Port) —— “收货人姓名/房间号”
应用程序在设备中的唯一标识。 一台电脑上同时运行着微信、IDEA、拼图游戏和浏览器,IP 只能找电脑,而端口负责找到对应的程序。
范围:0 ~ 65535。
分类:
周知端口 (0~1023):被系统协议占用,如 HTTP (80)、HTTPS (443)、FTP (21)。
注册端口 (1024~49151):常用软件占用,如 MySQL (3306)、Tomcat (8080)、Redis (6379)。
动态端口 (49152~65535):系统自动分配。
注意:同一个端口在同一时刻不能被两个程序同时占用。
3. 协议 (Protocol) —— “语言/交通规则”
数据传输的规则。 通信双方必须遵守相同的约定(如数据格式、重传机制),才能听懂对方在说什么。
在 Java 基础开发中,最核心的是 传输层 的两大协议:
| 协议 | UDP (用户数据报协议) | TCP (传输控制协议) |
|---|---|---|
| 连接性 | 面向无连接(直接发,不管对方在不在) | 面向连接(必须先握手建立连接) |
| 可靠性 | 不可靠(可能丢包、乱序) | 可靠(保证数据准确、有序) |
| 传输效率 | 极高(没有确认机制) | 相对较低 |
| 传输限制 | 每个数据包限 64KB | 无限制(流式传输) |
| 典型应用 | 视频会议、直播、语音通话 | 浏览器网页 (HTTP)、文件传输、邮件 |
TCP/IP协议
| 层级 | 层名称 | 核心职责 (快递比喻) | 常见协议 |
|---|---|---|---|
| 4 | 应用层 (Application) | 内容制作:决定快递寄什么(信件、包裹、视频)。 | HTTP, FTP, DNS, SMTP |
| 3 | 传输层 (Transport) | 运输方式:平邮(UDP)还是挂号信(TCP)。 | TCP, UDP |
| 2 | 网络层 (Internet) | 地址路由:规划从上海到北京的最优路线。 | IP, ICMP, ARP |
| 1 | 网络接口层 (Link) | 物理运输:实际上是走铁路、公路还是空运。 | Ethernet (以太网), Wi-Fi |
UDP发送数据
具体流程
找邮局:创建
DatagramSocket对象(码头/发送端)。装包裹:创建
DatagramPacket对象,并打包数据、指定接收端 IP 和端口。发快递:调用
send()方法发送包裹。关门走人:释放资源(
close())。
DatagramSocket ds = new DatagramSocket();
String s = "你好";
byte[] bytes = s.getBytes();
InetAddress address = InetAddress.getLocalHost();
int port = 10086;
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, port);
ds.send(dp);
ds.close();
UDP接收数据
具体流程
设信箱:创建
DatagramSocket对象,并明确指定端口(必须和发送端指向的端口一致)。准备空盘子:创建一个字节数组,用来存放接收到的数据。
造空包裹:创建
DatagramPacket对象,作为接收数据的容器。等快递:调用
receive()方法。注意:这个方法是阻塞的,没收到货之前程序会一直停在这儿。
DatagramSocket ds = new DatagramSocket(10086);
byte[] bytes = new byte[1024];
DatagramPacket dp = new DatagramPacket(bytes, bytes.length);
//receive是阻塞的
ds.receive(dp);
byte[] data = dp.getData();
int len = dp.getLength();
InetAddress address = dp.getAddress();
int port = dp.getPort();
System.out.println("接收到了来自" + address + ":" + port + "的数据:" + new String(data, 0, len));
UDP的三种通信方式
单播
这是最常见的通信方式。发送端明确指定接收端的 IP 地址 和 端口号。只有目标主机能收到数据包。
特点:点对点,不会给其他不相关的机器造成负担。
Java 实现:使用常规的
InetAddress.getByName("192.168.1.100")
广播
发送端向局域网(LAN)内的所有主机发送数据。无论对方想不想听,数据都会送到他们的门口。
广播地址:固定为
255.255.255.255。特点:范围限制在当前局域网内,路由器通常不会转发广播包(防止垃圾信息塞满互联网)。
Java 实现:
// 发送到广播地址
InetAddress address = InetAddress.getByName("255.255.255.255");
DatagramPacket dp = new DatagramPacket(bytes, bytes.length, address, 10086);
组播
组播是介于单播和广播之间的一种高效方式。发送端向一个特定的组播地址发消息,只有“加入”了这个组的主机才能收到。
组播地址范围:D 类地址(
224.0.0.0~239.255.255.255)。其中224.0.0.0~224.0.0.255为预留。特点:比广播更节省资源,只有感兴趣的主机才会处理数据。
Java 实现(特殊类):接收端需要使用
MulticastSocket并调用joinGroup()
TCP发送和接收数据
客户端 (Socket):主动拨号的人(请求连接)。
服务端 (ServerSocket):接电话的人(等待连接)。
//TCP发送数据
// 1. 创建客户端 Socket 对象,并连接服务端
// 这一步会自动完成“三次握手”
Socket socket = new Socket("127.0.0.1", 10086);
// 2. 获取输出流,准备往服务器写数据
OutputStream os = socket.getOutputStream();
// 3. 写出数据
os.write("你好,TCP服务端!我是客户端".getBytes());
// 4. 释放资源
os.close();
socket.close();
//TCP接收数据
// 1. 创建服务端的 ServerSocket,并指定端口
ServerSocket ss = new ServerSocket(10086);
// 2. 等待客户端连接(阻塞式)
// 只有客户端拨通了,这行代码才会往下走,并返回一个 Socket 对象
System.out.println("服务端已启动,等待连接...");
Socket socket = ss.accept();
// 3. 获取输入流,读取客户端发来的数据
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int len = is.read(bytes); // 读取数据
// 4. 解析并打印
String msg = new String(bytes, 0, len);
System.out.println("收到消息:" + msg);
// 5. 释放资源
socket.close();
ss.close();
三次握手 (Three-way Handshake) —— 建立连接
核心目的:确认双方的收、发能力都是正常的,并同步序列号。
第一次握手:客户端发送一个
SYN包给服务端。- 潜台词:“喂,你能听到我说话吗?”(客户端:确认自己能发;服务端:确认自己能收,对方能发)。
第二次握手:服务端收到后,回传一个
SYN + ACK包。- 潜台词:“我能听到!你能听到我说话吗?”(服务端:确认自己能发;客户端:确认自己能收,对方能发/收都正常)。
第三次握手:客户端收到后,再回传一个
ACK包。- 潜台词:“我也能听到。那我们要开始传数据了。”(服务端:确认对方收/发都正常)。
四次挥手 (Four-way Wave) —— 释放连接
核心目的:确保双方都发送完了数据,并优雅地关闭双向通道。
第一次挥手:客户端发一个
FIN包。- 潜台词:“我的数据传完了,我要挂了。”(此时客户端进入半关闭状态)。
第二次挥手:服务端回一个
ACK。- 潜台词:“我知道你要挂了,但我这儿还有点活没干完,你等会儿。”(此时服务端可能还在发最后的数据)。
第三次挥手:服务端干完活了,发一个
FIN。- 潜台词:“好了,我也干完了,挂吧。”
第四次挥手:客户端回一个
ACK,并进入TIME_WAIT状态。- 潜台词:“收到,拜拜!”(客户端会等一会儿确保服务端收到了这个 ACK,然后彻底消失)。
为什么握手是 3 次,挥手要 4 次?
为什么不是 2 次? * 防止“失效的连接请求”突然到达服务端。如果只有 2 次,服务端一回应就建立连接,但客户端其实已经走掉了,这会浪费服务端资源。
为什么挥手多一次? * 核心原因:半关闭状态。当客户端说“我不发了”时(第一次挥手),服务端可能还有数据没发完。服务端必须先回一个 ACK 告诉客户端“我收到你的请求了”,等自己发完最后的数据,再发 FIN(第三次挥手)。中间这两步不能合并。
豫公网安备41019702004633号