本章节内容是给后续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)单发单收(基础版)
服务端(接收方)
  1. 创建 DatagramSocket 实例,绑定端口(如 8888)。

  2. 创建字节缓冲区(如 byte[] buf = new byte[1024])。

  3. 创建 DatagramPacket 实例(接收数据)。

  4. 调用 receive() 阻塞接收数据。

  5. 解析数据包(获取数据、发送方 IP / 端口)。

  6. 关闭资源。

客户端(发送方)
  1. 创建 DatagramSocket 实例(随机端口)。

  2. 准备发送数据(转为字节数组)。

  3. 获取目标 IP(InetAddress.getByName("192.168.1.66"))。

  4. 创建 DatagramPacket 实例(封装数据、目标 IP、端口)。

  5. 调用 send() 发送数据包。

  6. 关闭资源。

(2)多发多收(进阶版)
  • 核心优化:通过 while 死循环实现持续发送 / 接收。

  • 客户端:循环读取用户输入,输入 “exit” 退出,否则发送数据。

  • 服务端:循环调用 receive(),持续接收不同客户端的数据包(无需区分发送方,直接解析)。

4. 关键注意事项

  • 服务端必须先启动,否则客户端发送的数据包会丢失。

  • 接收端缓冲区大小建议 ≥ 发送端数据长度,避免数据截断。

  • 多客户端发送时,服务端通过 DatagramPacketgetAddress()/getPort() 识别发送方。


三、TCP 通信 —— 面向连接可靠传输

1. 核心特点

  • 面向连接:通信前需通过 “三次握手” 建立连接,通信后通过 “四次挥手” 断开连接。

  • 可靠传输:通过序列号、确认应答、重传机制确保数据不丢失、不重复、有序。

  • 流式传输:数据以字节流形式传输,无大小限制(适合大文件传输)。

  • 通信效率低:连接、确认开销大,延迟高于 UDP。

2. 核心机制 —— 三次握手与四次挥手

(1)三次握手(建立连接)

目的:确保通信双方的发送和接收能力正常,建立可靠连接。

  1. 客户端 → 服务端:发送连接请求(SYN 报文)。

  2. 服务端 → 客户端:响应请求(SYN+ACK 报文),确认自身接收正常。

  3. 客户端 → 服务端:发送确认报文(ACK 报文),确认自身发送正常,连接建立。

(2)四次挥手(断开连接)

目的:确保双方数据都已发送完毕,优雅断开连接。

  1. 客户端 → 服务端:发送断开请求(FIN 报文),表示客户端无数据发送。

  2. 服务端 → 客户端:响应确认(ACK 报文),表示收到断开请求,正在处理剩余数据。

  3. 服务端 → 客户端:发送断开确认(FIN 报文),表示服务端无数据发送。

  4. 客户端 → 服务端:发送最终确认(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)一发一收(基础版)
服务端
  1. 创建 ServerSocket 实例,绑定端口(如 9999)。

  2. 调用 accept() 阻塞等待客户端连接,获取 Socket 实例。

  3. 通过 Socket 获取 InputStream,读取客户端数据。

  4. 处理数据(如打印)。

  5. 关闭 SocketServerSocket

客户端
  1. 创建 Socket 实例,连接服务端(IP + 端口)。

  2. 通过 Socket 获取 OutputStream,发送数据。

  3. 关闭 Socket

(2)多发多收(进阶版)
  • 核心优化:循环读取 / 发送数据,客户端输入 “exit” 退出。

  • 注意:TCP 是流式传输,需约定 “结束标记”(如客户端发送 “exit” 时,服务端停止接收),避免 read() 阻塞。

(3)多客户端并发处理(高级版)
  • 问题:基础版服务端只有一个主线程,只能处理一个客户端,处理完才能接收下一个。

  • 解决方案:主线程负责监听连接,每接收一个客户端连接,创建独立子线程(或线程池)处理通信。

  • 实现步骤:

    1. 服务端主线程:循环调用 accept(),接收客户端 Socket

    2. 每次获取 Socket 后,创建 Thread 或提交到线程池。

    3. 子线程中:通过 Socket 与客户端通信(读 / 写数据)。

    4. 通信结束后,关闭 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)服务端
  1. 创建 ServerSocket,绑定端口(如 8888)。

  2. 初始化线程池(核心线程 5,最大线程 10)。

  3. 循环调用 accept(),接收客户端 Socket

  4. 将客户端通信任务提交到线程池:

    • 读取客户端发送的消息(昵称 + 内容)。

    • 广播消息到所有在线客户端(遍历客户端 Socket 集合,发送消息)。

  5. 客户端断开连接时,从集合中移除,关闭 Socket

