【Java +AI |基础加强篇day14 网络编程】
互联网协议地址(Internet Protocol Address),用于定位网络中的设备,确保数据发送到正确目标。分类IPv4:32 位,点分十进制表示(如 192.168.1.66),地址池有限,目前仍广泛使用。IPv6:128 位,冒分十六进制表示(如 2001:0db8:0000:0023:0008:0800:200c:417a),解决 IPv4 地址枯竭问题。关键概念公网 IP:可直接连
本章节内容是给后续web框架做的铺垫 其中服务器创建接收了解即可 尝试用计算机网络学到的理论知识通过Java实现
Java 网络编程深度学习笔记(含通信协议、架构与实战)
核心结论
Java 网络编程的核心是实现不同设备间的跨网络数据交互,依赖 IP、端口、协议 三要素,主要支持 UDP(无连接、高效)和 TCP(面向连接、可靠)两种通信方式,适配 CS/BS 两种架构,广泛应用于即时通信、文件传输、网页访问等场景。
一、网络编程基础 —— 三要素与通信架构
1. 网络通信三要素(核心前提)
网络设备间要实现通信,必须明确 “定位设备、定位程序、遵循规则”,即三大核心要素:
(1)IP 地址 —— 设备的唯一标识
-
定义:互联网协议地址(Internet Protocol Address),用于定位网络中的设备,确保数据发送到正确目标。
-
分类
:
-
IPv4:32 位,点分十进制表示(如 192.168.1.66),地址池有限,目前仍广泛使用。
-
IPv6:128 位,冒分十六进制表示(如 2001:0db8:0000:0023:0008:0800:200c:417a),解决 IPv4 地址枯竭问题。
-
-
关键概念
:
-
公网 IP:可直接连接互联网的地址(如服务器 IP)。
-
内网 IP:局域网内部使用的地址(如 192.168.x.x、10.x.x.x),无法直接被外网访问。
-
本机 IP:127.0.0.1 或 localhost,指向当前设备,用于本地程序测试。
-
-
常用命令
:
-
ipconfig:查看本机 IP 地址(Windows)。 -
ping + IP:测试网络连通性(如ping 192.168.1.1)。
-
-
Java 核心类:InetAddress
:
封装 IP 地址信息,核心方法:
java
运行
InetAddress localIp = InetAddress.getLocalHost(); // 获取本机 IP InetAddress baiduIp = InetAddress.getByName("www.baidu.com"); // 根据域名获取 IP String hostName = localIp.getHostName(); // 获取主机名 String ipAddr = localIp.getHostAddress(); // 获取 IP 字符串 boolean reachable = localIp.isReachable(3000); // 测试 3 秒内是否连通
(2)端口 —— 程序的唯一标识
-
定义:标记设备上运行的应用程序,是 16 位二进制数,范围 0~65535。
-
分类
:
-
周知端口(0~1023):系统预留,如 HTTP 80、FTP 21、HTTPS 443。
-
注册端口(1024~49151):用户程序使用,如 Tomcat 8080、MySQL 3306。
-
动态端口(49152~65535):临时分配,程序结束后释放。
-
-
核心规则:同一设备上,不同程序的端口不能重复,否则会抛出 “端口冲突” 异常。
(3)协议 —— 通信的规则约定
-
定义:设备间连接和数据传输的规则集合,确保数据格式、传输方式一致。
-
核心协议模型
:
OSI 参考模型(7 层) TCP/IP 模型(4 层) 核心作用 典型协议 应用层 应用层 提供用户交互接口(程序员开发重点) HTTP、FTP、SMTP 表示层 / 会话层 应用层 数据格式转换、会话管理(TCP/IP 合并入应用层) - 传输层 传输层 端到端数据传输(可靠 / 不可靠) TCP、UDP 网络层 网络层 路由选择、IP 封装 IP 数据链路层 / 物理层 网络接口层 物理介质传输(比特流) - -
传输层核心协议对比(UDP vs TCP)
:
对比维度 UDP(用户数据报协议) TCP(传输控制协议) 连接方式 无连接(无需建立连接,直接发送) 面向连接(三次握手建立连接) 可靠性 不可靠(数据可能丢失、乱序,无确认机制) 可靠(数据确认、重传、排序,确保送达) 数据限制 单次传输 ≤64KB 无大小限制(流式传输) 通信效率 高(无连接开销,延迟低) 低(连接、确认开销,延迟高) 适用场景 视频直播、语音通话、游戏(容忍数据丢失) 网页访问、文件下载、支付(需数据可靠)
2. 通信架构 ——CS 与 BS 架构
网络应用的两种核心部署架构,适配不同场景需求:
(1)CS 架构(Client-Server)
-
结构:客户端(需开发安装)+ 服务端(需开发部署)。
-
核心特点
:
-
客户端:需程序员开发专属软件(如微信、网易云音乐),用户需下载安装。
-
服务端:部署在服务器,提供数据服务,支持多客户端连接。
-
-
优势:交互体验好、功能丰富(可访问本地资源)。
-
劣势:客户端需更新维护,跨平台成本高。
-
适用场景:即时通信、大型游戏、专业软件(如 Photoshop 云服务)。
(2)BS 架构(Browser-Server)
-
结构:浏览器(无需开发)+ 服务端(需开发部署)。
-
核心特点
:
-
客户端:使用通用浏览器(Chrome、Edge),无需额外开发。
-
服务端:需遵循 HTTP 协议,响应浏览器请求(返回网页数据)。
-
-
优势:无需安装更新、跨平台(浏览器兼容)、开发维护成本低。
-
劣势:交互体验受限(依赖浏览器功能)、对网络依赖性强。
-
适用场景:网页应用(京东、百度)、后台管理系统、轻量工具(在线文档)。
(3)核心对比
| 对比维度 | CS 架构 | BS 架构 |
|---|---|---|
| 客户端开发 | 需定制开发(如 Java Swing、Android) | 无需开发(复用浏览器) |
| 用户操作 | 需下载安装、更新 | 直接通过 URL 访问,无更新成本 |
| 功能扩展性 | 强(可调用本地硬件、资源) | 弱(受浏览器沙箱限制) |
| 开发维护成本 | 高(客户端 + 服务端双端维护) | 低(仅维护服务端) |
| 适用场景 | 重交互、强功能应用 | 轻量、跨平台访问应用 |
二、UDP 通信 —— 无连接高效传输
1. 核心特点
-
无连接:发送方无需与接收方建立连接,直接封装数据包发送。
-
不可靠:不保证数据送达,无确认、重传机制,可能丢失、乱序。
-
高效低延迟:无连接开销,适合实时传输(如直播、语音)。
-
数据限制:单次传输数据 ≤64KB(数据包大小限制)。
2. 核心类与 API
Java 通过 java.net 包下的两个核心类实现 UDP 通信:
(1)DatagramSocket—— 通信端点
-
作用:创建 UDP 通信的客户端 / 服务端,负责发送和接收数据包。
-
核心构造器:
-
DatagramSocket():创建客户端实例,系统随机分配端口。 -
DatagramSocket(int port):创建服务端实例,绑定指定端口(必须唯一)。
-
-
核心方法:
-
void send(DatagramPacket dp):发送数据包。 -
void receive(DatagramPacket dp):接收数据包(阻塞等待)。 -
void close():关闭通信端点,释放资源。
-
(2)DatagramPacket—— 数据包
-
作用:封装待发送 / 接收的数据、目标 IP、端口等信息。
-
核心构造器:
-
发送端:
DatagramPacket(byte[] buf, int length, InetAddress addr, int port)
(buf:数据字节数组;length:数据长度;addr:目标 IP;port:目标端口)。
-
接收端:
DatagramPacket(byte[] buf, int length)
(buf:接收数据的缓冲区;length:缓冲区大小)。
-
-
核心方法:
-
int getLength():获取实际接收的数据长度。 -
InetAddress getAddress():获取发送方的 IP 地址(接收端用)。 -
int getPort():获取发送方的端口(接收端用)。
-
3. 实现步骤
(1)单发单收(基础版)
服务端(接收方)
-
创建
DatagramSocket实例,绑定端口(如 8888)。 -
创建字节缓冲区(如
byte[] buf = new byte[1024])。 -
创建
DatagramPacket实例(接收数据)。 -
调用
receive()阻塞接收数据。 -
解析数据包(获取数据、发送方 IP / 端口)。
-
关闭资源。
客户端(发送方)
-
创建
DatagramSocket实例(随机端口)。 -
准备发送数据(转为字节数组)。
-
获取目标 IP(
InetAddress.getByName("192.168.1.66"))。 -
创建
DatagramPacket实例(封装数据、目标 IP、端口)。 -
调用
send()发送数据包。 -
关闭资源。
(2)多发多收(进阶版)
-
核心优化:通过
while死循环实现持续发送 / 接收。 -
客户端:循环读取用户输入,输入 “exit” 退出,否则发送数据。
-
服务端:循环调用
receive(),持续接收不同客户端的数据包(无需区分发送方,直接解析)。
4. 关键注意事项
-
服务端必须先启动,否则客户端发送的数据包会丢失。
-
接收端缓冲区大小建议 ≥ 发送端数据长度,避免数据截断。
-
多客户端发送时,服务端通过
DatagramPacket的getAddress()/getPort()识别发送方。
三、TCP 通信 —— 面向连接可靠传输
1. 核心特点
-
面向连接:通信前需通过 “三次握手” 建立连接,通信后通过 “四次挥手” 断开连接。
-
可靠传输:通过序列号、确认应答、重传机制确保数据不丢失、不重复、有序。
-
流式传输:数据以字节流形式传输,无大小限制(适合大文件传输)。
-
通信效率低:连接、确认开销大,延迟高于 UDP。
2. 核心机制 —— 三次握手与四次挥手
(1)三次握手(建立连接)
目的:确保通信双方的发送和接收能力正常,建立可靠连接。
-
客户端 → 服务端:发送连接请求(SYN 报文)。
-
服务端 → 客户端:响应请求(SYN+ACK 报文),确认自身接收正常。
-
客户端 → 服务端:发送确认报文(ACK 报文),确认自身发送正常,连接建立。
(2)四次挥手(断开连接)
目的:确保双方数据都已发送完毕,优雅断开连接。
-
客户端 → 服务端:发送断开请求(FIN 报文),表示客户端无数据发送。
-
服务端 → 客户端:响应确认(ACK 报文),表示收到断开请求,正在处理剩余数据。
-
服务端 → 客户端:发送断开确认(FIN 报文),表示服务端无数据发送。
-
客户端 → 服务端:发送最终确认(ACK 报文),连接断开。
3. 核心类与 API
(1)Socket——TCP 客户端
-
作用:创建客户端连接,与服务端建立通信管道,获取输入 / 输出流。
-
核心构造器:
-
Socket(String host, int port):根据服务端 IP / 域名和端口建立连接(连接失败抛异常)。
-
-
核心方法:
-
OutputStream getOutputStream():获取字节输出流(发送数据)。 -
InputStream getInputStream():获取字节输入流(接收数据)。 -
void close():关闭客户端连接(会触发四次挥手)。
-
(2)ServerSocket——TCP 服务端
-
作用:绑定端口,监听客户端连接请求,创建与客户端的通信管道。
-
核心构造器:
-
ServerSocket(int port):绑定指定端口,启动服务端。
-
-
核心方法:
-
Socket accept():阻塞等待客户端连接,连接成功返回Socket实例(与该客户端通信)。 -
void close():关闭服务端,停止监听。
-
4. 实现步骤
(1)一发一收(基础版)
服务端
-
创建
ServerSocket实例,绑定端口(如 9999)。 -
调用
accept()阻塞等待客户端连接,获取Socket实例。 -
通过
Socket获取InputStream,读取客户端数据。 -
处理数据(如打印)。
-
关闭
Socket和ServerSocket。
客户端
-
创建
Socket实例,连接服务端(IP + 端口)。 -
通过
Socket获取OutputStream,发送数据。 -
关闭
Socket。
(2)多发多收(进阶版)
-
核心优化:循环读取 / 发送数据,客户端输入 “exit” 退出。
-
注意:TCP 是流式传输,需约定 “结束标记”(如客户端发送 “exit” 时,服务端停止接收),避免
read()阻塞。
(3)多客户端并发处理(高级版)
-
问题:基础版服务端只有一个主线程,只能处理一个客户端,处理完才能接收下一个。
-
解决方案:主线程负责监听连接,每接收一个客户端连接,创建独立子线程(或线程池)处理通信。
-
实现步骤:
-
服务端主线程:循环调用
accept(),接收客户端Socket。 -
每次获取
Socket后,创建Thread或提交到线程池。 -
子线程中:通过
Socket与客户端通信(读 / 写数据)。 -
通信结束后,关闭
Socket。
-
5. BS 架构原理(TCP 应用延伸)
BS 架构本质是 “TCP 通信 + HTTP 协议”,浏览器作为客户端,服务端需遵循 HTTP 协议响应数据。
(1)HTTP 响应格式要求
服务端响应浏览器的数据必须符合以下格式,否则浏览器无法识别:
plaintext
HTTP/1.1 200 OK\r\n // 协议版本 + 状态码 + 状态描述 Content-Type: text/html; charset=UTF-8\r\n // 响应数据类型(网页) \r\n // 空行(分隔头信息和正文) <div style='color:red'>听黑马磊哥讲Java</div> // 响应正文(网页内容)
(2)线程池优化(高并发场景)
-
问题:多客户端(浏览器)请求时,频繁创建子线程会导致资源耗尽。
-
解决方案:使用线程池复用线程,控制最大线程数。
-
核心逻辑:服务端主线程接收浏览器连接后,将通信任务提交到线程池,线程池中的核心线程 / 临时线程处理请求。
四、综合实战 —— 局域网聊天室(CS 架构)
1. 需求说明
实现局域网内多用户即时通信:
-
客户端:GUI 界面(输入昵称、发送消息、显示聊天记录)。
-
服务端:支持多客户端并发连接,转发消息(广播给所有在线客户端)。
-
核心功能:昵称登录、消息发送 / 接收、时间戳显示、退出聊天。
2. 技术栈
-
网络通信:TCP 协议(可靠传输,确保消息不丢失)。
-
多线程:服务端线程池处理多客户端,客户端独立线程接收消息(避免 GUI 卡顿)。
-
GUI 编程:Java Swing(窗口、输入框、按钮、文本域)。
-
时间处理:
LocalDateTime(获取当前时间,添加消息时间戳)。 -
字符串操作:
StringBuilder(高效拼接聊天记录,避免 String 频繁拼接开销)。
3. 核心实现思路
(1)服务端
-
创建
ServerSocket,绑定端口(如 8888)。 -
初始化线程池(核心线程 5,最大线程 10)。
-
循环调用
accept(),接收客户端Socket。 -
将客户端通信任务提交到线程池:
-
读取客户端发送的消息(昵称 + 内容)。
-
广播消息到所有在线客户端(遍历客户端
Socket集合,发送消息)。
-
-
客户端断开连接时,从集合中移除,关闭
Socket。
(2)客户端
-
GUI 界面初始化:登录窗口(昵称输入)、聊天窗口(消息显示域、输入框、发送按钮)。
-
登录成功后,创建
Socket连接服务端。 -
启动独立线程:循环读取服务端广播的消息,更新 GUI 文本域(带时间戳)。
-
发送按钮绑定事件:读取输入框内容,发送给服务端,清空输入框。
-
退出时,关闭
Socket和线程。
4. 关键技术细节
-
消息格式:
昵称 + " " + 时间戳 + ":" + 消息内容(便于服务端解析和客户端显示)。 -
时间格式化:
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))。 -
线程安全:客户端 GUI 更新需在 EDT(事件调度线程)中执行,避免线程安全问题。
-
异常处理:网络中断时,捕获
IOException,提示用户重新连接。
五、核心对比与面试高频点
1. UDP vs TCP 深度对比
| 对比维度 | UDP | TCP |
|---|---|---|
| 连接方式 | 无连接 | 三次握手建立连接 |
| 可靠性 | 不可靠(无确认、重传) | 可靠(序列号、确认、重传) |
| 数据传输方式 | 数据包(离散) | 字节流(连续) |
| 数据限制 | 单次 ≤64KB | 无限制 |
| 通信效率 | 高(低延迟) | 低(连接 / 确认开销) |
| 核心类 | DatagramSocket、DatagramPacket | Socket、ServerSocket |
| 适用场景 | 直播、语音、游戏 | 网页、文件下载、支付 |
2. 面试高频问题
-
TCP 三次握手的目的是什么?
确保通信双方的发送和接收能力正常,避免无效数据传输(如服务端未就绪,客户端发送数据丢失)。
-
TCP 四次挥手为什么需要四次?
服务端收到断开请求后,可能仍有未发送完毕的数据,需先响应确认,待数据发送完后再发送断开请求,因此需四次交互。
-
UDP 为什么不能保证可靠传输?
无连接机制,发送方不关心接收方是否在线;无确认、重传、排序机制,数据可能丢失、乱序。
-
CS 和 BS 架构的核心区别?
客户端是否需要定制开发(CS 需开发,BS 复用浏览器)、功能扩展性(CS 强,BS 弱)、开发维护成本(CS 高,BS 低)。
-
TCP 服务端如何支持多客户端并发?
主线程监听连接,每接收一个客户端
Socket
,创建子线程或提交到线程池处理通信,避免主线程阻塞。
5 道 Java 网络编程中大厂面试题
面试题 1
请从 “连接方式、可靠性、数据传输、效率、适用场景” 五个维度对比 UDP 与 TCP 协议的核心差异,并结合实际业务场景说明:为什么视频直播选择 UDP 协议,而文件下载选择 TCP 协议?若需基于 UDP 实现可靠传输,需额外开发哪些核心机制?
面试题 2
TCP 协议的 “三次握手” 和 “四次挥手” 分别用于解决什么问题?请详细描述每一步的交互过程(包括发送的报文类型、双方状态变化),并解释:为什么三次握手不能简化为两次?四次挥手中 “TIME_WAIT” 状态的作用是什么?
面试题 3
网络编程三要素中的 “IP” 和 “端口” 分别承担什么角色?请说明内网 IP(如 192.168.1.100)与公网 IP 的区别,以及本机 IP(127.0.0.1)的特殊作用。若开发 TCP 服务端时出现 “端口已被占用” 异常,如何定位占用端口的进程?如何避免端口冲突?
面试题 4
基于 TCP 协议开发支持 1000 个客户端并发连接的聊天室服务端,需解决哪些核心问题?请说明服务端的架构设计(如线程模型选择)、关键技术(如线程池参数设置),并解释:为什么不推荐使用 “一个客户端一个线程” 的模型?如何实现客户端消息的广播功能?
面试题 5
BS 架构(浏览器 - 服务端)的通信本质是什么?请描述用户在浏览器输入 “http://localhost:8080” 到页面显示的完整流程(包括网络协议交互、数据格式要求)。若服务端返回的数据缺少 “HTTP/1.1 200 OK\r\n” 头信息,浏览器会出现什么问题?为什么?
面试题答案
面试题 1 答案
1. UDP 与 TCP 的五维度核心差异
| 对比维度 | UDP(用户数据报协议) | TCP(传输控制协议) |
|---|---|---|
| 连接方式 | 无连接:无需建立连接,直接封装数据包发送,发送前不确认接收方状态 | 面向连接:通过 “三次握手” 建立连接,确保双方收发能力正常 |
| 可靠性 | 不可靠:无确认机制,数据可能丢失、乱序;无重传、排序逻辑 | 可靠:通过序列号(确保有序)、确认应答(确保送达)、重传机制(丢失重发)保障数据完整 |
| 数据传输 | 数据包传输:单次传输≤64KB,数据离散封装为独立数据包 | 字节流传输:无大小限制,数据连续传输,需手动定义 “数据边界”(如分隔符) |
| 通信效率 | 高:无连接、确认开销,延迟低(适合实时场景) | 低:连接建立、确认、重传均有开销,延迟高(适合可靠场景) |
| 适用场景 | 容忍数据丢失的实时场景(视频直播、语音通话、游戏) | 需数据绝对可靠的场景(文件下载、网页访问、支付交易) |
2. 业务场景选择依据
(1)视频直播选 UDP 的原因
-
实时性优先:直播需低延迟(如体育赛事直播延迟需 < 3 秒),UDP 无连接开销,数据发送后无需等待确认,延迟远低于 TCP。
-
容忍数据丢失:视频帧丢失 1-2 帧不影响观看体验(人眼难以察觉),而 TCP 的重传机制会导致 “卡顿”(重传丢失帧时,后续帧需等待,产生延迟累积)。
-
数据量适配:视频流按帧封装为小数据包(通常 < 64KB),符合 UDP 单次传输限制,无需拆分流数据。
(2)文件下载选 TCP 的原因
-
可靠性优先:文件(如安装包、文档)需 100% 完整,若丢失数据会导致文件损坏(如 EXE 文件无法运行),TCP 的确认 + 重传机制确保数据无丢失。
-
无大小限制:文件大小可能达 GB 级,TCP 的字节流传输支持无限大小,无需手动拆分数据包。
3. 基于 UDP 实现可靠传输的额外机制
需手动开发 TCP 的核心可靠机制,典型方案包括:
-
确认应答机制:接收方收到 UDP 数据包后,向发送方回复 “确认报文”,发送方超时未收到确认则重传。
-
序列号与排序:为每个 UDP 数据包添加序列号,接收方按序列号重组数据,丢弃重复包。
-
超时重传:发送方维护 “超时计时器”,超时未收到确认则重传数据包(需避免频繁重传导致网络拥塞)。
-
流量控制:接收方根据自身处理能力(如缓冲区大小),向发送方反馈 “可接收数据量”,避免发送方发送过快导致缓冲区溢出。
面试题 2 答案
1. 三次握手与四次挥手的核心目的
-
三次握手:解决 “双方收发能力确认” 问题,确保客户端和服务端的 “发送器” 和 “接收器” 均正常,避免无效数据传输(如服务端未就绪,客户端发送数据丢失)。
-
四次挥手:解决 “数据完整性” 问题,确保双方已发送的所有数据都被接收,避免断开连接时残留数据未处理。
2. 三次握手详细过程(建立连接)
| 步骤 | 发送方 | 接收方 | 报文类型 | 核心目的 | 双方状态变化 |
|---|---|---|---|---|---|
| 1 | 客户端 | 服务端 | SYN(同步报文) | 客户端向服务端请求建立连接,携带 “客户端初始序列号” | 客户端:CLOSED→SYN_SENT;服务端:LISTEN→SYN_RCVD |
| 2 | 服务端 | 客户端 | SYN+ACK(同步 + 确认) | 服务端确认接收请求,携带 “服务端初始序列号” 和 “客户端序列号确认” | 服务端:SYN_RCVD→ESTABLISHED;客户端:SYN_SENT→ESTABLISHED(半连接) |
| 3 | 客户端 | 服务端 | ACK(确认报文) | 客户端确认接收服务端响应,完成连接建立 | 客户端:ESTABLISHED(全连接);服务端:ESTABLISHED(全连接) |
3. 四次挥手详细过程(断开连接)
| 步骤 | 发送方 | 接收方 | 报文类型 | 核心目的 | 双方状态变化 |
|---|---|---|---|---|---|
| 1 | 客户端 | 服务端 | FIN(结束报文) | 客户端通知服务端:“我已无数据发送,请求断开” | 客户端:ESTABLISHED→FIN_WAIT_1 |
| 2 | 服务端 | 客户端 | ACK(确认报文) | 服务端确认接收断开请求,“我知道了,正在处理剩余数据” | 服务端:ESTABLISHED→CLOSE_WAIT;客户端:FIN_WAIT_1→FIN_WAIT_2 |
| 3 | 服务端 | 客户端 | FIN+ACK(结束 + 确认) | 服务端通知客户端:“我已无数据发送,可断开” | 服务端:CLOSE_WAIT→LAST_ACK |
| 4 | 客户端 | 服务端 | ACK(确认报文) | 客户端确认接收,等待 2MSL 后断开 | 客户端:FIN_WAIT_2→TIME_WAIT→CLOSED;服务端:LAST_ACK→CLOSED |
4. 关键问题解释
(1)三次握手不能简化为两次的原因
若简化为 “客户端发 SYN→服务端发 SYN+ACK”,服务端无法确认客户端能否接收自身的 SYN 报文:
-
场景:客户端发送的 SYN 报文因网络延迟到达服务端,客户端已超时重发并建立新连接;延迟的 SYN 报文到达后,服务端发送 SYN+ACK,但客户端认为该报文无效(已建立新连接),不会回复 ACK,服务端会持续重发 SYN+ACK,造成资源浪费。
-
三次握手的第三次 ACK,本质是 “客户端向服务端确认:我能接收你的数据”,确保双方收发能力均正常,避免 “单向连接”。
(2)TIME_WAIT 状态的作用
客户端发送第四次 ACK 后,进入 TIME_WAIT 状态(默认等待 2MSL,MSL 是报文最大生存时间,通常为 1 分钟),核心作用:
-
确保服务端收到最后一次 ACK:若第四次 ACK 丢失,服务端会重发 FIN 报文,客户端在 TIME_WAIT 内可重新回复 ACK,避免服务端无法正常断开。
-
避免旧连接报文干扰新连接:等待 2MSL 可确保网络中所有旧连接的残留报文(如延迟的 SYN、FIN)已失效,新连接使用相同端口时不会被旧报文干扰。
面试题 3 答案
1. IP 与端口的核心角色
-
IP 地址:定位 “网络中的设备”,是设备在网络中的唯一标识(如家里的路由器 IP 是 192.168.1.1,服务器公网 IP 是 39.106.xx.xx),确保数据能发送到正确的物理设备。
-
端口:定位 “设备中的程序”,是应用程序的唯一标识(如 Tomcat 用 8080,MySQL 用 3306),确保数据能交付到设备上的正确应用(避免浏览器数据被 MySQL 接收)。
2. 内网 IP、公网 IP 与本机 IP 的区别
| IP 类型 | 定义 | 访问范围 | 典型场景 |
|---|---|---|---|
| 内网 IP | 局域网内部分配的 IP(如 192.168.x.x、10.x.x.x) | 仅局域网内可访问,无法被外网直接访问 | 家庭 / 公司内部设备(手机、电脑) |
| 公网 IP | 互联网分配的唯一 IP(需运营商申请) | 全球可访问,是设备在互联网的唯一标识 | 服务器(如百度服务器 IP:180.101.xx.xx) |
| 本机 IP | 127.0.0.1(或localhost),指向当前设备 | 仅本机内部可访问,不经过网络硬件 | 本地程序测试(如 Tomcat 本地访问http://localhost:8080) |
关键关联:内网设备需通过 “路由器 NAT 转发” 使用公网 IP 访问互联网(如手机连 WiFi 时,用路由器的公网 IP 访问百度)。
3. 端口占用问题的定位与解决
(1)定位占用端口的进程(Windows 系统)
-
打开命令提示符(CMD),输入命令:
netstat -ano | findstr "端口号"
(如查找 8080 端口:
netstat -ano | findstr "8080"
)。
-
输出结果中,最后一列数字是进程 ID(PID)。
-
-
打开 “任务管理器”→“详细信息”,按 PID 排序,找到对应进程,即可知道哪个程序占用端口(如 java.exe 占用 8080,说明 Tomcat 未关闭)。
(2)避免端口冲突的方案
-
动态端口选择:客户端程序不指定固定端口(如 UDP 客户端用
new DatagramSocket(),系统自动分配 49152~65535 的动态端口)。 -
配置文件指定端口:服务端程序将端口配置在配置文件(如 application.properties),部署时修改为未占用端口。
-
端口占用检测:启动服务前,通过代码检测端口是否可用(如尝试绑定端口,抛异常则说明占用)。
-
常用端口约定:遵循端口分类规则,开发程序使用 1024~49151 的注册端口,避免使用周知端口(如 80、21)。
面试题 4 答案
1. 1000 客户端并发需解决的核心问题
-
线程资源耗尽:1000 个客户端若用 “一个客户端一个线程”,会创建 1000 个线程,导致 JVM 栈内存溢出(每个线程默认栈内存 1MB,1000 个线程占 1GB)。
-
并发安全:消息广播时需遍历所有客户端 Socket,多线程操作 Socket 集合需保证线程安全(避免 ConcurrentModificationException)。
-
连接管理:客户端异常断开(如网络中断)时,需及时关闭 Socket,移除客户端集合,避免资源泄漏。
-
消息边界:TCP 是流式传输,需定义消息分隔符(如 “\n”),避免多个客户端的消息粘连。
2. 服务端架构设计(线程池模型)
推荐使用 “主线程监听连接 + 线程池处理通信” 的模型,核心架构如下:
plaintext
[主线程]:循环调用ServerSocket.accept(),接收客户端Socket → 将Socket封装为“通信任务”提交到线程池 [线程池]:核心线程(如20)+ 临时线程(如80)+ 任务队列(如100),处理客户端消息的读/写/广播 [客户端集合]:用ConcurrentHashMap存储在线Socket(线程安全),key为客户端ID,value为Socket
线程池参数设置依据(以 1000 并发为例)
-
核心线程数(corePoolSize):设为 CPU 核心数 ×2(如 8 核 CPU 设 16),确保 CPU 资源充分利用,避免线程切换过多。
-
最大线程数(maximumPoolSize):设为 100(核心 20 + 临时 80),足够处理 1000 并发(多数客户端处于 “等待消息” 状态,线程池可复用线程)。
-
任务队列(workQueue):用 LinkedBlockingQueue,容量设 100,缓冲突发的连接请求,避免直接触发拒绝策略。
-
拒绝策略(handler):用 ThreadPoolExecutor.CallerRunsPolicy,主线程处理超出线程池容量的任务,避免客户端连接被拒绝。
3. 不推荐 “一个客户端一个线程” 的原因
-
资源开销大:线程是重量级资源,创建 / 销毁需操作系统内核态切换,1000 个线程会占用大量内存(栈内存 + 线程控制块),导致 JVM 内存紧张。
-
CPU 利用率低:多数客户端处于 “空闲等待消息” 状态(如聊天室用户未发送消息时,线程阻塞在 read ()),大量空闲线程会导致 CPU 频繁切换(上下文切换开销),利用率降至 30% 以下。
-
维护成本高:需手动管理 1000 个线程的启动 / 关闭,异常处理复杂(如线程抛出未捕获异常会导致客户端连接断开)。
4. 客户端消息广播功能实现
-
消息封装:客户端发送消息时,按 “客户端 ID | 消息内容 | 时间戳” 格式封装(如 “user1|Hello|2024-10-01 15:30:00”),服务端解析后拼接为广播内容。
-
线程安全集合:用 ConcurrentHashMap<String, Socket> 存储在线客户端,key 为客户端 ID,value 为 Socket,确保多线程遍历 / 修改安全。
-
广播逻辑
:
java
运行
// 遍历所有在线客户端,发送消息 public void broadcast(String message) { // 遍历ConcurrentHashMap的values(),避免ConcurrentModificationException for (Socket clientSocket : clientMap.values()) { try (OutputStream os = clientSocket.getOutputStream()) { os.write((message + "\n").getBytes(StandardCharsets.UTF_8)); // 加\n作为消息边界 os.flush(); } catch (IOException e) { // 客户端异常断开,移除集合 clientMap.remove(getClientId(clientSocket)); } } } -
异常处理:广播时若捕获 IOException(如客户端断开),立即从集合中移除该 Socket,关闭资源,避免后续广播持续失败。
面试题 5 答案
1. BS 架构的通信本质
BS 架构本质是 “TCP 通信 + HTTP 协议规范”:
-
浏览器是 “通用 TCP 客户端”,无需开发,自动遵循 HTTP 协议发送请求;
-
服务端是 “TCP 服务端”,需按 HTTP 协议格式响应数据,确保浏览器能解析(如返回 HTML、CSS、JS)。
-
核心:HTTP 协议是 “TCP 之上的应用层协议”,定义了请求 / 响应的数据格式,而实际数据传输依赖 TCP 的可靠连接。
2. 输入 URL 到页面显示的完整流程
以 “http://localhost:8080” 为例,流程分为 6 步:
-
URL 解析:浏览器解析 URL,提取 “协议(HTTP)、域名(localhost)、端口(8080)、路径(/)”。
-
DNS 域名解析:localhost是本机域名,解析为 127.0.0.1(若为公网域名如www.baidu.com,需向 DNS 服务器查询公网 IP)。
-
TCP 三次握手:浏览器作为 TCP 客户端,与 127.0.0.1:8080 的服务端建立 TCP 连接(三次握手流程见面试题 2)。
-
HTTP 请求发送
:浏览器按 HTTP 协议格式发送请求报文:
plaintext
GET / HTTP/1.1\r\n // 请求方法(GET)+ 路径(/)+ 协议版本(HTTP/1.1) Host: localhost:8080\r\n // 服务端地址 User-Agent: Chrome/120.0.0.1\r\n // 浏览器信息 \r\n // 空行(分隔请求头和请求体)
-
服务端处理与响应
:服务端(如 Tomcat)接收请求,处理后按 HTTP 协议返回响应报文:
plaintext
HTTP/1.1 200 OK\r\n // 协议版本 + 状态码(200=成功)+ 状态描述 Content-Type: text/html; charset=UTF-8\r\n // 响应数据类型(HTML) \r\n // 空行 <html><body>欢迎访问</body></html> // 响应正文(HTML内容)
-
TCP 四次挥手与页面渲染:浏览器接收响应,关闭 TCP 连接(四次挥手),解析 HTML/CSS/JS,渲染页面并显示。
3. 缺少 HTTP 头信息的问题
若服务端返回的数据缺少 “HTTP/1.1 200 OK\r\n” 头信息(如直接返回...),浏览器会无法解析数据,显示 “无法访问此网站” 或空白页面,原因如下:
-
浏览器识别响应的前提是 “符合 HTTP 协议格式”:浏览器默认按 HTTP 协议解析 TCP 数据,若缺少协议头,无法判断数据类型(是 HTML、图片还是二进制文件),也无法确认请求是否成功(状态码缺失)。
-
协议头是 “数据解析的标识”:HTTP 头中的
Content-Type(如 text/html)告诉浏览器 “用 HTML 引擎渲染数据”,缺少头信息时,浏览器无法确定渲染方式,只能拒绝显示。
更多推荐


所有评论(0)