Python手写SMTP+POP3邮件系统:计算机网络协议原理实践
发布时间:2026/10/1 10:33:17来源:尧图网络
简介这是一份面向计算机网络课程设计与Java Web开发初学者的SMTPPOP3邮件系统模拟项目聚焦协议原理实践与MVC架构落地。资源完整实现用户注册登录、信息管理、邮件收发与回复等核心功能采用ServletJSPMyBatisJavaMail技术栈基于Maven构建适配MySQL数据库开箱即用。压缩包共58个文件含12个Java源码业务逻辑与协议交互、12个Class字节码、10个XML配置MyBatis映射与Web部署、6个JSP页面前端交互及CSS/JS/Properties等辅助文件总大小仅119KB结构精简、模块清晰便于理解分层设计与协议调用流程。已有3493人学习下载提供可直接运行的工程骨架、完整的目录组织含src/main/java、WEB-INF/jsp、resources等标准路径、JavaMail集成示例及数据库初始化脚本是掌握网络协议应用与Web后端开发衔接的典型教学案例。1. 为什么用 Python 自己搭一个 SMTPPOP3 邮件系统比调现成 SDK 更适合计算机网络课设这不是在造轮子而是在拆轮子——把谢希仁《计算机网络》第6章“应用层”里那几页协议图变成能 telnet 连、能抓包看、能改端口、能断点调试的活体黑匣子。我带过三届计网实验课学生交的“基于 JavaMail 的邮件发送界面”作业90% 连EHLO和HELO的区别都说不清但只要亲手用 socket 写完一个支持AUTH LOGIN的 SMTP 服务端再配个能解析RETR 1响应的 POP3 客户端TCP 三次握手、状态机迁移、CRLF 换行、Base64 编码边界这些“玄学”当场就从 PPT 里跳出来咬人。本方案不依赖任何云邮箱 API比如网易企业邮箱后台开启 SMTP 这类操作纯本地双协议闭环发信走你写的 SMTP Server收信走你写的 POP3 Server中间用 SQLite 存邮件——所有协议字段、状态码、时序逻辑全暴露在代码里。适合计网期末复习、湖科大教书匠式原理验证、ZZU/BJTU/HNU 实验报告硬核交付也扛得住王道 408 应用层真题反向拷问。2. 用 Python socket sqlite3 从零实现 SMTP 服务端最小可运行骨架与协议状态机SMTP 协议不是“发个字符串就完事”它是一套严格的状态机CONNECT → HELO/EHLO → AUTH → MAIL FROM → RCPT TO → DATA → QUIT。跳步或乱序会直接被客户端拒绝。我们不用 Flask-Mail 或 aiosmtplib 这类封装库而是用原生socketthreading搭出可调试的服务端重点控制三个核心环节命令解析、状态流转、响应编码。2.1 创建监听 socket 并处理单连接会话import socket import threading import time import sqlite3 class SMTPServer: def __init__(self, host127.0.0.1, port2525): self.host host self.port port self.db_path mails.db self._init_db() def _init_db(self): conn sqlite3.connect(self.db_path) conn.execute( CREATE TABLE IF NOT EXISTS emails ( id INTEGER PRIMARY KEY AUTOINCREMENT, sender TEXT NOT NULL, recipient TEXT NOT NULL, subject TEXT, body TEXT NOT NULL, timestamp DATETIME DEFAULT CURRENT_TIMESTAMP ) ) conn.close() def start(self): server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((self.host, self.port)) server_socket.listen(5) print(f[SMTP] Server listening on {self.host}:{self.port}) while True: client_socket, addr server_socket.accept() print(f[SMTP] New connection from {addr}) # 每个连接开独立线程避免阻塞 thread threading.Thread(targetself.handle_client, args(client_socket,)) thread.daemon True thread.start() def handle_client(self, client_socket): # SMTP 初始欢迎消息必须以 220 开头 client_socket.send(b220 localhost SMTP Service Ready\r\n) state INIT # 状态机初始态 mail_from None rcpt_to [] data_buffer [] while True: try: data client_socket.recv(1024).decode(utf-8).strip() if not data: break # 所有 SMTP 命令必须大写且以 \r\n 结尾RFC 5321 要求 cmd data.split()[0].upper() args data[len(cmd):].strip() if cmd HELO or cmd EHLO: # 支持两种握手返回 250 OK client_socket.send(b250 localhost Hello\r\n) state HELO_OK elif cmd AUTH and state HELO_OK: # 简化版只支持 LOGIN 认证实际需 Base64 解码 if args LOGIN: client_socket.send(b334 VXNlcm5hbWU6\r\n) # Username: # 这里应等待用户名为简化跳过认证逻辑课设可注释掉 auth client_socket.send(b334 UGFzc3dvcmQ6\r\n) # Password: client_socket.send(b235 2.0.0 Authentication succeeded\r\n) state AUTH_OK else: client_socket.send(b504 Unrecognized authentication type\r\n) elif cmd MAIL and state in [HELO_OK, AUTH_OK]: if args.startswith(FROM:): mail_from args[5:].strip() client_socket.send(b250 OK\r\n) state MAIL_OK else: client_socket.send(b501 Syntax error in parameters or arguments\r\n) elif cmd RCPT and state MAIL_OK: if args.startswith(TO:): rcpt_to.append(args[3:].strip()) client_socket.send(b250 OK\r\n) else: client_socket.send(b501 Syntax error in parameters or arguments\r\n) elif cmd DATA and state MAIL_OK: client_socket.send(b354 Start mail input; end with CRLF.CRLF\r\n) state DATA_INPUT data_buffer [] elif cmd . and state DATA_INPUT: # 结束 DATA 输入保存邮件 full_body \n.join(data_buffer) subject self._extract_subject(full_body) # 插入数据库真实场景需解析 MIME self._save_email(mail_from, rcpt_to[0] if rcpt_to else , subject, full_body) client_socket.send(b250 Message accepted for delivery\r\n) state INIT # 重置状态支持后续命令 mail_from None rcpt_to [] data_buffer [] elif cmd QUIT: client_socket.send(b221 Bye\r\n) break else: # 状态不匹配时的通用错误响应 client_socket.send(b503 Bad sequence of commands\r\n) except ConnectionResetError: break except Exception as e: print(f[SMTP] Error: {e}) break client_socket.close() def _extract_subject(self, body): # 简单提取 Subject: 行实际需按 RFC 2822 解析头部 for line in body.split(\n): if line.startswith(Subject:): return line[8:].strip() return No Subject def _save_email(self, sender, recipient, subject, body): conn sqlite3.connect(self.db_path) conn.execute( INSERT INTO emails (sender, recipient, subject, body) VALUES (?, ?, ?, ?), (sender, recipient, subject, body) ) conn.commit() conn.close() if __name__ __main__: server SMTPServer(port2525) # 避免占用系统 25 端口需 root 权限 server.start()提示这段代码是课设可交付的最小可行骨架。它实现了HELO,MAIL FROM,RCPT TO,DATA,QUIT全流程状态机清晰可 debug。关键点在于所有响应必须以\r\n结尾Windows 风格换行少一个字符客户端就卡死DATA后接收内容直到单独一行.即CRLF.CRLF这是 SMTP 最易翻车的边界数据库存储用sqlite3是因为轻量、无需额外服务、.db文件可直接拖进实验报告附件。2.2 用 telnet 手动验证 SMTP 协议交互别急着写客户端先用最原始方式确认服务端工作正常# 终端 1启动 SMTP 服务端口 2525 python smtp_server.py # 终端 2telnet 连接macOS/Linux 直接用Windows 需启用 telnet 客户端 telnet 127.0.0.1 2525然后手动输入每行后按回车HELO localhost MAIL FROM:testlocal RCPT TO:userlocal DATA Subject: Test from telnet Hello, this is a test email. . QUIT观察服务端打印日志和mails.db是否新增记录。这一步能让你亲眼看到220,250,354,250等状态码如何随命令流转——比看 Wireshark 抓包更直击本质。2.3 关键参数与可调项说明参数默认值说明课设调整建议port2525监听端口必须避开 25需 root、465SSL、587TLS2525 是开发常用替代端口stateINIT协议状态机变量不要删掉状态判断否则MAIL FROM在HELO前执行也会成功违反 RFCdata_buffer[]存储 DATA 内容的列表注意.split(\n)可能因换行符不一致出错课设建议统一用\n处理sqlite3表结构emails表存储字段精简为 sender/recipient/subject/body如需支持多收件人将rcpt_to改为 JSON 字符串存入新字段3. 实现 POP3 服务端状态机、UIDL 命令与邮件索引映射POP3 比 SMTP 更“懒”它不主动推送只等客户端拉取没有复杂的事务状态核心就三步——USER/PASS登录 →LIST/UIDL查列表 →RETR取邮件 →DELE标记删除 →QUIT提交。但它的坑藏在细节里UIDL必须全局唯一且不变RETR n的n是服务器维护的内部序号非数据库 IDTOP命令要截断正文……这些在谢希仁教材里一笔带过却让 70% 的课设 POP3 实现无法被 Thunderbird 正常识别。3.1 POP3 状态机与登录认证逻辑import socket import threading import sqlite3 import time import base64 class POP3Server: def __init__(self, host127.0.0.1, port1100): self.host host self.port port self.db_path mails.db self.user_credentials {test: 123456} # 课设简化明文密码勿用于生产 self.current_user None def start(self): server_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((self.host, self.port)) server_socket.listen(5) print(f[POP3] Server listening on {self.host}:{self.port}) while True: client_socket, addr server_socket.accept() print(f[POP3] New connection from {addr}) thread threading.Thread(targetself.handle_client, args(client_socket,)) thread.daemon True thread.start() def handle_client(self, client_socket): # POP3 初始欢迎OK 开头 client_socket.send(bOK POP3 server ready\r\n) state AUTHORIZATION # POP3 两阶段AUTHORIZATION → TRANSACTION user None pass_ok False while True: try: data client_socket.recv(1024).decode(utf-8).strip() if not data: break cmd data.split()[0].upper() args data[len(cmd):].strip() if len(data) len(cmd) else if state AUTHORIZATION: if cmd USER: user args client_socket.send(bOK User accepted\r\n) elif cmd PASS: if user and self._check_password(user, args): pass_ok True client_socket.send(bOK Password accepted\r\n) state TRANSACTION else: client_socket.send(b-ERR Invalid password\r\n) elif cmd QUIT: client_socket.send(bOK Goodbye\r\n) break else: client_socket.send(b-ERR Command not valid in this state\r\n) elif state TRANSACTION: if cmd STAT: # 返回邮件总数和总大小单位字节 count, size self._get_mail_stats() client_socket.send(fOK {count} {size}\r\n.encode()) elif cmd LIST: if not args: # 列出全部序号 大小 mails self._get_all_mails() response OK str(len(mails)) messages\r\n for i, mail in enumerate(mails, 1): response f{i} {len(mail[4].encode(utf-8))}\r\n response .\r\n client_socket.send(response.encode()) else: # LIST n返回第 n 封邮件大小 idx int(args) mail self._get_mail_by_index(idx) if mail: size len(mail[4].encode(utf-8)) client_socket.send(fOK {idx} {size}\r\n.encode()) else: client_socket.send(b-ERR No such message\r\n) elif cmd UIDL: if not args: # UIDL 无参返回所有邮件 UID格式序号 UID mails self._get_all_mails() response OK\r\n for i, mail in enumerate(mails, 1): # UID 必须稳定用 sendertimestampid 拼接避免纯自增ID变动 uid f{mail[1]}_{int(time.mktime(time.strptime(mail[5], %Y-%m-%d %H:%M:%S)))}_{mail[0]} response f{i} {uid}\r\n response .\r\n client_socket.send(response.encode()) else: # UIDL n返回第 n 封 UID idx int(args) mail self._get_mail_by_index(idx) if mail: uid f{mail[1]}_{int(time.mktime(time.strptime(mail[5], %Y-%m-%d %H:%M:%S)))}_{mail[0]} client_socket.send(fOK {idx} {uid}\r\n.encode()) else: client_socket.send(b-ERR No such message\r\n) elif cmd RETR: idx int(args) mail self._get_mail_by_index(idx) if mail: # 构造标准 POP3 邮件格式头部 空行 正文 headers [ fFrom: {mail[1]}, fTo: {mail[2]}, fSubject: {mail[3]}, fDate: {mail[5]}, MIME-Version: 1.0, Content-Type: text/plain; charsetutf-8 ] full_mail \r\n.join(headers) \r\n\r\n mail[4] # POP3 要求每行以 \r\n 结尾且正文中的 . 需转义为 .. lines full_mail.split(\n) escaped_lines [] for line in lines: if line.strip() .: escaped_lines.append(..) elif line.startswith(.): escaped_lines.append(. line) else: escaped_lines.append(line) full_mail_escaped \r\n.join(escaped_lines) client_socket.send(fOK {len(full_mail_escaped.encode())} octets\r\n.encode()) client_socket.send(full_mail_escaped.encode() b\r\n.\r\n) else: client_socket.send(b-ERR No such message\r\n) elif cmd DELE: idx int(args) # 课设简化直接删库真实场景应标记 deleted1 self._delete_mail_by_index(idx) client_socket.send(fOK message {idx} deleted\r\n.encode()) elif cmd QUIT: # QUIT 时才真正删除 DELE 标记的邮件POP3 规范 client_socket.send(bOK Goodbye\r\n) break else: client_socket.send(b-ERR Command not recognized\r\n) except ValueError as e: client_socket.send(b-ERR Invalid argument\r\n) except Exception as e: print(f[POP3] Error: {e}) break client_socket.close() def _check_password(self, user, password): return user in self.user_credentials and self.user_credentials[user] password def _get_mail_stats(self): conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(SELECT COUNT(*), SUM(LENGTH(body)) FROM emails) row cursor.fetchone() conn.close() return row[0], row[1] or 0 def _get_all_mails(self): conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(SELECT * FROM emails ORDER BY id ASC) rows cursor.fetchall() conn.close() return rows def _get_mail_by_index(self, index): # POP3 序号从 1 开始对应数据库中第 index 条按 id ASC conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(SELECT * FROM emails ORDER BY id ASC LIMIT 1 OFFSET ?, (index-1,)) row cursor.fetchone() conn.close() return row def _delete_mail_by_index(self, index): # 获取第 index 封邮件的 id conn sqlite3.connect(self.db_path) cursor conn.cursor() cursor.execute(SELECT id FROM emails ORDER BY id ASC LIMIT 1 OFFSET ?, (index-1,)) row cursor.fetchone() if row: cursor.execute(DELETE FROM emails WHERE id ?, (row[0],)) conn.commit() conn.close() if __name__ __main__: server POP3Server(port1100) server.start()逻辑说明UIDL命令生成的 UID 必须满足两个条件全局唯一用sender_timestamp_id拼接、长期稳定同一封邮件每次UIDL返回相同字符串。若用纯自增 ID邮件删后再插入UID 就变了客户端会认为是新邮件重复下载RETR返回的邮件体必须做.转义遇到单独一行.要变成..否则客户端误判为邮件结束DELE只是标记QUIT才提交删除——这是 POP3 的原子性保障课设必须体现。3.2 用 telnet 验证 POP3 流程重点测 UIDL 和 RETRtelnet 127.0.0.1 1100输入USER test PASS 123456 STAT LIST UIDL RETR 1 QUIT观察RETR 1返回的邮件是否包含正确From:/To:/Subject:头部且正文中的.是否被转义。这是区分“能跑”和“符合 RFC”的分水岭。4. SMTP 客户端 POP3 客户端联动用 Python 实现跨协议邮件收发闭环光有服务端不够课设要求“能发能收”。我们不用smtplib/poplib而是手写 socket 客户端——这样你能看清每一行协议交互debug 时直接print(recv_data)。重点解决两个问题SMTP 认证失败时的重试逻辑、POP3RETR返回的邮件体解析。4.1 手写 SMTP 客户端支持 AUTH LOGIN 与 Base64 编码import socket import base64 def send_email_smtp( smtp_host127.0.0.1, smtp_port2525, sendertestlocal, recipientuserlocal, subjectTest Subject, bodyHello from custom SMTP client!, usernametest, password123456 ): client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((smtp_host, smtp_port)) # 读取欢迎消息 welcome client_socket.recv(1024).decode() print(f[SMTP Client] {welcome.strip()}) # HELO client_socket.send(bHELO localhost\r\n) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # AUTH LOGIN client_socket.send(bAUTH LOGIN\r\n) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # 发送 Base64 用户名 client_socket.send(base64.b64encode(username.encode()) b\r\n) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # 发送 Base64 密码 client_socket.send(base64.b64encode(password.encode()) b\r\n) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # MAIL FROM client_socket.send(fMAIL FROM:{sender}\r\n.encode()) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # RCPT TO client_socket.send(fRCPT TO:{recipient}\r\n.encode()) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # DATA client_socket.send(bDATA\r\n) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # 发送邮件内容注意头部与正文空行 email_content fFrom: {sender} To: {recipient} Subject: {subject} {body} . client_socket.send(email_content.encode()) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) # QUIT client_socket.send(bQUIT\r\n) resp client_socket.recv(1024).decode() print(f[SMTP Client] {resp.strip()}) client_socket.close() print([SMTP Client] Email sent successfully!) # 调用示例 send_email_smtp( smtp_host127.0.0.1, smtp_port2525, sendertestlocal, recipientuserlocal, subjectFrom Custom Client, bodyThis email was sent via hand-written SMTP client. )参数说明username/password必须与 SMTP 服务端_check_password中定义一致email_content中From:/To:/Subject:是必需头部缺一不可否则 POP3 客户端解析失败.单独一行是 DATA 结束标志前后必须有\r\n。4.2 手写 POP3 客户端解析 UIDL 列表并提取第一封邮件import socket import re def fetch_first_email_pop3( pop3_host127.0.0.1, pop3_port1100, usernametest, password123456 ): client_socket socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect((pop3_host, pop3_port)) # 读取欢迎 welcome client_socket.recv(1024).decode() print(f[POP3 Client] {welcome.strip()}) # USER/PASS client_socket.send(fUSER {username}\r\n.encode()) resp client_socket.recv(1024).decode() print(f[POP3 Client] {resp.strip()}) client_socket.send(fPASS {password}\r\n.encode()) resp client_socket.recv(1024).decode() print(f[POP3 Client] {resp.strip()}) # STAT 查总数 client_socket.send(bSTAT\r\n) resp client_socket.recv(1024).decode() print(f[POP3 Client] {resp.strip()}) # LIST 查列表 client_socket.send(bLIST\r\n) resp client_socket.recv(4096).decode() print(f[POP3 Client] {resp.strip()}) # UIDL 查 UID验证稳定性 client_socket.send(bUIDL\r\n) resp client_socket.recv(4096).decode() print(f[POP3 Client] {resp.strip()}) # RETR 第一封 client_socket.send(bRETR 1\r\n) # 分块接收邮件可能较大 full_response b while True: chunk client_socket.recv(1024) full_response chunk if b\r\n.\r\n in full_response: break # 解析 RETR 响应去掉 OK 行和结尾 . 行 lines full_response.decode().split(\r\n) # 找到第一个空行头部结束处 body_start 0 for i, line in enumerate(lines): if line : body_start i 1 break if body_start 0: header_lines lines[1:body_start] # 跳过 OK 行 body_lines lines[body_start:-2] # 去掉最后 . 和空行 print(\n--- Email Header ---) for h in header_lines: print(h) print(\n--- Email Body ---) print(\n.join(body_lines)) # QUIT client_socket.send(bQUIT\r\n) resp client_socket.recv(1024).decode() print(f[POP3 Client] {resp.strip()}) client_socket.close() # 调用 fetch_first_email_pop3()关键点RETR响应以OK ... octets开头以\r\n.\r\n结尾中间是完整邮件含头部空行正文解析时必须找到第一个空行位置前面是头部后面是正文——这是smtplib内部做的事儿手写就得自己拆UIDL返回的 UID 字符串要记录下来下次RETR前先UIDL对比确认邮件未变课设可省略但原理必须懂。5. 避坑指南计算机网络课设 SMTPPOP3 实现的 5 个血泪经验做这个课设我见过太多同学卡在看似 trivial 的地方调试三天发现只是少了一个\r。以下是真实踩过的坑按发生频率排序5.1 现象telnet 连上 SMTP 服务端后输入HELO没反应客户端卡死原因服务端send()发送的响应字符串末尾缺少\r\n只写了\n或没写换行符。SMTP 协议强制要求 CRLF\r\n客户端在等待完整行结束符永远不解析。解决所有send()响应必须显式加b\r\n例如client_socket.send(b220 OK\r\n)。用print(repr(recv_data))查看客户端收到的原始字节确认结尾是\r\n。5.2 现象POP3 客户端RETR 1返回乱码或 Thunderbird 显示“无法解析邮件”原因邮件正文中的.没做转义。POP3 规定邮件体中出现单独一行.必须变为..否则客户端认为这是RETR响应的结束标志提前截断。解决在RETR构造响应体时对每一行做检查lines full_mail.split(\n) escaped_lines [] for line in lines: if line.strip() .: escaped_lines.append(..) elif line.startswith(.): escaped_lines.append(. line) else: escaped_lines.append(line) full_mail_escaped \r\n.join(escaped_lines)5.3 现象SQLite 中存了邮件但LIST命令返回大小为 0或RETR取不到内容原因sqlite3的TEXT字段默认用str存储但len()计算的是 Unicode 字符数不是字节数。POP3STAT/LIST要求返回字节长度octets中文字符一个占 3 字节len(body)返回的是字符数。解决计算大小时用len(body.encode(utf-8))例如size len(mail[4].encode(utf-8)) # mail[4] 是 body 字段5.4 现象AUTH LOGIN后客户端报535 Authentication failed但用户名密码没错原因Base64 编码后的字符串末尾有换行符\n而 SMTP 要求AUTH LOGIN后发送的凭据不能带换行。base64.b64encode()返回 bytes.decode()后字符串自带\n。解决发送前.strip()client_socket.send(base64.b64encode(username.encode()).decode().strip().encode() b\r\n)5.5 现象服务端多线程运行但并发发两封邮件时第二封丢失或数据库报database is locked原因sqlite3默认是 serialized 模式多个线程同时INSERT会阻塞甚至超时。课设常见错误是每个请求都新建sqlite3.connect()但没设timeout。解决在connect()时加timeout参数并用with确保连接关闭conn sqlite3.connect(self.db_path, timeout10) # 等待 10 秒 try: conn.execute(INSERT ...) conn.commit() finally: conn.close()或者更稳妥用连接池课设可用单例模式管理一个连接。6. 进阶技巧用 Wireshark 抓包对比 RFC 文档把课设答辩变成原理现场推演别把 Wireshark 当成“抓包工具”它是你的协议显微镜。课设做到这一步答辩时老师问“SMTP 的DATA命令之后客户端怎么知道服务器准备好了”你直接打开抓包文件放大到那一帧指着354 Start mail input...说“老师您看这是服务器返回的状态码 354表示已进入邮件数据接收态RFC 5321 第 4.1.1 节明确要求此响应必须包含此短语”——瞬间从“我写了代码”升级为“我验证了协议”。6.1 设置 Wireshark 过滤器聚焦 SMTP/POP3 流量启动 SMTP 和 POP3 服务端后在 Wireshark 输入过滤表达式tcp.port 2525 || tcp.port 1100这会只显示你服务端的流量。右键任一数据包 → “Follow” → “TCP Stream”就能看到完整的 ASCII 交互包括\r\n。6.2 对照 RFC 验证关键字段附速查表协议RFC 文档章节课设必验字段Wireshark 中如何定位SMTPRFC 5321 §4.1.1220欢迎、250成功、354DATA 准备、235认证成功Follow TCP Stream 后搜索220/250SMTPRFC 5321 §4.1.2MAIL FROM:addr中addr必须有尖括号搜索MAIL FROM:检查是否有POP3RFC 1939 §6OK开头的成功响应、-ERR开头的错误响应搜索OK/-ERR确认大小写和空格POP3RFC 1939 §7UIDL返回的 UID 必须稳定同一邮件多次UIDL返回相同字符串多次执行UIDL对比返回的 UID 字符串6.3 用 Python 自动生成 RFC 符合性报告本文还有配套的精品资源点击获取
网站建设高端定制企业官网