简介这份资源是面向高校计算机网络课程设计场景的模拟邮件系统完整项目适合正在完成课程作业或希望理解邮件收发底层协议的学生与开发者。项目基于SMTP与POP3协议实现用户注册登录、资料编辑、发信、查看来信、回复及收件箱管理等核心功能并采用MVC架构以Maven工程整合Servlet、JSP、MyBatis与JavaMail配置数据库后即可运行便于对照协议流程理解邮件系统的实际运作。压缩包共58个文件约119KB包含12个Java源文件与12个class文件承载业务逻辑10个XML与3个properties负责配置6个JSP与3个CSS构成前端页面另有工程配置文件支撑IDE导入。目前已有3493人学习下载可作为课程设计参考方案帮助读者快速掌握邮件协议与J2EE整合开发的完整思路。1. 模拟邮件系统从一封发不出去的邮件说起很多同学做计算机网络课程设计时选题第一反应是抓包分析或者写个聊天室但真正能把 SMTP 和 POP3 这两个协议串起来跑通的人并不多。我见过太多这样的情况代码写完了telnet连上去也能看到220欢迎语但邮件就是发不出去或者发出去收不回来最后只能截图交差。模拟邮件系统这个题目表面上是让你实现一个能收发邮件的程序实际上考的是你对应用层协议交互流程的理解——SMTP 负责把邮件从客户端推到服务器、再从服务器推到另一台服务器POP3 负责把邮件从服务器拉回客户端。这两个协议一个推一个拉方向相反端口不同命令集也完全不一样。如果你正在做计算机网络课程设计或者期末复习到应用层这一章想找个能动手的项目巩固这套东西值得你花两三天认真做一遍。它不需要你买服务器不需要域名一台笔记本就能跑通全流程。2. 先把协议交互流程刻在脑子里SMTP 推、POP3 拉2.1 为什么不能只写一个“发邮件”的函数很多人一开始的想法很简单我写个函数传入收件人、发件人、主题、正文调用一下就发出去了。这种思路在应用层开发里没问题但在课程设计里会翻车因为老师看的是你对协议本身的理解。SMTP 不是“一个函数”它是一套基于文本命令的对话流程。客户端连上服务器的 25 端口或者提交端口 587服务器先说220客户端回HELO或EHLO然后MAIL FROM、RCPT TO、DATA最后QUIT。每一步服务器都有明确的响应码你必须按顺序来跳一步就报错。POP3 同理连上 110 端口服务器回OK然后USER、PASS、STAT、LIST、RETR、DELE、QUIT。这两个协议的交互流程就是课程设计的核心考点代码只是把流程翻译成程序。2.2 用 telnet 手动走一遍 SMTP 对话在写任何代码之前我强烈建议你先用telnet手动跟服务器对话一次。这是最直观的方式能让你看到每一行命令和响应。如果你本地没有邮件服务器可以用 Python 自带的smtpd模块起一个调试服务器或者用aiosmtpd这个库。下面是在本地 8025 端口起一个调试 SMTP 服务器的命令python -m aiosmtpd -n -l localhost:8025然后另开一个终端用telnet连接telnet localhost 8025连上之后你会看到220开头的欢迎语。接着手动输入以下命令每输一行按回车观察服务器返回什么EHLO test.com MAIL FROM:sendertest.com RCPT TO:receivertest.com DATA Subject: Hello This is a test email. . QUIT注意DATA之后服务器会回354然后你输入邮件内容最后单独一行输入一个英文句点.表示结束。这个句点就是 SMTP 协议里最容易踩坑的地方之一——如果你的邮件正文里有一行只有一个句点必须写成两个句点来转义。手动走完这一遍你就明白代码里每一步该发什么、该等什么响应了。2.3 POP3 的拉取流程和 SMTP 的对称性POP3 的交互比 SMTP 简单一些但状态机更明确。连接建立后进入“授权状态”你必须先USER再PASS验证通过后进入“事务状态”这时候才能STAT、LIST、RETR。RETR后面跟邮件编号服务器会把整封邮件的内容返回给你包括头部和正文最后以.单独一行结束。这里有个细节POP3 返回的邮件内容里行结束符是\r\n不是\n如果你用 Python 的readline()读要注意解码和换行处理。我一般会先把原始字节读出来再按\r\n切分这样最稳妥。2.4 用 Python 的 smtplib 和 poplib 快速验证标准库已经帮你封装好了协议细节但课程设计里我建议你至少手写一遍核心命令的发送和解析再用标准库做对照。下面这段代码是用smtplib发一封邮件的最小示例import smtplib from email.mime.text import MIMEText # 构造邮件内容 msg MIMEText(这是一封测试邮件正文, plain, utf-8) msg[Subject] 测试主题 msg[From] sendertest.com msg[To] receivertest.com # 连接本地调试服务器不发真实邮件 with smtplib.SMTP(localhost, 8025) as server: server.set_debuglevel(1) # 打印完整协议对话 server.send_message(msg)set_debuglevel(1)会把你和服务器之间的每一行命令都打印出来方便你对照协议流程。参数localhost和8025换成你实际调试服务器的地址和端口。如果你要连真实的邮件服务商端口通常是 587 并需要starttls()但课程设计里用本地调试服务器就够了避免账号配置的麻烦。3. 动手实现从零写一个能收发的模拟邮件系统3.1 整体架构三个模块各管一件事我一般会把系统拆成三个模块SMTP 客户端、POP3 客户端、以及一个简易的本地邮件存储。SMTP 客户端负责把邮件“推”到服务器POP3 客户端负责从服务器“拉”邮件本地存储用一个目录下的.eml文件来模拟邮箱。这样拆的好处是每个模块职责单一调试的时候可以单独测试。比如你先用 SMTP 客户端发一封邮件到调试服务器然后用 POP3 客户端去拉看能不能拿到。如果拿不到问题一定在 POP3 那边不用来回猜。3.2 SMTP 客户端命令发送与响应码校验手写 SMTP 客户端的核心是“发一条命令读一行响应检查响应码”。下面是一个简化版的实现import socket class SMTPClient: def __init__(self, host, port): self.sock socket.create_connection((host, port)) self.file self.sock.makefile(r, encodingutf-8, newline\r\n) self._read_response() # 读取 220 欢迎语 def _read_response(self): line self.file.readline() code line[:3] # 多行响应以 250- 开头最后一行是 250 while line[3:4] -: line self.file.readline() return code, line def send_command(self, cmd, expect_code): self.sock.sendall((cmd \r\n).encode(utf-8)) code, line self._read_response() if code ! expect_code: raise RuntimeError(f命令 {cmd} 期望 {expect_code}实际 {code}: {line}) return line def send_mail(self, sender, receiver, subject, body): self.send_command(fEHLO test.com, 250) self.send_command(fMAIL FROM:{sender}, 250) self.send_command(fRCPT TO:{receiver}, 250) self.send_command(DATA, 354) # 正文中的单独句点需要转义 safe_body body.replace(\r\n.\r\n, \r\n..\r\n) message fSubject: {subject}\r\n\r\n{safe_body}\r\n.\r\n self.sock.sendall(message.encode(utf-8)) self._read_response() # 读取 250 发送成功 self.send_command(QUIT, 221)这段代码里_read_response处理了多行响应的情况因为EHLO的返回通常是多行每行以250-开头最后一行以250开头。send_command做了响应码校验不匹配就抛异常这样你能立刻定位是哪一步出了问题。send_mail里的句点转义是必须的否则正文里如果有单独一行句点服务器会提前认为邮件结束。3.3 POP3 客户端拉取、解析与删除POP3 客户端比 SMTP 稍简单但要注意RETR返回的内容是多行直到单独一个.结束。下面是一个基本实现class POP3Client: def __init__(self, host, port): self.sock socket.create_connection((host, port)) self.file self.sock.makefile(r, encodingutf-8, newline\r\n) self._read_response() # OK 欢迎语 def _read_response(self): line self.file.readline() if not line.startswith(OK): raise RuntimeError(fPOP3 错误: {line}) return line def login(self, user, password): self.sock.sendall(fUSER {user}\r\n.encode()) self._read_response() self.sock.sendall(fPASS {password}\r\n.encode()) self._read_response() def list_mails(self): self.sock.sendall(bLIST\r\n) self._read_response() mails [] while True: line self.file.readline() if line.strip() .: break parts line.split() mails.append((int(parts[0]), int(parts[1]))) return mails def retrieve(self, mail_id): self.sock.sendall(fRETR {mail_id}\r\n.encode()) self._read_response() lines [] while True: line self.file.readline() if line.strip() .: break # 还原被转义的句点 if line.startswith(..): line line[1:] lines.append(line) return .join(lines)list_mails返回邮件编号和大小retrieve返回完整邮件内容。注意RETR返回的内容里如果某行以..开头说明原始内容是以.开头需要去掉一个点。这个转义规则和 SMTP 的DATA是对称的。3.4 本地邮件存储用 .eml 文件模拟邮箱为了模拟“收件箱”我一般会在本地建一个目录每收到一封邮件就存成一个.eml文件。文件名可以用时间戳加邮件编号避免冲突。下面是一个简单的存储函数import os import time def save_mail(mail_dir, mail_id, content): os.makedirs(mail_dir, exist_okTrue) filename f{int(time.time())}_{mail_id}.eml path os.path.join(mail_dir, filename) with open(path, w, encodingutf-8) as f: f.write(content) return path参数mail_dir是存储目录mail_id是 POP3 返回的邮件编号content是retrieve拿到的完整内容。这样你就能在文件管理器里直接打开.eml文件查看邮件也可以用邮件客户端导入。3.5 端到端联调发一封、拉一封、看结果把三个模块串起来完整的测试流程是这样的先起调试 SMTP 服务器和调试 POP3 服务器然后用 SMTP 客户端发一封邮件再用 POP3 客户端登录、列出邮件、拉取、保存。调试 POP3 服务器可以用poplib配合一个简单的存储后端或者直接用aiosmtpd的 POP3 扩展。如果不想折腾服务器也可以自己写一个极简的 POP3 服务端把收到的邮件存在内存里。联调的时候把set_debuglevel打开对照协议流程看每一步的响应出问题基本都能定位到具体命令。4. 避坑与排查那些让你卡到半夜的细节4.1 现象EHLO 之后服务器返回 500 错误原因通常是你发的是HELO但服务器要求EHLO或者命令末尾少了\r\n。SMTP 协议规定行结束符必须是\r\n不是\n。如果你用sendall发的是EHLO test.com\n有些服务器会直接报错。解决方法是统一用\r\n并且在 Python 里打开 socket 文件对象时指定newline\r\n。4.2 现象DATA 阶段邮件内容被截断最常见的原因是正文里有一行只有.服务器认为邮件结束了。按照协议正文里单独的句点必须写成两个句点。另外如果你的正文里包含\r\n.\r\n这样的序列也需要转义。我一般在发送前统一做一次替换body.replace(\r\n.\r\n, \r\n..\r\n)。4.3 现象POP3 的 RETR 返回内容乱码原因通常是编码问题。POP3 返回的邮件内容可能是utf-8也可能是gbk取决于邮件头里的Content-Type。如果你直接用utf-8解码遇到gbk编码的邮件就会报错。解决方法是先按字节读取然后从邮件头里解析charset再用对应的编码解码。如果解析不出来用utf-8加errorsreplace兜底。4.4 现象本地调试服务器收不到邮件检查三点第一服务器是否真的在监听用netstat -an | grep 8025看一下端口状态第二防火墙是否拦了本地回环以外的连接如果你用0.0.0.0监听但客户端连的是localhost一般没问题第三MAIL FROM和RCPT TO的地址格式是否正确必须用尖括号包起来比如sendertest.com不能只写sendertest.com。4.5 现象邮件发出去但 POP3 拉不到这通常是因为 SMTP 和 POP3 用的是两个独立的存储。你发到 SMTP 服务器的邮件并不会自动出现在 POP3 服务器的邮箱里。课程设计里你需要自己实现一个共享的存储层或者用同一个调试服务器同时支持 SMTP 和 POP3。我一般会写一个简单的内存字典SMTP 收到邮件后存进去POP3 从同一个字典里读。5. 进阶技巧用 Wireshark 抓包验证协议交互5.1 抓包位置和过滤条件把模拟邮件系统跑起来之后用 Wireshark 抓本地回环流量过滤条件写tcp.port 8025 || tcp.port 8110其中 8025 是 SMTP 调试端口8110 是 POP3 调试端口。这样你就能看到每一行命令和响应的原始字节。抓包的好处是能验证你的代码是否真的按协议格式发送了数据比如行结束符到底是\r\n还是\n在 Wireshark 里一目了然。5.2 从抓包结果反推代码问题如果你在 Wireshark 里看到某条命令后面跟着\n而不是\r\n那服务器返回错误就不奇怪了。如果看到DATA之后的内容里句点没有被转义也能立刻定位。我习惯在调试阶段同时开着 Wireshark 和set_debuglevel两边对照基本不会卡住。5.3 用 Python 脚本自动校验响应码最后分享一个我常用的技巧写一个简单的校验脚本把协议交互的每一步期望响应码和实际响应码都记录下来输出成表格。这样你跑一次测试就能看到哪一步不符合预期。下面是一个示例输出格式步骤发送命令期望响应码实际响应码结果1EHLO test.com250250通过2MAIL FROM250250通过3RCPT TO250550失败4DATA354-跳过这种表格能帮你快速定位问题。比如上面 RCPT TO 返回 550说明收件人地址被服务器拒绝了可能是地址格式不对或者服务器不认这个域名。根据响应码去查 RFC 文档比盲目改代码高效得多。做课程设计最怕的就是“看起来能跑但不知道对不对”抓包加响应码校验这套组合拳打下来你对 SMTP 和 POP3 的理解会比只调库的人深一个层次。我自己的习惯是每写一个协议交互模块先用 telnet 手动走一遍再用代码复现最后抓包确认字节级别没问题。这个流程走下来答辩的时候老师问任何细节你都能答上。希望帮到你。本文还有配套的精品资源点击获取