(2)客户端
  1. GUI 界面初始化:登录窗口(昵称输入)、聊天窗口(消息显示域、输入框、发送按钮)。

  2. 登录成功后,创建 Socket 连接服务端。

  3. 启动独立线程:循环读取服务端广播的消息,更新 GUI 文本域(带时间戳)。

  4. 发送按钮绑定事件:读取输入框内容,发送给服务端,清空输入框。

  5. 退出时,关闭 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 的核心可靠机制,典型方案包括:

  1. 确认应答机制:接收方收到 UDP 数据包后,向发送方回复 “确认报文”,发送方超时未收到确认则重传。

  2. 序列号与排序:为每个 UDP 数据包添加序列号,接收方按序列号重组数据,丢弃重复包。

  3. 超时重传:发送方维护 “超时计时器”,超时未收到确认则重传数据包(需避免频繁重传导致网络拥塞)。

  4. 流量控制:接收方根据自身处理能力(如缓冲区大小),向发送方反馈 “可接收数据量”,避免发送方发送过快导致缓冲区溢出。

面试题 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 分钟),核心作用:

  1. 确保服务端收到最后一次 ACK:若第四次 ACK 丢失,服务端会重发 FIN 报文,客户端在 TIME_WAIT 内可重新回复 ACK,避免服务端无法正常断开。

  2. 避免旧连接报文干扰新连接:等待 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 系统)
  1. 打开命令提示符(CMD),输入命令:

    netstat -ano | findstr "端口号"

    (如查找 8080 端口:

    netstat -ano | findstr "8080"

    )。

    • 输出结果中,最后一列数字是进程 ID(PID)

  2. 打开 “任务管理器”→“详细信息”,按 PID 排序,找到对应进程,即可知道哪个程序占用端口(如 java.exe 占用 8080,说明 Tomcat 未关闭)。

(2)避免端口冲突的方案
  1. 动态端口选择:客户端程序不指定固定端口(如 UDP 客户端用new DatagramSocket(),系统自动分配 49152~65535 的动态端口)。

  2. 配置文件指定端口:服务端程序将端口配置在配置文件(如 application.properties),部署时修改为未占用端口。

  3. 端口占用检测:启动服务前,通过代码检测端口是否可用(如尝试绑定端口,抛异常则说明占用)。

  4. 常用端口约定:遵循端口分类规则,开发程序使用 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. 客户端消息广播功能实现

  1. 消息封装:客户端发送消息时,按 “客户端 ID | 消息内容 | 时间戳” 格式封装(如 “user1|Hello|2024-10-01 15:30:00”),服务端解析后拼接为广播内容。

  2. 线程安全集合:用 ConcurrentHashMap<String, Socket> 存储在线客户端,key 为客户端 ID,value 为 Socket,确保多线程遍历 / 修改安全。

  3. 广播逻辑

    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));
            }
        }
    }

  4. 异常处理:广播时若捕获 IOException(如客户端断开),立即从集合中移除该 Socket,关闭资源,避免后续广播持续失败。

面试题 5 答案

1. BS 架构的通信本质

BS 架构本质是 “TCP 通信 + HTTP 协议规范”:

  • 浏览器是 “通用 TCP 客户端”,无需开发,自动遵循 HTTP 协议发送请求;

  • 服务端是 “TCP 服务端”,需按 HTTP 协议格式响应数据,确保浏览器能解析(如返回 HTML、CSS、JS)。

  • 核心:HTTP 协议是 “TCP 之上的应用层协议”,定义了请求 / 响应的数据格式,而实际数据传输依赖 TCP 的可靠连接。

2. 输入 URL 到页面显示的完整流程

以 “http://localhost:8080” 为例,流程分为 6 步:

  1. URL 解析:浏览器解析 URL,提取 “协议(HTTP)、域名(localhost)、端口(8080)、路径(/)”。

  2. DNS 域名解析localhost是本机域名,解析为 127.0.0.1(若为公网域名如www.baidu.com,需向 DNS 服务器查询公网 IP)。

  3. TCP 三次握手:浏览器作为 TCP 客户端,与 127.0.0.1:8080 的服务端建立 TCP 连接(三次握手流程见面试题 2)。

  4. 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  // 空行(分隔请求头和请求体)

  5. 服务端处理与响应

    :服务端(如 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内容)

  6. TCP 四次挥手与页面渲染:浏览器接收响应,关闭 TCP 连接(四次挥手),解析 HTML/CSS/JS,渲染页面并显示。

3. 缺少 HTTP 头信息的问题

若服务端返回的数据缺少 “HTTP/1.1 200 OK\r\n” 头信息(如直接返回...),浏览器会无法解析数据,显示 “无法访问此网站” 或空白页面,原因如下:

  • 浏览器识别响应的前提是 “符合 HTTP 协议格式”:浏览器默认按 HTTP 协议解析 TCP 数据,若缺少协议头,无法判断数据类型(是 HTML、图片还是二进制文件),也无法确认请求是否成功(状态码缺失)。

  • 协议头是 “数据解析的标识”:HTTP 头中的Content-Type(如 text/html)告诉浏览器 “用 HTML 引擎渲染数据”,缺少头信息时,浏览器无法确定渲染方式,只能拒绝显示。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