
简介这是一份来自华中科技大学的计算机网络课程实验报告适合计算机专业本科生参考尤其可用于套接字编程与网络组建实验的课前预习和实验报告撰写借鉴。内容分为两部分前半部分以 Java 语言实现电子邮件收发覆盖套接字定义与创建、传输控制协议与用户数据报协议的分类、简单邮件传输协议和邮局协议以及客户端-服务器架构下的通信流程后半部分为网络组建与实施依次完成组网、路由配置、虚拟局域网划分和访问控制配置等实验涉及网络拓扑结构、TCP/IP 协议体系及路由器、交换机等网络设备操作。资源共 1 个 docx 文档压缩包大小 3.67MB正文包含实验目的、需求分析、界面截图、详细配置步骤和实验体会目录完整便于对照学习。已有 427 人学习下载。对于需要完成类似实验并希望理解从环境搭建到通信验证全过程的高校学生这份报告能提供清晰的思路和可复用的配置记录。1. 这份实验报告到底在做什么先搞清题目想问什么Java Socket编程和网络组建实验放进同一份报告是华科计算机网络实验里最常见的组合方式。表面上要你交代码、交截图实际上只考一件事你有没有让两台真实主机把数据从应用层一路送到对端应用层并且能说清中间每一跳发生了什么。见过太多人上课用 localhost 跑通就算完事答辩时老师把服务端程序挪到隔壁机器上客户端连一次就超时。问题往往不在 Java 代码而在你没把 Socket 当成一条“跨主机”的链路来看绑定地址写死、端口被防火墙拦、两台设备不在同一个 VLAN 里。这篇笔记顺着实验报告的三个环节讲Socket 程序怎么写才能扛住追问、网络组建VLAN 与路由最小拓扑怎么搭、抓包验证怎么截进报告最后给五个高发踩坑点。适合正在写计网报告的学生也适合要把局域网通信程序部署到真实交换机环境里的工程师。2. 先拆题目Java Socket编程实验的“报告级”代码骨架2.1 实验报告常考的三个能力点TCP、UDP与并发处理写 Socket 实验报告第一步不是找现成代码而是先把题目要你证明什么拆开。华科这套计网实验Socket 部分通常落在三个点上TCP 回显或文件传输、UDP 数据报收发、至少两个客户端同时在线。三个点分别对应传输层的连接管理、无连接通信、以及服务器端的并发模型报告里每个点都要有代码和现象对应。如果题目里还带了“网络组建”那它和 Socket 不是两个孤立实验而是一条链路的两端服务端监听地址、客户端连接的 IP、中间交换机端口所属 VLAN、路由器网关全部串起来才是完整答案。报告开头最好用一句话写清楚“为什么这个实验选 TCP 而不是 UDP需要可靠传输、需要按序到达、需要面向连接的会话”。这句话在实验目的里只占两行但在答辩时能挡掉一半追问。而且这三个能力点也是 Java 基础面试里常被追问的内容把这个实验吃透顺带把 TCP 三次握手、IO 阻塞、线程池这些八股问题一起理解了。2.2 能直接抄进报告的TCP服务端绑定0.0.0.0、backlog与线程池常见的报告级 TCP 服务端长这样它把“监听地址”“连接队列”“线程池”三个关键决策都显式写出来了import java.io.*; import java.net.*; import java.util.concurrent.*; public class TcpEchoServer { private static final int PORT 8899; private static final int BACKLOG 50; public static void main(String[] args) throws IOException { ServerSocket serverSocket new ServerSocket(); // 监听本机所有网卡而不是只监听 127.0.0.1 serverSocket.setReuseAddress(true); serverSocket.bind(new InetSocketAddress(0.0.0.0, PORT), BACKLOG); System.out.println(服务器监听地址 serverSocket.getLocalSocketAddress()); // 固定线程池避免每来一个连接就 new Thread ExecutorService pool Executors.newFixedThreadPool(4); while (true) { Socket client serverSocket.accept(); System.out.println(接受到客户端 client.getRemoteSocketAddress()); pool.execute(new ClientHandler(client)); } } }这段代码最容易被追问的是三个点。第一为什么绑0.0.0.0而不是localhostlocalhost只监听回环口换成另一台物理机连时根本进不来绑0.0.0.0表示监听本机所有网卡地址实验环境里无论虚拟机还是真机都能连。第二BACKLOG 50是操作系统允许的等待连接队列长度超过这个数的新连接会被内核拒绝实验里这个值够用不用调大。第三newFixedThreadPool(4)限制了同时处理的连接数比CachedThreadPool更安全答辩时被问到“为什么不直接 new Thread”就可以答避免无限创建线程导致资源耗尽用有界线程池让服务器在高并发下表现可控。2.3 客户端要带着协议写长度前缀把粘包和半包挡在门外很多报告里的客户端就是socket.getOutputStream().write(hello.getBytes())然后readLine这一点在答辩时几乎必被问到TCP 是字节流没有消息边界你怎么知道一条消息从哪开始、到哪结束常见做法是给每条消息加一个 4 字节长度前缀客户端先读长度再按长度读正文。代码如下import java.io.*; import java.net.*; public class TcpEchoClient { public static void main(String[] args) throws IOException { Socket socket new Socket(192.168.1.20, 8899); // 帧格式4字节长度 业务数据避免粘包和半包 byte[] payload hello, this is a network lab test.getBytes(UTF-8); DataOutputStream out new DataOutputStream(socket.getOutputStream()); out.writeInt(payload.length); out.write(payload); out.flush(); DataInputStream in new DataInputStream(socket.getInputStream()); // 读取回显先读长度再读正文 int len in.readInt(); byte[] echo new byte[len]; in.readFully(echo); System.out.println(服务端回显 new String(echo, UTF-8)); socket.close(); } }DataInputStream.readInt()读 4 字节并还原成 intreadFully()保证要么读满指定长度要么抛异常不会出现“读了一半就返回”的情况。这个设计同时处理了粘包和半包粘包时首帧长度字段能正确切出第一条消息半包时readFully会继续等待剩余字节。还有两个容易忽略的细节字符串统一用UTF-8编解码socket.close()放在最后不要在flush()之后就 close否则可能把还没发出去的数据截断。2.4 报告里少不了的三种截图日志、状态与数据通道Socket 实验的报告截图常见的失败模式是只贴一个黑窗口里面一行“connected”老师根本没法判断程序是否真的完成了传输。一份能让人信服的结果至少有三类截图第一类是服务端日志每一行带本地时间和双方 IP:端口第二类是客户端运行状态比如发送字节数、回显内容、耗时第三类是 Wireshark 抓包能看到三次握手里的 SYN、SYNACK、ACK 三个包以及之后的数据段。三类截图按时间线排好报告的说服力立刻上一个档次。抓包的具体做法放在后面第 4.3 节展开这里先记住Socket 实验的结果不是“跑通了”而是“每一步都有证据”。3. 从“能跑”到“能答辩”并发、心跳与网络组建场景挂钩3.1 多客户端连接工作线程要能扛住“accept之后没人管”实验要求多客户端在线时很多人直接while (true) { new Thread(new Handler(socket)).start(); }。功能上没错但答辩时被问到“如果 1 万个客户端同时连怎么办”就答不上来。用线程池加一个独立的ClientHandler是更稳的写法import java.io.*; import java.net.*; public class ClientHandler implements Runnable { private Socket socket; private static final int MAX_FRAME_LEN 64 * 1024; public ClientHandler(Socket socket) { this.socket socket; } Override public void run() { System.out.println(线程 Thread.currentThread().getName() 正在处理 socket.getRemoteSocketAddress()); try (DataInputStream in new DataInputStream(socket.getInputStream()); DataOutputStream out new DataOutputStream(socket.getOutputStream())) { while (true) { int len in.readInt(); if (len 0 || len MAX_FRAME_LEN) { break; } byte[] data new byte[len]; in.readFully(data); System.out.println(收到 socket.getRemoteSocketAddress() 的消息 new String(data, UTF-8)); out.writeInt(len); out.write(data); out.flush(); } } catch (EOFException e) { System.out.println(客户端 socket.getRemoteSocketAddress() 正常断开); } catch (IOException e) { System.out.println(连接异常 e.getMessage()); } finally { // try-with-resources 不会自动关闭socketfinally里显式关 try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }MAX_FRAME_LEN 64 * 1024是给长度上限做的保护防止恶意客户端声明一个超大长度把服务端内存打爆。EOFException单独捕获因为对端正常 close 时读操作会读到文件结束标志抛出的正是这个异常把它和普通IOException分开处理日志才分得清“正常断开”和“传输中断”。finally 里的socket.close()经常被漏掉try-with-resources 只关了流没关底层的 Socket不显式 close 会导致句柄泄漏连接数一多服务端就卡死。3.2 心跳与超时让断线在几秒内被感知而不是卡到天荒地老实验报告如果能写出“断线检测”这一项含金量会明显不一样。很多初学者把readInt()放在循环里对端机器直接断电时这个读操作可能阻塞很久直到 TCP 超时机制触发才返回期间服务器线程一直被占用。常见做法是给 Socket 设置读超时再配合心跳Socket socket new Socket(192.168.1.20, 8899); // 读操作最多阻塞5秒超时抛SocketTimeoutException socket.setSoTimeout(5000); while (true) { try { int len in.readInt(); byte[] data new byte[len]; in.readFully(data); System.out.println(收到数据 new String(data, UTF-8)); } catch (SocketTimeoutException e) { // 5秒没有业务数据发送心跳探测对端是否存活 out.writeUTF(heartbeat); out.flush(); } }这里有个隐蔽的坑out.writeUTF(heartbeat)用的是 Java 的 modified UTF-8自带 2 字节长度前缀如果你自己的协议也用长度前缀两者会冲突服务端解析时把心跳的 2 字节长度也当成消息内容整个帧就错位了。正确做法是把心跳也设计成和业务消息相同的帧格式用一个固定消息类型区分// 心跳帧前2字节为消息类型后4字节为长度最后是数据 byte[] heartbeat HB.getBytes(UTF-8); out.writeShort(1); // 1 表示心跳 out.writeInt(heartbeat.length); out.write(heartbeat);超时时间选 5 秒还是 30 秒取决于实验场景同机房局域网内延迟很低5 秒足够如果跨路由跨网段建议 10 到 15 秒太短会把正常慢请求误判成死连接。心跳次数一般连续 2 次无响应就判定对端不可达这时主动 close 并释放资源。把这些参数写进报告的“设计说明”老师会认为你真的处理过异常情况而不是只在理想路径上跑了一遍。3.3 Socket与VLAN组合验证把“能通”变成“可解释的能通”Socket 实验和网络组建实验的结合点不在代码本身而在“连通性验证”上。设想你已经在两台 PC 上跑通了 Java 聊天程序接下来把两台 PC 接到同一台交换机上分别在端口上划不同的 VLANPC-A 划到 VLAN 10PC-B 划到 VLAN 20。此时再跑客户端表现通常是 TCP 连接直接超时或者 SYN 发出去没人应答。这不是 Java 代码变了而是二层被隔离了ARP 请求只能在 VLAN 10 内广播PC-B 根本收不到。把两个端口都划进 VLAN 10程序不需要重新编译立刻恢复通信。这组对比实验非常适合写进报告同一个 Socket 程序在两种网络配置下一个通一个不通结论直接指向“数据传输依赖二层可达性VLAN 是隔离广播域的机制”。比起单独罗列“我配了 VLAN”更有说服力。如果题目还要求跨 VLAN 通信就加一台路由器做单臂路由配好子接口后 Socket 程序再跑一次把三个状态——VLAN 隔离不通、同 VLAN 通、跨 VLAN 通——各截一张图一条完整的从“不同网段”到“应用层互通”的证据链就出来了。4. 网络组建实验最小拓扑、VLAN划分与抓包验证4.1 最小拓扑两台PC、一台交换机跨网段再加路由器网络组建实验不需要复杂拓扑报告里最好从最小可验证的场景起步。一套够用的组网如下两台 PC 分别接交换机G0/0/1和G0/0/2口初始都划在 VLAN 1此时两台机器同网段Socket 程序可以直接通信。然后做 VLAN 隔离实验再引入三层设备做跨 VLAN 路由。IP 规划建议用独立的实验网段别和机房真实网络冲突常见做法是设备接口VLANIP 地址网关PC-Aeth0VLAN 10192.168.10.10/24192.168.10.1PC-Beth0VLAN 20192.168.20.10/24192.168.20.1路由器G0/0/1.10VLAN 10192.168.10.1/24-路由器G0/0/1.20VLAN 20192.168.20.1/24-这个规划把“二层隔离”和“三层互通”分得很清楚PC-A 和 PC-B 在同一个交换机上但属于不同 VLAN广播域被隔开默认网关指向路由器子接口跨 VLAN 的数据先走二层到路由器再由路由器转发。报告里画一张这样的表格比贴一堆命令行更能体现你理解了自己在做什么。4.2 VLAN划分与网关配置Socket连不上时先查这两处交换机侧的配置以下用华为数通设备的命令行风格做演示。把端口设为 access 并划入指定 VLAN# 配置PC-A所在端口 interface GigabitEthernet0/0/1 port link-type access port default vlan 10 quit # 配置PC-B所在端口 interface GigabitEthernet0/0/2 port link-type access port default vlan 20 quit配置完 VLAN 之后Socket 程序连不上时先查两处第一客户端的网关是否指向正确跨 VLAN 通信必须保证 PC 的默认网关和路由器子接口 IP 在同一网段第二交换机端口是否真的加入了对应 VLAN可以用display vlan查看端口归属。很多翻车场景是命令配了但没保存设备一重启回到默认 VLAN 1Socket 程序又能通了但抓包发现走的根本不是实验拓扑里规划的路径。需要注意一个细节实验里如果只用两台 PC 加一台交换机PC 的 IP 要静态配置别图省事用 DHCP。DHCP 分配的地址可能落在不同网段VLAN 配置对了但 IP 不在规划里照样连不上。报告的附录里放一段 PC 端ipconfig /all或ifconfig的截图把 IP、掩码、网关三个字段圈出来和上面的表格对照排除“配置对但没生效”的争议。4.3 抓包验证把三次握手和数据段截进报告里抓包工具用 Wireshark 就够。实验开始时先确认服务端已经在0.0.0.0:8899监听然后在 Wireshark 里设置过滤条件为tcp.port 8899再启动客户端。捕获到的包序列应该是第一个包是客户端的 SYN第二个是服务端的 SYNACK第三个是客户端的 ACK三次握手完成之后才是双方的数据段和最终的 FIN/ACK 挥手过程。验证点抓包过滤表达式预期结果三次握手tcp.port 8899 tcp.flags.syn 1恰好两个 SYN 包一上一下数据段tcp.port 8899 tcp.len 0载荷长度与代码里发送的字节数一致挥手过程tcp.port 8899 tcp.flags.fin 1四次 FIN/ACK 成对出现截图时把 Wireshark 的“Time”列改成绝对时间方法是在列头右键选择“Column Preferences”调整或者直接在“View”菜单里切换时间显示格式。这一步很重要后面第 6 章会用它和程序日志对时间戳。截图上最好给关键帧加注释右键选中帧后选择“Add Packet Comment”写清楚“这是客户端第 X 行 write 发出的一条消息”报告的可读性会立刻不同。5. 避坑指南Socket和组网实验翻车最多的五个地方5.1 收到奇数字节后面多一个“随机数”编码与字节流边界现象客户端发送中文消息服务端收到的最后一个字符变成乱码或者在正常内容后面多出一个看起来像随机数的字节。把接收缓冲区的原始 byte 打印出来发现长度比预期大尾部多出一个孤立字节。原因TCP 是字节流没有应用层包边界。发送方把包含多个中文字符的 UTF-8 字节一次性 write但底层网络栈分成多个 TCP 段接收方第一次 read 只拿到了一个段恰好把一个多字节字符切开剩余的半个字符留在第二次 read 里。如果代码用new String(buffer, 0, n, UTF-8)直接转换半个字符解码失败就变成乱码或替换字符看起来像系统“补了一个随机数”。解决不要在网络上传输“裸字符串”统一按第 2.3 节的长度前缀协议发送接收端用readFully先读完整帧再解码收发两端固定UTF-8。这个坑在实验里最容易伪装成“代码没问题”实际上是字节流边界没处理。5.2 连接能建立但数据过不去从 no more data to read from socket 说起现象客户端connect成功write也不报错但read一直超时换成 JDBC 连接数据库时则直接抛no more data to read from socket这类异常。服务端日志里什么输出都没有。原因这类报错的核心是“TCP 连接建立了但应用层数据没有抵达对端”。常见三种成因一是服务端进程其实已经退出留下一个孤立的半开连接二是客户端的请求发出后Windows 防火墙只放行了入站端口却拦住了响应的临时端口三是发送方一次write的数据量超过 TCP 发送缓冲区数据被分段后部分丢失对端永远收不到完整消息。解决先把读超时从默认值改小比如setSoTimeout(5000)5 秒内读不到数据就打印日志并 close避免线程被无意义挂起然后用抓包确认 SYN、SYNACK、ACK 是否完整出现再检查两边防火墙是否放行了8899端口以及临时端口范围传输大文件时改成 8KB 大小的循环发送不要试图一次write几 MB。5.3 虚拟机互相ping不通先查VM网卡模式和默认VLAN现象两台虚拟机配了同网段静态 IP但互相 ping 不通把两台都切成桥接模式后能通了可是交换机的抓包里看不到这两台机器的 ARP 请求。原因VMware 里桥接和 NAT 本质上是两个不同的二层网络。一台桥接、一台 NAT 时两台机器虽然 IP 在同一网段但二层广播域根本不同ARP 请求互相不可见。另外很多实验交换机默认所有端口都在 VLAN 1如果上一组实验把某个端口划到了 VLAN 10没还原就继续用也会出现“配置没问题但就是不通”。解决两台虚拟机统一选择桥接且桥接到同一块物理网卡或者统一用 NAT 模式并确保子网一致。做完 VLAN 实验后用undo port default vlan或手动把端口还原回 VLAN 1再开始下一组实验。这个“上一组配置残留”的问题在我带过的实际操作里至少出现过三次每次都让人怀疑人生。5.4 重启就报 Address already in useTIME_WAIT与SO_REUSEADDR现象服务端程序 CtrlC 结束马上重新启动时抛BindException: Address already in use等几十秒再启动又正常了。原因主动关闭连接的一端会进入TIME_WAIT状态默认持续 2MSLLinux 上通常 60 秒。实验里的服务端往往是主动关闭方端口还处于TIME_WAIT内核不允许立即重新绑定同一个端口。另一个常见成因是上一次进程没完全退出服务还占着端口。解决代码里在bind之前调用serverSocket.setReuseAddress(true)开发调试时用netstat -ano | grep 8899找到占用进程并杀掉。注意SO_REUSEADDR必须在绑定前设置才有效放在bind之后是很多人踩过的坑。还有一种省事做法是每次重启换端口但报告里的抓包和分析就会变得不一致不建议。5.5 抓包第一帧就是RST三次握手根本没开始现象Wireshark 过滤tcp.port 8899后列表里没有 SYN/ACK 包第一个 TCP 包就是RST客户端报Connection reset。原因客户端连接的端口上没有服务端在监听内核直接回 RST或者服务端的半连接队列已满新连接被拒绝还有一种情况是防火墙策略直接以 RST 回应了入站 SYN 包。解决先用netstat -an | grep 8899确认服务端在监听检查服务端进程是否还在日志文件满了也会让 JVM 假死端口残留但不再 accept临时关闭 Windows 防火墙做一次对比测试排除安全策略干预。实验里最常见的还是“代码还没启动就点客户端”抓包结果自然全是 RST记录下这个现象反而能证明你理解 RST 的语义。6. 让报告“一次过”的验收技巧把程序日志和Wireshark时间戳对齐报告最后一定要有一个“验收闭环”先跑通代码再开抓包然后把程序日志里的时间戳和 Wireshark 里的包时间对齐。具体做法是在服务端代码里把每条消息的处理时间打印到毫秒System.out.println(String.format([%tF %tT.%tL] 收到 %s 的消息%s, ts, ts, ts, socket.getRemoteSocketAddress(), new String(data, UTF-8)));启动 Wireshark 时保持默认的“relative time”即可但截图前把时间显示切成绝对时间或者直接在帧注释里抄下程序日志的时间。我自己的验收顺序是先抓包再运行客户端然后回到 Wireshark 里逐帧核对程序中每个write对应的那个 TCP 段。第 1 帧是 SYN对应客户端new Socket(...)中间某一帧len 0载荷长度和代码里writeInt的值一致最后成对的 FIN/ACK 对应socket.close()。做完这一步再动手写报告的文字分析而不是先写分析再补截图否则容易写成“一切正常”的流水账。报告里文字分析的正确写法是“第 3 帧 ACK 的 Sequence Number 为 1说明服务端已经收到客户端初始序号对应客户端代码第 X 行的连接建立成功”而不是一句“从图中可以看到三次握手完成”。把包序号、载荷长度、对应代码行这三个信息写在同一条分析里老师基本不会再追问。我做这个实验时吃过一次亏连着抓了十几张包截图时间全是相对时间程序日志用的是默认格式两边对不上最后重跑了一遍才凑齐。后来养成的习惯是先固定拓扑和 IP再让程序打毫秒级时间戳最后开抓包软件——顺序反过来重跑的代价很高。希望这个方法也能让你的报告少返一次工。本文还有配套的精品资源点击获取