简介本资源是一套面向计算机专业本科生的Java毕业设计完整交付包聚焦网络通信系统开发实践帮助学生系统掌握Socket编程、多线程并发处理、IO/NIO数据传输、常见设计模式应用及异常健壮性设计等核心能力。压缩包共81个文件含10个核心Java源码文件涵盖Server/Client主逻辑、线程管理、消息协议解析等、45个编译后class文件、2份关键文档毕业论文.doc与开题报告.doc、11张系统界面与架构示意图jpg格式以及DLL动态库、Eclipse项目配置文件.project/.classpath等工程必需组件整体仅562KB轻量易部署。已有413人学习下载资源结构清晰源码注释完整论文与报告内容翔实覆盖需求分析、技术选型、模块设计、测试验证全流程可直接用于答辩参考、代码复现或二次开发拓展。 很多计算机专业的同学都会在选题阶段纠结一个问题做什么题目既能覆盖核心知识点、代码量可控又能顺利过答辩如果你点进这篇文章说明你大概率也在考虑或已经选定了“Java网络通信系统”这个题目并且手头可能已经有了一套包含论文、源代码和开题报告的完整资料包。我当年带毕设时见过太多类似的项目也帮忙改过不少今天就把这个题目背后真正值得做的点、源码里容易踩的坑、以及从开题到答辩的完整思路从头到尾给你捋一遍。这篇内容不是让你直接“交差”而是帮你真正搞清楚一套Java网络通信系统该如何研究与开发就算你手里已经有一份现成的zip包也能通过这篇文章判断它的质量、补齐它的短板甚至把它改造成能在答辩时讲得头头是道的项目。1. 项目整体设计与选型思路1.1 这个题目到底在考核什么能力网络通信系统这个题目本质上考核的是三块能力。第一块是Java基础包括Socket编程、多线程、I/O模型、异常处理这些都是Java开发者的基本功也是面试和答辩时最容易被追问的点。第二块是系统设计能力包括通信协议怎么定义、消息格式怎么设计、客户端和服务端的交互模型怎么搭这部分考察的不是“会不会调API”而是“能不能设计出一个可用、稳定、可扩展的系统”。第三块是工程实践能力包括项目结构、代码规范、配置文件管理、日志输出甚至是打包发布。为什么很多同学在做这个题目时觉得“很空”因为网络通信系统的范围实在太大了你可以做简单的局域网聊天室也可以做基于Netty的高性能网关还可以做类似即时通讯软件的大工程。关键在于选题范围是否可控、技术栈是否清晰、工作量是否合适。一套优质的资料包必然在设计之初就固定了系统的边界比如“基于Java Socket的C/S聊天系统”或者“基于Netty的多人即时通信系统”而不是泛泛地称为“网络通信系统”。1.2 为什么我建议做C/S架构而不是B/S架构大多数毕业设计级别的Java网络通信系统都采用C/S架构也就是客户端-服务端模式。原因很简单B/S架构拿浏览器当客户端很多网络通信的细节被浏览器屏蔽掉了你根本体验不到Socket编程的核心过程而C/S架构要求你亲手写客户端能完整地走一遍“建立连接—传输数据—断开连接”的流程无论是答辩演示还是论文描述都有肉眼可见的实物。具体来说C/S架构下常见的形态有以下几种命令行版的Socket聊天程序适合快速验证通信原理Swing/JavaFX图形界面的聊天室适合做功能完整的桌面应用Android客户端Java服务端的移动端聊天系统适合想往移动方向发展的同学NettyWebSocket的Web即时通信系统适合基础较好、希望展示高阶框架能力的同学。如果你拿到的题目包是“Java网络通信系统的研究与开发”打开源码后先看它的包结构和主类大概率就能判断它是哪种形态。一个负责任的项目服务端会有ServerSocket启动类或者Netty的ServerBootstrap配置客户端会有连接管理器交互界面上至少要有消息发送框和消息展示区。1.3 技术选型原生Socket还是Netty这是做这个题目最先要做的决策。原生Socketjava.net包下的Socket、ServerSocket是Java网络编程的地基学的时候必须搞懂它的工作原理比如TCP三次握手、输入输出流、阻塞式I/O。如果你只掌握到调API的层面答辩时老师一问你“Socket底层是怎么接收数据的”你就很容易卡壳。但真正设计一个略具规模的通信系统时原生Socket的弊端就暴露了每个客户端一个线程线程数一多就出问题处理粘包拆包要自己写逻辑断线重连、心跳检测也要自己实现。这些工作不是不能做而是会占用大量时间而且代码容易出bug。所以我的建议是如果你毕业设计时间在3个月以上或你已经有较好的Java基础直接使用Netty框架。Netty做网络通信系统是工业级首选基于NIO、事件驱动模型内置了编解码器、粘包拆包解决方案、心跳机制、线程池管理开发效率和质量都有明显优势。而且Netty本身就是面试高频考点写在简历里比“熟练使用Socket”更有含金量。当然如果你是Java基础比较薄弱、时间又紧的同学那就老老实实用原生Socket做一套简单的聊天系统把原理吃透同样能过答辩。最怕的就是“半吊子”写了Socket但说不清楚原理或者抄了Netty项目但不懂事件循环是什么这就是给自己挖坑。2. 核心模块拆解与关键技术实现2.1 通信协议的设计比写代码更重要网络通信系统最容易忽略但最核心的部分是通信协议的设计。这里的“协议”不是在说TCP/IP那套底层协议而是指应用层协议——通信双方约定好的消息格式。很多同学写聊天系统时喜欢直接用writeUTF和readUTF一个字符串发过去另一端直接读。这种方式做Demo没问题但一旦消息类型多了比如登录请求、聊天消息、文件传输、心跳包、下线通知就会陷入“如何区分这条消息是聊天还是心跳”的困境。比较推荐的做法是定义一个统一的消息数据类比如Message内部包含消息类型type、发送者、接收者、消息内容、时间戳等字段再用Java对象序列化或者JSON序列化转换为字节流传输。如果使用Netty配合Protobuf或JSON编解码器是标准操作。举个例子一个简单的消息类设计可以是public class Message implements Serializable { private String type; // 消息类型LOGIN / CHAT / FILE / HEARTBEAT / LOGOUT private String from; // 发送者ID或用户名 private String to; // 接收者ID私聊时有效群聊为null private String content; // 消息内容 private long timestamp; // 时间戳 // 无参构造、有参构造、getter/setter省略 }这样做的好处非常多。首先代码逻辑清晰服务端拿到Message对象后只要判断type字段就知道该把它路由给谁、要不要存库、要不要回执。其次后续扩展方便比如你想增加“文件发送”功能只需要在type里增加一个枚举值并在处理逻辑里加一个分支即可不需要改动通信主链路。在答辩或者面试时你能够主动说出“我设计了一套应用层协议包含消息类型、消息体、扩展字段”这种话比“我用Socket传了字符串”要高级一个维度。2.2 Socket连接管理与线程模型无论是用原生Socket还是Netty连接管理都是系统稳定性的生命线。如果是原生Socket最常见的方案是“每客户端一个线程”服务端每接受一个连接就创建一个新的ClientHandler线程在线程内循环读取客户端发来的消息。示例代码核心思路ServerSocket serverSocket new ServerSocket(8888); while (running) { Socket socket serverSocket.accept(); // 连接数控制逻辑比如当前连接数是否达到上限 ExecutorService pool Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); pool.execute(new ClientHandler(socket, this)); }这里有个坑在实际项目里直接用Executors.newFixedThreadPool虽然方便但推荐使用ThreadPoolExecutor手动配置核心线程数、最大线程数、队列容量、拒绝策略这样才能掌握线程池的真实行为也是答辩时的加分项。线程数不是越多越好I/O密集型任务是线程数为CPU核心数的2倍左右比较合理如果线程过多操作系统光切换线程上下文就耗掉大量性能。用Netty的话线程模型就简单清晰得多BossGroup负责接收连接WorkerGroup负责处理I/O事件默认的EventLoopGroup线程数就是CPU核数的2倍。服务端通过addLast添加各种ChannelHandler把业务逻辑分解成一个个独立的处理器。2.3 消息编码解码与粘包拆包处理这个知识点是网络通信系统绕不开的一道坎也是答辩时老师最爱问的“进阶题”。如果你用的是原生Socket传输的数据是字节流对方怎么知道你发的那一堆字节里包含了多少条消息如果连续发送两条消息接收方可能一次就读到了两段数据也可能一段数据被拆成了两次读取——这就是粘包和半包。应对方案通常有三种固定长度每条消息定长不足时补位简单但对空间浪费较大特殊分隔符比如每条消息后面加一个换行符或者“#End#”标记服务端按分隔符切分要注意消息内容本身不能包含分隔符长度前缀先发送4个字节的int表示消息体的长度然后再发送消息体接收方先读长度再按长度读消息体。这三种方案中长度前缀最可靠也是Netty内置编码器LengthFieldBasedFrameDecoder和LengthFieldPrepender的实现原理。你在论文中如果能把粘包拆包的处理机制写清楚无论是开题报告还是答辩都会显得非常有理论深度。2.4 心跳机制与断线重连网络通信系统中最常见的问题就是“连接假死”客户端和服务端之间的物理链路还在但有一方已经崩溃或网络异常TCP连接并不会立刻断开导致服务器端维护着一个已经没用的连接资源浪费内存和句柄。解决办法就是心跳机制。所谓心跳就是通信双方每隔一段时间发送一个很小的探测包如果多次没收到对方的回应就判定连接已死然后清理资源。实现方式有两种定时发送心跳包可以是客户端主动周期性地发送心跳包服务端如果连续N次没收到就关闭通道也可以在Netty中使用IdleStateHandler配置读空闲、写空闲或读写空闲的阈值在handler里处理超时事件。断线重连则是客户端的行为客户端检测到与服务端的连接断开后不是直接退出程序而是通过指数退避的策略重新尝试连接。指数退避的意思是不停地重试但每次重试的等待时间都越来越长比如1秒、2秒、4秒、8秒直到达到最大间隔。这个策略能让服务器在故障恢复后有富余的容量接受大量客户端的重连请求不至于瞬间被重连风暴打垮。2.5 数据存储与消息持久化聊天记录和历史消息是很多网络通信系统的隐藏需求。很多初学者以为通信系统只是在内存里转发消息就行了但实际上用户关闭客户端再打开之前的聊天记录应该还在。数据存储方案的选择上最低成本的是用文件存储也就是把消息追加写入日志文件启动时读入内存其次是SQLite或嵌入式H2数据库最常见的是用MySQL建一张消息记录表包含id、from_user、to_user、content、create_time等字段服务端收到消息后异步写库。要注意的是消息写库如果放在通信的关键路径上会显著增加响应延迟所以通常采用异步方式先把消息转发给目标客户端再提交一条日志或事务到消息队列最后由一个独立的线程池批量入库。3. 从开发到交付实操过程与项目落地3.1 一次典型的项目开发流程如果你现在拿到的是别人做好的项目或者你打算自己从零写下面这个开发流程都可以作为参考。第一步环境准备。JDK版本建议使用JDK 8或JDK 11这两个版本最稳定且大多数学校的老机器不会出现版本兼容问题。IDE用IntelliJ IDEA构建工具选Maven如果项目里有Netty依赖Maven中央仓库直接拉取即可。注意配置好本地仓库镜像不然依赖下载能卡到怀疑人生。第二步搭项目骨架。一个规范的Maven工程结构是src/main/java存放源代码下面按包名组织——com.example.communcation.server存放服务端代码com.example.communcation.client存放客户端代码com.example.communcation.common存放公共类比如消息定义、常量、工具类。这样的分包方式在答辩时看代码会很舒服老师一眼就能看出你的代码结构是经过设计的。第三步先做端到端的最小Demo。不要一开始就做界面先写一个服务端类启动后监听8888端口再写一个最简单的客户端往服务端发送一条字符串消息服务端收到后打印出来然后把消息原样返回。这一步能验证开发环境和工作原理是后面所有功能的地基。第四步逐步叠加功能。加功能时遵循“小步快跑”的原则先做登录再做私聊再做群聊再做文件传输最后再加心跳和断线重连。每加一个功能就跑一遍完整流程确保前一个功能没被改坏。第五步做界面。如果用的是Swing就用JFrameJTextAreaJTextField做一个最简单的聊天窗口如果用JavaFX结构和样式会更好看但上手成本稍高。我个人比较推荐JavaFX因为它的CSS样式能让界面看起来现代化不少也更好讲出亮点。3.2 开题报告和论文的撰写技巧这部分虽然听起来像“文书工作”但很多同学就是在论文这里栽了跟头。你需要明确一点毕业设计论文不是代码文档而是“系统研究”的学术化说明。因此即便你实现的是一个小型聊天系统也要按“需求分析—系统设计—系统实现—系统测试”的逻辑去写。开题报告的重点在于研究背景、研究内容、技术路线和预期成果。研究背景可以从互联网时代通信需求切入引出Java平台在网络通信方向的优势技术路线画一张从Socket到NIO到Netty的演进图预期成果要具体一点比如“实现一个支持多客户端同时在线的文本聊天系统”就比“实现一个网络通信系统”好得多。论文核心章节建议这样排第一章 绪论写研究背景、国内外研究现状、目的和意义第二章 相关技术介绍写Java、TCP/IP、Socket、多线程、Netty如果用了第三章 系统需求分析写功能需求、非功能需求、用例图第四章 系统设计写总体架构、模块划分、数据库设计、通信协议设计第五章 系统实现写每个模块的核心代码和运行效果截图第六章 系统测试写功能测试用例表、性能测试结果、测试结论。这里有个很现实的经验系统测试章节是大部分同学偷懒的地方但其实它是最好写的。不要用“系统功能正常”这种话而是列出测试用例表用例编号、测试步骤、预期结果、实际结果、是否通过这一张表就能凑出两页纸而且显得非常专业。3.3 源代码中的易炸点自查如果你拿到的zip包里已经有一份源代码建议打开后重点检查下面几个位置第一检查Socket关闭和流的关闭。很多代码写了socket.close()但没写inputStream.close()和outputStream.close()或者没有用try-with-resources语法运行几次后就会出现文件句柄泄漏。第二检查是否有全局异常拦截。服务端启动后如果某个客户端异常断开默认会抛SocketException导致整个服务端崩溃。正确做法是在读取循环外加try-catch在finally里移出连接并关闭资源。第三检查是否硬编码了IP和端口。客户端连接地址写死了127.0.0.1和8888虽然本机测试没问题但答辩展示时如果需要连接另一台机器就得改代码重编译非常尴尬。更好的做法是写在配置文件里通过Properties读取。第四检查消息处理和UI是否跑在同一个线程。Swing和JavaFX的UI操作都必须发生在事件调度线程中如果在子线程里更新UI轻则界面无响应重则直接抛异常。常见解决方法是使用SwingUtilities.invokeLater或者Platform.runLater把UI更新操作扔回UI线程。3.4 打包与部署演示的准备工作毕业设计答辩一般需要现场演示提前把环境准备好可以避免很多意外。如果你用的是Maven项目可以在pom.xml里配置maven-shade-plugin或maven-assembly-plugin把依赖打包进一个可执行的Fat Jar。这样答辩机器上只要有JDK就能跑不用配Netty依赖。服务端和客户端各自打包成一个Jar包。为了方便演示可以准备两台电脑或者在一台电脑上开两个窗口——一个窗口跑服务端另一个窗口跑两个客户端实例。注意聊天系统在演示时最好先展示“两人私聊”再展示“群聊”最后演示“有用户断开后服务端不崩溃”这样整个流程就是一串逻辑完整的故事。4. 常见问题与排坑实录4.1 客户端连接不上服务端没有报错但就是连不上这是我见过最多的问题。原因通常是服务端绑定的IP或防火墙策略出了岔子。如果服务端绑定了“127.0.0.1”那就只有本机可以连接其他机器根本连不进来所以开发时服务端尽量绑定“0.0.0.0”或直接不指定IP让系统自动绑定所有网卡。还要检查Windows防火墙有没有拦截Java进程的入站连接最省事的办法是在第一次弹出防火墙提示时允许访问。4.2 粘包半包导致消息错乱现象是客户端连续发送多条消息服务端收到的内容全混在一起了。解决方案就按我刚才提到的“长度前缀”方式来处理。如果你用的是Netty在pipeline里加入LengthFieldBasedFrameDecoder即可。4.3 客户端断开后服务端抛java.net.SocketException这是网络编程最经典的异常。客户端直接关掉窗口服务端如果还在调read方法就会触发这个异常。核心处理思路是把每个客户端的处理逻辑单独放在try-catch块里即使某个客户端断开或报错也只影响这一个handler不会拖垮整个服务端。4.4 并发量稍大时服务器卡死通常是出现了线程饥饿。如果你是每个Socket一个线程客户端数量一多系统就扛不住了。最粗暴但有效的方法是限制最大客户端数量超过上限时返回“服务器已满”的提示并关闭新连接。但这只是权宜之计更理想的方案是使用NIO或者Netty用少量I/O线程处理海量连接。4.5 界面中文乱码或文本输入框丢失输入内容中文乱码大概率是字符编码不一致代码文件保存的编码与运行时读取的编码不同。这里有个经验值得在你做毕设时直接套用所有Java源文件统一使用UTF-8编码在IDE全局设置里配置好Socket传输字符串时明确使用“UTF-8”作为字符集比如消息.getBytes(StandardCharsets.UTF_8)。注意千万不要依赖默认字符集因为不同操作系统的默认字符集可能完全不同。我把高频问题整理成一个速查表方便你随时对照现象大概率原因解决思路客户端连不上服务端绑定IP错误、防火墙拦截绑定0.0.0.0放行Java端口中文乱码字符编码不一致统一UTF-8传输时显式指定字符集消息粘包没有处理字节流分界使用长度前缀或分隔符Netty加LengthFieldBasedFrameDecoder客户端断开导致服务端崩溃读取异常未被捕获handler内try-catchfinally清理资源消息丢失服务端路由逻辑在客户端下线时未移除映射维护连接与用户名的Map下线时同步删除界面卡死子线程直接操作UI通过invokeLater或Platform.runLater更新UI内存溢出数组或列表无限增长定时清理过期连接和消息使用有界队列4.6 答辩时被问到设计思路该怎么组织语言答辩老师不会盯着每一行代码看他们更关心你有没有真正理解自己做的东西。如果被问到“你这个系统最大的难点是什么”千万不要说“没有难点”。一个稳的答法是从通信协议设计、并发连接处理、粘包拆包入手讲一两句你遇到的问题和解决过程。可以这样回答做这个系统的过程中我觉得难点在于并发连接的管理和通信协议的稳定可靠。比如一开始我用每个客户端一个线程后来发现线程过多会降低系统性能于是改成了线程池方式协议上遇到了粘包问题后来通过自定义消息头和长度字段来解决。这样的回答有理有据比背概念有效得多。另外老师大概率会追问Netty和传统Socket的区别。你要能把“BIO每连接一线程、阻塞I/O、性能受限”和“NIO基于Selector事件驱动、少量线程处理多连接”这两组关键词说到位就可以过关了。4.7 这套项目后续能扩展出什么如果你希望这个项目能继续在简历上发光而不是做完就丢可以考虑做下面这些扩展方向。接入WebSocket把客户端变浏览器页面做成Web版通信系统引入消息队列或Redis提高并发能力和消息吞吐量把单机服务端扩展为集群模式给通信内容加上对称加密和非对称加密混合机制。5. 代码可运行与完整交付的细节5.1 一个极简但完整可跑的Socket通信核心代码如果你打算用自己的方式从零搭建建议你先写出下面这段最核心的代码并确认它能跑通再往上加功能。这是一个基于线程池的Echo服务端也是很多通信系统服务端的雏形。public class ChatServer { private static final int PORT 8888; private static final MapString, PrintWriter CLIENTS new ConcurrentHashMap(); public void start() { try (ServerSocket serverSocket new ServerSocket(PORT)) { System.out.println(服务器启动端口号 PORT); ExecutorService pool Executors.newFixedThreadPool(16); while (true) { Socket socket serverSocket.accept(); pool.execute(() - handleClient(socket)); } } catch (IOException e) { e.printStackTrace(); } } private void handleClient(Socket socket) { try ( BufferedReader in new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); PrintWriter out new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true) ) { String line; while ((line in.readLine()) ! null) { System.out.println(收到消息 line); out.println(服务端已收到 line); } } catch (IOException e) { System.out.println(客户端连接异常 e.getMessage()); } finally { try { socket.close(); } catch (IOException ignored) { } } } public static void main(String[] args) { new ChatServer().start(); } }这段代码虽然简单但它已经隐含了三个很重要的设计使用线程池而不是每连接一个裸线程、使用try-with-resources保证流自动关闭、在finally里关闭socket。答辩的时候你能把这三点讲出来代码质量就立住了。5.2 从零写一个Netty版本的服务端骨架如果你决定使用Netty服务端启动代码骨架如下public class NettyChatServer { public void start(int port) throws InterruptedException { EventLoopGroup bossGroup new NioEventLoopGroup(1); EventLoopGroup workerGroup new NioEventLoopGroup(); try { ServerBootstrap bootstrap new ServerBootstrap(); bootstrap.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true) .childHandler(new ChannelInitializerSocketChannel() { Override protected void initChannel(SocketChannel ch) { ChannelPipeline pipeline ch.pipeline(); // 解决粘包拆包 pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4)); pipeline.addLast(new LengthFieldPrepender(4)); // 可以将对象序列化为JSON或Java对象 pipeline.addLast(new ObjectEncoder()); pipeline.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); // 业务处理Handler pipeline.addLast(new ChatHandler()); } }); ChannelFuture future bootstrap.bind(port).sync(); System.out.println(Netty服务端已启动监听端口 port); future.channel().closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }代码本身不算多但每一步背后都有明确目的。BossGroup和WorkerGroup的线程数分离是事件循环模型的基本思想LengthFieldBasedFrameDecoder和LengthFieldPrepender解决了我前面反复提到的粘包拆包ObjectEncoder和ObjectDecoder让你可以直接在pipeline里传递对象而不必手动序列化。这个骨架跑通之后再往ChatHandler里写登录、聊天、群聊等业务逻辑就非常顺畅了。5.3 交付资料的整理与检查清单最后当你准备提交这份毕业设计时建议按下面的清单做一次交付自查是否提供了完整的项目数据库脚本包括建库建表语句和初始化数据是否附带README说明文件写清楚JDK版本、依赖库、启动参数、演示账号和常见问题是否删除敏感信息比如服务器密码、真实姓名、校园网账号是否将源代码、论文、开题报告、答辩PPT放在不同的目录并清楚命名是否使用压缩包时保留文件目录的层级关系不要全部散落在根目录。6. 这个项目还能怎样延伸成你的加分亮点如果你时间比较充足我强烈建议不要停留在“运行通过”这个层面而是给系统叠加一个很小的亮点。这样在简历和答辩里你的项目就从一个课程级别的Demo变成了一个有思考深度的系统。举几个成本低但收益明显的改进方向如果目前是文本聊天可以加一个简单的文件传输功能用DataOutputStream发送文件名和长度再发送文件字节流如果目前的通信内容都是明文可以加一个AES对称加密客户端加密后发送密文服务端解密后再处理代码量不超过100行但能引出信息安全的话题如果目前的界面比较简陋可以改成JavaFX用CSS给消息气泡换配色。另外你在写论文时可以把“数据加密传输”作为第五章的一个小节哪怕只写了核心思路和关键代码片段答辩时老师对安全相关的加分项通常都会多看一眼。我个人在实际操作中的体会是一份毕业设计能不能拿到高分往往不是看功能有多丰富技术栈有多花哨而是看你能不能把每个环节的设计动机说清楚。网络通信系统这个题目的下限很低随便写个Socket收发字符串就能交差但上限也很高你可以往并发、协议、架构、安全这些方向无限延伸。你手里那套包含论文、源代码和开题报告的资料包说白了只是一个起点真正有价值的是你把原理吃透以后用自己的话把它讲出来把代码里不合理的部分改掉把缺少的部分补齐。尤其是答辩前建议你自己动手把服务端启动过程、客户端发送消息的完整链路、一条消息从发出到被接收的流程从头到尾梳理一遍能做到这一步这个题目就真正属于你了。本文还有配套的精品资源点击获取