Tio Boot DocsTio Boot Docs
Home
  • java-db
  • api-table
  • Enjoy
  • Tio Boot Admin
  • ai_agent
  • translator
  • knowlege_base
  • ai-search
  • 案例
Abount
  • Github
  • Gitee
Home
  • java-db
  • api-table
  • Enjoy
  • Tio Boot Admin
  • ai_agent
  • translator
  • knowlege_base
  • ai-search
  • 案例
Abount
  • Github
  • Gitee
  • 01_tio-boot 简介

    • tio-boot:新一代高性能 Java Web 开发框架
    • tio-boot 入门示例
    • Tio-Boot 配置 : 现代化的配置方案
    • tio-boot 整合 Logback
    • tio-boot 整合 hotswap-classloader 实现热加载
    • 自行编译 tio-boot
    • 最新版本
    • 开发规范
  • 02_部署

    • 使用 Maven Profile 实现分环境打包 tio-boot 项目
    • Maven 项目配置详解:依赖与 Profiles 配置
    • tio-boot 打包成 FastJar
    • 使用 GraalVM 构建 tio-boot Native 程序
    • 使用 Docker 部署 tio-boot
    • 部署到 Fly.io
    • 部署到 AWS Lambda
    • 到阿里云云函数
    • 使用 Deploy 工具部署
    • 使用Systemctl启动项目
    • 使用 Jenkins 部署 Tio-Boot 项目
    • 使用 Nginx 反向代理 Tio-Boot
    • 使用 Supervisor 管理 Java 应用
    • 已过时
    • 胖包与瘦包的打包与部署
  • 03_配置

    • 配置参数
    • 服务器监听器
    • 内置缓存系统 AbsCache
    • 使用 Redis 作为内部 Cache
    • 静态文件处理器
    • 基于域名的静态资源隔离
    • DecodeExceptionHandler
  • 04_原理

    • 生命周期
    • 请求处理流程
    • 重要的类
  • 05_json

    • Json
    • 接受 JSON 和响应 JSON
    • 响应实体类
  • 06_web

    • 概述
    • 文件上传
    • 接收请求参数
    • 接收日期参数
    • 接收数组参数
    • 返回字符串
    • 返回文本数据
    • 返回网页
    • 请求和响应字节
    • 文件下载
    • 返回视频文件并支持断点续传
    • http Session
    • Cookie
    • HttpRequest
    • HttpResponse
    • Resps
    • RespBodyVo
    • 拦截器
    • 全局异常处理器
    • 异步
    • 动态 返回 CSS 实现
    • 返回图片
    • Transfer-Encoding: chunked 实时音频播放
    • Server-Sent Events (SSE)
    • 接口访问统计
    • 接口请求和响应数据记录
    • 自定义 Handler 转发请求
    • 使用 HttpForwardHandler 转发所有请求
    • 跨域
    • 添加 Controller
    • 常用工具类
    • HTTP Basic 认证
    • Http响应加密
    • 在 Tio-boot 中使用零拷贝发送大文件
    • WebJars
    • JProtobuf
    • Tio-Boot HTTP Speed Test
  • 07_validate

    • 数据紧校验规范
    • 参数校验
  • 08_websocket

    • 使用 tio-boot 搭建 WebSocket 服务
    • WebSocket 聊天室项目示例
  • 09_java-db

    • java‑db
    • 操作数据库入门示例
    • SQL 模板 (SqlTemplates)
    • 数据源配置与使用
    • ActiveRecord
    • Db 工具类
    • 批量操作
    • Model
    • 生成器与 Model
    • 异常处理
    • 数据库事务处理
    • Cache 缓存
    • Dialect 多数据库支持
    • 表关联操作
    • 复合主键
    • Oracle 支持
    • Enjoy SQL 模板
    • 整合 Enjoy 模板最佳实践
    • 多数据源支持
    • 独立使用 ActiveRecord
    • 调用存储过程
    • java-db 整合 Guava 的 Striped 锁优化
    • 生成 SQL
    • 通过实体类操作数据库
    • java-db 读写分离
    • Spring Boot 整合 Java-DB
    • like 查询
    • 常用操作示例
    • Druid 监控集成指南
    • SQL 统计
  • 10_api-table

    • ApiTable 概述
    • 使用 ApiTable 连接 SQLite
    • 使用 ApiTable 连接 Mysql
    • 使用 ApiTable 连接 Postgres
    • 使用 ApiTable 连接 TDEngine
    • 使用 api-table 连接 oracle
    • 使用 api-table 连接 mysql and tdengine 多数据源
    • EasyExcel 导出
    • EasyExcel 导入
    • TQL(Table SQL)前端输入规范
    • ApiTable 实现增删改查
    • 数组类型
    • 单独使用 ApiTable
  • 11_aop

    • JFinal-aop
    • Aop 工具类
    • 配置
    • 配置
    • 独立使用 JFinal Aop
    • @AImport
    • 原理解析
  • 12_cache

    • Caffine
    • Jedis-redis
    • hutool RedisDS
    • Redisson
    • Caffeine and redis
    • CacheUtils 工具类
    • 使用 CacheUtils 整合 caffeine 和 redis 实现的两级缓存
    • 使用 java-db 整合 ehcache
    • 使用 java-db 整合 redis
    • Java DB Redis 相关 Api
    • redis 使用示例
  • 13_认证和权限

    • hutool-JWT
    • FixedTokenInterceptor
    • 使用内置 TokenManager 实现登录
    • 用户系统
    • 重置密码
    • 匿名登录
    • Google 登录
    • 权限校验注解
    • Sa-Token
    • sa-token 登录注册
    • StpUtil.isLogin() 源码解析
    • 短信登录
    • 移动端微信登录实现指南
    • 移动端重置密码
  • 14_i18n

    • i18n
  • 15_enjoy

    • tio-boot 整合 Enjoy 模版引擎文档
    • 引擎配置
    • 表达式
    • 指令
    • 注释
    • 原样输出
    • Shared Method 扩展
    • Shared Object 扩展
    • Extension Method 扩展
    • Spring boot 整合
    • 独立使用 Enjoy
    • tio-boot enjoy 自定义指令 localeDate
    • PromptEngine
    • Enjoy 入门示例-擎渲染大模型请求体
    • Enjoy 使用示例
  • 16_定时任务

    • Quartz 定时任务集成指南
    • 分布式定时任务 xxl-jb
    • cron4j 使用指南
  • 17_tests

    • TioBootTest 类
  • 18_tio

    • TioBootServer
    • 使用 tio-core 在 tio-boot 中构建独立的 TCP 服务器
    • 内置 TCP 处理器
    • 独立启动 UDPServer
    • 使用内置 UDPServer
    • t-io 消息处理流程
    • tio-运行原理详解
    • TioConfig
    • ChannelContext
    • Tio 工具类
    • 业务数据绑定
    • 业务数据解绑
    • 发送数据
    • 关闭连接
    • Packet
    • 监控: 心跳
    • 监控: 客户端的流量数据
    • 监控: 单条 TCP 连接的流量数据
    • 监控: 端口的流量数据
    • 单条通道统计: ChannelStat
    • 所有通道统计: GroupStat
    • 资源共享
    • 成员排序
    • SSL
    • DecodeRunnable
    • 使用 AsynchronousSocketChannel 响应数据
    • 拉黑 IP
    • 深入解析 Tio 源码:构建高性能 Java 网络应用
  • 19_aio

    • ByteBuffer
    • AIO HTTP 服务器
    • 自定义和线程池和池化 ByteBuffer
    • AioHttpServer 应用示例 IP 属地查询
    • 手写 AIO Http 服务器
  • 20_netty

    • Netty TCP Server
    • Netty Web Socket Server
    • 使用 protoc 生成 Java 包文件
    • Netty WebSocket Server 二进制数据传输
    • Netty 组件详解
  • 21_netty-boot

    • Netty-Boot
    • 原理解析
    • 整合 Hot Reload
    • 整合 数据库
    • 整合 Redis
    • 整合 Elasticsearch
    • 整合 Dubbo
    • Listener
    • 文件上传
    • 拦截器
    • Spring Boot 整合 Netty-Boot
    • SSL 配置指南
    • ChannelInitializer
    • Reserve
  • 22_MQ

    • Mica-mqtt
    • EMQX
    • Disruptor
  • 23_tio-utils

    • tio-utils
    • HttpUtils
    • Notification
    • 邮箱
    • JSON
    • 读取文件
    • Base64
    • 上传和下载
    • Http
    • Telegram
    • RsaUtils
    • EnvUtils
    • 系统监控
    • 毫秒并发 ID (MCID) 生成方案
  • 24_tio-http-server

    • 使用 Tio-Http-Server 搭建简单的 HTTP 服务
    • tio-boot 添加 HttpRequestHandler
    • 在 Android 上使用 tio-boot 运行 HTTP 服务
    • tio-http-server-native
    • handler 常用操作
  • 25_tio-websocket

    • WebSocket 服务器
    • WebSocket Client
  • 26_tio-im

    • 通讯协议文档
    • ChatPacket.proto 文档
    • java protobuf
    • 数据表设计
    • 创建工程
    • 登录
    • 历史消息
    • 发消息
  • 27_mybatis

    • Tio-Boot 整合 MyBatis
    • 使用配置类方式整合 MyBatis
    • 整合数据源
    • 使用 mybatis-plus 整合 tdengine
    • 整合 mybatis-plus
  • 28_mongodb

    • tio-boot 使用 mongo-java-driver 操作 mongodb
  • 29_elastic-search

    • Elasticsearch
    • JavaDB 整合 ElasticSearch
    • Elastic 工具类使用指南
    • Elastic-search 注意事项
    • ES 课程示例文档
  • 30_magic-script

    • tio-boot 与 magic-script 集成指南
  • 31_groovy

    • tio-boot 整合 Groovy
  • 32_firebase

    • 整合 google firebase
    • Firebase Storage
    • Firebase Authentication
    • 使用 Firebase Admin SDK 进行匿名用户管理与自定义状态标记
    • 导出用户
    • 注册回调
    • 登录注册
  • 33_文件存储

    • 文件上传数据表
    • 本地存储
    • 使用 AWS S3 存储文件并整合到 Tio-Boot 项目中
    • 存储文件到 腾讯 COS
  • 34_spider

    • jsoup
    • 爬取 z-lib.io 数据
    • 整合 WebMagic
    • WebMagic 示例:爬取学校课程数据
    • Playwright
    • Flexmark (Markdown 处理器)
    • tio-boot 整合 Playwright
    • 缓存网页数据
  • 36_integration_thirty_party

    • tio-boot 整合 okhttp
    • 整合 GrpahQL
    • 集成 Mailjet
    • 整合 ip2region
    • 整合 GeoLite 离线库
    • 整合 Lark 机器人指南
    • 集成 Lark Mail 实现邮件发送
    • Thymeleaf
    • Swagger
    • Clerk 验证
  • 37_dubbo

    • 概述
    • dubbo 2.6.0
    • dubbo 2.6.0 调用过程
    • dubbo 3.2.0
  • 38_spring

    • Spring Boot Web 整合 Tio Boot
    • spring-boot-starter-webflux 整合 tio-boot
    • Tio Boot 整合 Spring Boot Starter
    • Tio Boot 整合 Spring Boot Starter Data Redis 指南
  • 39_spring-cloud

    • tio-boot spring-cloud
  • 40_mysql

    • 使用 Docker 运行 MySQL
    • /zh/42_mysql/02.html
  • 41_postgresql

    • PostgreSQL 安装
    • PostgreSQL 主键自增
    • PostgreSQL 日期类型
    • Postgresql 金融类型
    • PostgreSQL 数组类型
    • PostgreSQL 全文检索
    • PostgreSQL 查询优化
    • 获取字段类型
    • PostgreSQL 向量
    • PostgreSQL 优化向量查询
    • PostgreSQL 其他
  • 43_oceanbase

    • 快速体验 OceanBase 社区版
    • 快速上手 OceanBase 数据库单机部署与管理
    • 诊断集群性能
    • 优化 SQL 性能指南
    • /zh/43_oceanbase/05.html
  • 50_media

    • JAVE 提取视频中的声音
    • Jave 提取视频中的图片
    • /zh/50_media/03.html
  • 51_asr

    • Whisper-JNI
  • 54_native-media

    • java-native-media
    • JNI 入门示例
    • mp3 拆分
    • mp4 转 mp3
    • 使用 libmp3lame 实现高质量 MP3 编码
    • Linux 编译
    • macOS 编译
    • 从 JAR 包中加载本地库文件
    • 支持的音频和视频格式
    • 任意格式转为 mp3
    • 通用格式转换
    • 通用格式拆分
    • 视频合并
    • VideoToHLS
    • split_video_to_hls 支持其他语言
    • 持久化 HLS 会话
  • 55_telegram4j

    • 数据库设计
    • /zh/55_telegram4j/02.html
    • 基于 MTProto 协议开发 Telegram 翻译机器人
    • 过滤旧消息
    • 保存机器人消息
    • 定时推送
    • 增加命令菜单
    • 使用 telegram-Client
    • 使用自定义 StoreLayout
    • 延迟测试
    • Reactor 错误处理
    • Telegram4J 常见错误处理指南
  • 56_telegram-bots

    • TelegramBots 入门指南
    • 使用工具库 telegram-bot-base 开发翻译机器人
  • 60_LLM

    • 简介
    • AI 问答
    • /zh/60_LLM/03.html
    • /zh/60_LLM/04.html
    • 增强检索(RAG)
    • 结构化数据检索
    • 搜索+AI
    • 集成第三方 API
    • 后置处理
    • 推荐问题生成
    • 连接代码执行器
    • 避免 GPT 混乱
    • /zh/60_LLM/13.html
  • 61_ai_agent

    • 数据库设计
    • 示例问题管理
    • 会话管理
    • 历史记录
    • 对接 Perplexity API
    • 意图识别与生成提示词
    • 智能问答模块设计与实现
    • 文件上传与解析文档
    • 翻译
    • 名人搜索功能实现
    • Ai studio gemini youbue 问答使用说明
    • 自建 YouTube 字幕问答系统
    • 自建 获取 youtube 字幕服务
    • 通用搜索
    • /zh/61_ai_agent/15.html
    • 16
    • 17
    • 18
    • 在 tio-boot 应用中整合 ai-agent
    • 16
  • 62_translator

    • 简介
  • 63_knowlege_base

    • 数据库设计
    • 用户登录实现
    • 模型管理
    • 知识库管理
    • 文档拆分
    • 片段向量
    • 命中测试
    • 文档管理
    • 片段管理
    • 问题管理
    • 应用管理
    • 向量检索
    • 推理问答
    • 问答模块
    • 统计分析
    • 用户管理
    • api 管理
    • 存储文件到 S3
    • 文档解析优化
    • 片段汇总
    • 段落分块与检索
    • 多文档解析
    • 对话日志
    • 检索性能优化
    • Milvus
    • 文档解析方案和费用对比
    • 离线运行向量模型
  • 64_ai-search

    • ai-search 项目简介
    • ai-search 数据库文档
    • ai-search SearxNG 搜索引擎
    • ai-search Jina Reader API
    • ai-search Jina Search API
    • ai-search 搜索、重排与读取内容
    • ai-search PDF 文件处理
    • ai-search 推理问答
    • Google Custom Search JSON API
    • ai-search 意图识别
    • ai-search 问题重写
    • ai-search 系统 API 接口 WebSocket 版本
    • ai-search 搜索代码实现 WebSocket 版本
    • ai-search 生成建议问
    • ai-search 生成问题标题
    • ai-search 历史记录
    • Discover API
    • 翻译
    • Tavily Search API 文档
    • 对接 Tavily Search
    • 火山引擎 DeepSeek
    • 对接 火山引擎 DeepSeek
    • ai-search 搜索代码实现 SSE 版本
    • jar 包部署
    • Docker 部署
    • 爬取一个静态网站的所有数据
    • 网页数据预处理
    • 网页数据检索与问答流程整合
  • 65_java-kit-server

    • Java 执行 python 代码
    • 通过大模型执行 Python 代码
    • MCP 协议
    • Cline 提示词
    • Cline 提示词-中文版本
  • 66_manim

    • Teach me anything - 基于大语言的知识点讲解视频生成系统
    • Manim 开发环境搭建
    • 生成场景提示词
    • 生成代码
    • 完整脚本示例
    • 语音合成系统
    • Fish.audio TTS 接口说明文档与 Java 客户端封装
    • 整合 fishaudio 到 java-uni-ai-server 项目
    • 执行 Python (Manim) 代码
    • 使用 SSE 流式传输生成进度的实现文档
    • 整合全流程完整文档
    • HLS 动态推流技术文档
    • manim 分场景生成代码
    • 分场景运行代码及流式播放支持
    • 分场景业务端完整实现流程
    • Maiim布局管理器
    • 仅仅生成场景代码
    • 使用 modal 运行 manim 代码
    • Python 使用 Modal GPU 加速渲染
    • Modal 平台 GPU 环境下运行 Manim
    • Modal Manim OpenGL 安装与使用
    • 优化 GPU 加速
    • 生成视频封面流程
    • Java 调用 manim 命令 执行代码 生成封面
    • Manim 图像生成服务客户端文档
    • manim render help
    • 显示 中文公式
    • manimgl
    • EGL
    • /zh/66_manim/30.html
    • /zh/66_manim/31.html
    • /zh/66_manim/32.html
    • /zh/66_manim/33.html
  • 68_java-llm-proxy

    • 使用tio-boot搭建openai 代理服务
  • 70_tio-boot-admin

    • 入门指南
    • 初始化数据
    • token 存储
    • 与前端集成
    • 文件上传
    • 网络请求
    • 图片管理
    • /zh/70_tio-boot-admin/08.html
    • Word 管理
    • PDF 管理
    • 文章管理
    • 富文本编辑器
  • 71_tio-boot

    • /zh/71_tio-boot/01.html
    • Swagger 整合到 Tio-Boot 中的指南
    • HTTP/1.1 Pipelining 性能测试报告
  • 73_tio-mail-wing

    • tio-mail-wing简介
    • 任务1:实现POP3系统
    • 使用 getmail 验证 tio-mail-wing POP3 服务
    • 任务2:实现 SMTP 服务
    • 数据库初始化文档
    • 用户管理
    • 邮件管理
    • 任务3:实现 SMTP 服务 数据库版本
    • 任务4:实现 POP3 服务(数据库版本)
    • IMAP 协议
    • 任务5:实现 IMAP 服务(数据库版本)
    • IMAP实现讲解
    • IMAP 手动测试脚本
  • 80_性能测试

    • 压力测试 - tio-http-serer
    • 压力测试 - tio-boot
    • 压力测试 - tio-boot-native
    • 压力测试 - netty-boot
    • 性能测试对比
    • TechEmpower FrameworkBenchmarks
    • 压力测试 - tio-boot 12 C 32G
  • 99_案例

    • 封装 IP 查询服务
    • tio-boot 案例 - 全局异常捕获与企业微信群通知
    • tio-boot 案例 - 文件上传和下载
    • tio-boot 案例 - 整合 ant design pro 增删改查
    • tio-boot 案例 - 流失响应
    • tio-boot 案例 - 增强检索
    • tio-boot 案例 - 整合 function call
    • tio-boot 案例 - 定时任务 监控 PostgreSQL、Redis 和 Elasticsearch
    • Tio-Boot 案例:使用 SQLite 整合到登录注册系统
    • tio-boot 案例 - 执行 shell 命令

任务1:实现POP3系统

POP3 协议详解 (Post Office Protocol version 3)

POP3 是一种应用层协议,其全称为“邮局协议第3版”。顾名思义,它的工作模式非常像现实生活中的邮局信箱。

核心思想: 你去邮局的信箱取信,把所有信件都拿出来带回家,然后你的信箱就空了。你在家阅读、整理、删除这些信件,这些操作都与邮局无关了。

POP3 就是这样一个“下载并删除”的协议。它允许邮件客户端连接到邮件服务器,将收件箱(INBOX)中的所有邮件下载到本地计算机,然后通常会从服务器上删除这些邮件。


主要特点

  1. 简单性 (Simplicity): POP3 协议非常简单,命令数量少,交互逻辑直接。这使得客户端和服务器的实现都相对容易。

  2. 离线工作模式 (Offline Model): 它是为离线邮件阅读而设计的。用户连接网络,一次性收取所有新邮件,然后可以断开网络,在本地慢慢阅读、回复和管理邮件。

  3. 状态化会话 (Stateful Session): 一个 POP3 会话包含三个明确定义的状态。服务器会根据客户端当前所处的状态来决定哪些命令是合法的。

  4. 服务器资源占用少 (Low Server Resource Usage): 由于邮件默认被下载到本地后就从服务器删除,服务器不需要长期存储大量邮件,节省了存储空间。

  5. 不适合多设备同步 (Poor for Multi-Device Sync): 这是它最大的缺点。如果你在电脑上用 POP3 收取了邮件,这些邮件就会从服务器上消失。此时你再用手机登录,将看不到任何旧邮件,因为它们已经“被你从邮局带回家了”。(虽然现在很多客户端提供了“在服务器上保留副本”的选项,但这并非 POP3 的原生设计,且管理起来依然不便)。


POP3 会话的三个阶段

一个典型的 POP3 连接过程分为三个阶段:

1. 授权 (AUTHORIZATION) 阶段

这是会话的开始阶段,客户端需要向服务器验明身份。

  • 目标:登录邮箱。
  • 过程:客户端连接到服务器的 110 端口后,服务器会发送一个 +OK 的欢迎语。然后客户端必须通过 USER 和 PASS 命令提供用户名和密码。
  • 可用命令:USER, PASS, QUIT。
  • 状态转换:一旦用户名和密码验证成功,会话就从 授权 阶段进入 事务 阶段。如果验证失败,会话仍停留在 授权 阶段。如果客户端发送 QUIT,则直接进入 更新 阶段。

2. 事务 (TRANSACTION) 阶段

这是会话的核心阶段,客户端在此阶段进行邮件的查询、获取和标记删除。

  • 目标:管理邮件。
  • 过程:客户端可以查看邮箱统计信息、获取邮件列表、下载邮件内容,并标记要删除的邮件。
  • 重要提示:在这一阶段,DELE 命令只是“标记”邮件为待删除,并不会立即从服务器上真正删除。
  • 可用命令:
    • STAT: 获取邮箱状态(邮件总数和总大小)。
    • LIST: 列出每封邮件的编号和大小。
    • RETR [msg_id]: 获取指定编号的邮件全文。
    • DELE [msg_id]: 标记指定编号的邮件为待删除。
    • NOOP: 无操作,用于保持连接活动,服务器会回复 +OK。
    • RSET: 重置,清除所有在本阶段做的 DELE 标记。
    • QUIT: 结束事务,准备退出。
  • 状态转换:当客户端发送 QUIT 命令后,会话进入 更新 阶段。

3. 更新 (UPDATE) 阶段

这是会话的结束阶段,服务器执行清理工作。

  • 目标:应用更改并关闭连接。
  • 过程:服务器会永久删除所有在 事务 阶段被 DELE 命令标记的邮件。完成删除后,服务器向客户端发送一个 +OK 的告别语,然后关闭 TCP 连接。
  • 可用命令:无(此阶段由服务器自动执行,不接受客户端新命令)。

常用 POP3 命令详解

命令格式作用示例可能的响应
USERUSER <username>发送用户名USER user1@tio.com+OK (用户存在) / -ERR (用户不存在)
PASSPASS <password>发送密码PASS mysecret+OK (密码正确) / -ERR (密码错误)
STATSTAT获取邮箱统计信息STAT+OK 2 320 (2封邮件, 共320字节)
LISTLIST [msg_id]列出邮件(不带参数列出所有)LIST+OK 2 messages\r\n1 120\r\n2 200\r\n.
RETRRETR <msg_id>获取指定邮件的完整内容RETR 1+OK 120 octets\r\n(邮件内容)\r\n.
DELEDELE <msg_id>标记邮件为待删除DELE 1+OK message 1 deleted
RSETRSET取消所有删除标记RSET+OK maildrop has 2 messages
NOOPNOOP无操作(心跳)NOOP+OK
QUITQUIT结束会话QUIT+OK dewey POP3 server signing off

响应格式说明:

  • +OK: 表示命令成功执行。后面通常跟着一些说明文字。
  • -ERR: 表示命令执行失败。后面跟着失败原因。
  • 多行响应: 对于 LIST 和 RETR 等可能返回多行数据的命令,响应以 +OK 开始,然后是数据行,最后以一个单独的句点 . 结束。

一个完整的 Telnet 交互示例

下面是一个手动使用 telnet 与 POP3 服务器交互的完整流程。S: 代表服务器响应,C: 代表客户端输入。

# 1. 连接到服务器的 110 端口
$ telnet mail.example.com 110

# 2. 服务器返回欢迎信息,进入【授权】阶段
S: +OK POP3 server ready <1896.697170952@mail.example.com>

# 3. 客户端发送用户名
C: USER alice

# 4. 服务器确认用户存在
S: +OK

# 5. 客户端发送密码
C: PASS mypassword

# 6. 服务器确认密码正确,进入【事务】阶段
S: +OK alice's maildrop has 2 messages (320 octets)

# 7. 客户端查看邮箱状态
C: STAT
S: +OK 2 320

# 8. 客户端列出所有邮件
C: LIST
S: +OK 2 messages (320 octets)
S: 1 120
S: 2 200
S: .

# 9. 客户端获取第一封邮件
C: RETR 1
S: +OK 120 octets
S: From: bob@example.com
S: To: alice@example.com
S: Subject: Hello
S: 
S: This is a test message.
S: .

# 10. 客户端决定删除第一封邮件
C: DELE 1
S: +OK message 1 deleted

# 11. 客户端决定不删除第二封邮件,准备退出
C: QUIT

# 12. 服务器进入【更新】阶段,永久删除被标记的邮件,并发送告别语,然后关闭连接
S: +OK dewey POP3 server signing off (maildrop empty)

# 连接已由外部主机关闭。

官方文档和资源链接

  1. RFC 1939 - Post Office Protocol - Version 3

    • 链接: https://www.rfc-editor.org/rfc/rfc1939
    • 说明: 这是 POP3 协议的最终官方规范,也被称为“协议圣经”。所有关于 POP3 的实现细节、命令格式、状态转换的权威定义都在这里。如果你要从头实现一个 POP3 服务器,这是必读文档。
  2. RFC 2449 - POP3 Extension Mechanism

    • 链接: https://www.rfc-editor.org/rfc/rfc2449
    • 说明: 定义了 POP3 的扩展机制,例如 CAPA 命令,它允许客户端查询服务器支持哪些扩展功能。
  3. RFC 2595 - Using TLS with IMAP, POP3 and ACAP

    • 链接: https://www.rfc-editor.org/rfc/rfc2595
    • 说明: 定义了如何通过 STARTTLS 命令将一个不安全的 POP3 连接升级为安全的 TLS 加密连接。这是实现现代邮件服务安全性的关键。

实现POP3服务


第一步:Pop3Packet 和创建会话上下文

POP3 协议是基于字符串命令的,所以我们的 Packet 直接存储字符串会更方便。同时,我们需要一个会话上下文来跟踪每个客户端连接的状态(例如,是否已登录)。

1. Pop3Packet.java

将 byte[] 改为 String,方便处理命令。

// src/main/java/com/tio/mail/wing/packet/Pop3Packet.java
package com.tio.mail.wing.packet;

import com.litongjava.aio.Packet;

/**
 * POP3 消息包,直接存储解码后的命令或响应字符串
 */
@SuppressWarnings("serial")
public class Pop3Packet extends Packet {
  private String line;

  public Pop3Packet(String line) {
    this.line = line;
  }

  public String getLine() {
    return line;
  }

  public void setLine(String line) {
    this.line = line;
  }
}

2. 创建 Pop3SessionContext.java

这是至关重要的一步,用于保存每个连接的会话状态。

// src/main/java/com/tio/mail/wing/handler/Pop3SessionContext.java
package com.tio.mail.wing.handler;

import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.Tio;
import com.tio.mail.wing.packet.Pop3Packet;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class Pop3SessionContext {
  // 会话状态枚举
  public enum State {
    /**
     * 未认证
     */
    AUTHORIZATION,
    /**
     * 已认证,可以进行邮件操作
     */
    TRANSACTION,
    /**
     * 准备关闭
     */
    UPDATE
  }

  private State state = State.AUTHORIZATION;
  private String username;
  // 可以添加更多字段,如待删除邮件列表等

  /**
   * 发送 OK 响应
   * @param context ChannelContext
   * @param message 消息内容
   */
  public static void sendOk(ChannelContext context, String message) {
    String response = "+OK " + message + "\r\n";
    Tio.send(context, new Pop3Packet(response));
  }

  /**
   * 发送 ERR 响应
   * @param context ChannelContext
   * @param message 消息内容
   */
  public static void sendErr(ChannelContext context, String message) {
    String response = "-ERR " + message + "\r\n";
    Tio.send(context, new Pop3Packet(response));
  }

  /**
   * 发送多行数据
   * @param context ChannelContext
   * @param data 多行数据
   */
  public static void sendData(ChannelContext context, String data) {
    // 多行数据以 "." 结尾
    String response = data + "\r\n.\r\n";
    Tio.send(context, new Pop3Packet(response));
  }
}

第二步:创建模拟的业务服务

为了让 Handler 能跑起来,我们先创建两个简单的内存版服务。

1. Email.java

// src/main/java/com/tio/mail/wing/model/Email.java
package com.tio.mail.wing.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.UUID;

/**
 * 邮件实体类
 * 代表一封存储在服务器上的邮件
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Email {

  /**
   * 唯一标识符 (UIDL)
   * 这是邮件的“身份证”,在邮件的整个生命周期中保持不变。
   * 使用 UUID 或者 数据库自增ID + 时间戳等方式确保唯一。
   */
  private String uid;

  /**
   * 邮件大小 (单位:字节)
   * 用于 LIST 命令的响应。
   */
  private int size;

  /**
   * 邮件的完整原始内容 (MIME 格式)
   * 包括邮件头和邮件体,用于 RETR 命令。
   */
  private String content;

  /**
   * 标记为待删除
   * 用于 DELE 命令。当会话结束时,标记为 true 的邮件将被真正删除。
   */
  private boolean deleted = false;

  /**
   * 一个便捷的构造函数,用于快速创建邮件对象。
   * 它会自动根据内容计算大小并生成 UID。
   * @param content 邮件的原始内容
   */
  public Email(String content) {
    this.content = content;
    // 在真实场景中,字符集转换可能更复杂,这里用 getBytes() 做简单估算
    this.size = content.getBytes().length;
    // 使用 UUID 生成一个唯一的、随机的 ID
    this.uid = UUID.randomUUID().toString().replace("-", "");
  }
}

2. UserService.java

// src/main/java/com/tio/mail/wing/service/UserService.java
package com.tio.mail.wing.service;

import com.litongjava.annotation.AService;
import java.util.HashMap;
import java.util.Map;

public class UserService {
  // 模拟用户数据库
  private static final Map<String, String> users = new HashMap<>();

  static {
    users.put("user1@tio.com", "pass1");
    users.put("user2@tio.com", "pass2");
  }

  /**
   * 认证用户
   * @param username 用户名
   * @param password 密码
   * @return 是否成功
   */
  public boolean authenticate(String username, String password) {
    String storedPassword = users.get(username);
    return storedPassword != null && storedPassword.equals(password);
  }
}

3. MailboxService.java

// src/main/java/com/tio/mail/wing/service/MailboxService.java
package com.tio.mail.wing.service;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

import com.litongjava.annotation.AService;
import com.tio.mail.wing.model.Email;

@AService
public class MailboxService {

  /**
   * 模拟邮件存储系统
   * Key: 用户名 (e.g., "user1@tio.com")
   * Value: 该用户的邮件列表 (List<Email>)
   */
  private static final Map<String, List<Email>> mailboxes = new ConcurrentHashMap<>();

  // 使用静态代码块初始化一些模拟数据
  static {
    // 为 user1@tio.com 创建邮箱并添加两封邮件
    List<Email> user1Emails = new ArrayList<>();
    user1Emails.add(new Email("From: sender@example.com\r\n" + "To: user1@tio.com\r\n" + "Subject: Test Mail 1\r\n" + "\r\n" + "This is the body of the first email."));
    user1Emails.add(new Email("From: another@example.com\r\n" + "To: user1@tio.com\r\n" + "Subject: Hello World\r\n" + "\r\n" + "This is the second message."));
    mailboxes.put("user1@tio.com", user1Emails);

    // 为 user2@tio.com 创建一个空邮箱
    mailboxes.put("user2@tio.com", new ArrayList<>());
  }

  /**
   * 获取用户邮箱中所有未被标记为删除的邮件。
   * @param username 用户名
   * @return 邮件列表
   */
  private List<Email> getActiveMessages(String username) {
    List<Email> userEmails = mailboxes.getOrDefault(username, new ArrayList<>());
    return userEmails.stream().filter(email -> !email.isDeleted()).collect(Collectors.toList());
  }

  /**
   * 获取邮箱状态(邮件数,总大小)
   * @param username 用户名
   * @return int[]{邮件数, 总大小}
   */
  public int[] getStat(String username) {
    List<Email> activeMessages = getActiveMessages(username);
    int count = activeMessages.size();
    int totalSize = activeMessages.stream().mapToInt(Email::getSize).sum();
    return new int[] { count, totalSize };
  }

  /**
   * 获取邮件大小列表,用于 LIST 命令。
   * @param username 用户名
   * @return 邮件大小列表
   */
  public List<Integer> listMessages(String username) {
    return getActiveMessages(username).stream().map(Email::getSize).collect(Collectors.toList());
  }

  /**
   * 获取指定邮件内容
   * @param username 用户名
   * @param msgNumber 邮件序号 (从 1 开始)
   * @return 邮件原始内容
   */
  public String getMessageContent(String username, int msgNumber) {
    List<Email> activeMessages = getActiveMessages(username);
    // 序号从 1 开始,列表索引从 0 开始,需要转换
    if (msgNumber > 0 && msgNumber <= activeMessages.size()) {
      return activeMessages.get(msgNumber - 1).getContent();
    }
    return null;
  }

  /**
   * 获取邮件的唯一ID列表,用于 UIDL 命令。
   * @param username 用户名
   * @return 邮件的唯一ID列表
   */
  public List<String> listUids(String username) {
    return getActiveMessages(username).stream().map(Email::getUid).collect(Collectors.toList());
  }
}

Pop3ServerAioListener

// src/main/java/com/tio/mail/wing/listener/Pop3ServerAioListener.java
package com.tio.mail.wing.listener;

import com.litongjava.aio.Packet;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.server.intf.ServerAioListener;
import com.tio.mail.wing.handler.Pop3SessionContext;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Pop3ServerAioListener implements ServerAioListener {

  @Override
  public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception {
    if (isConnected) {
      log.info("POP3 client connected: {}", channelContext.getClientNode());
      // 1. 创建会话上下文
      Pop3SessionContext sessionContext = new Pop3SessionContext();
      channelContext.set("sessionContext", sessionContext);

      // 2. 立即发送欢迎消息
      Pop3SessionContext.sendOk(channelContext, "tio-mail-wing POP3 server ready.");
      log.info("POP3 >>> +OK welcome message sent to {}", channelContext.getClientNode());
    }
  }
  @Override
  public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception {
    log.info("POP3 client disconnected: {}", channelContext.getClientNode());
  }

  @Override
  public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception {
    // Do nothing
  }

  @Override
  public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception {
    // Do nothing
  }

  @Override
  public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception {
    // Do nothing
  }

  @Override
  public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception {
    // Do nothing
  }

  @Override
  public boolean onHeartbeatTimeout(ChannelContext channelContext, Long interval, int heartbeatTimeoutCount) {
    return false;
  }
}

Pop3ServerAioHandler

这是最核心的修改,我们将实现真正的 POP3 协议逻辑。

// src/main/java/com/tio/mail/wing/handler/Pop3ServerAioHandler.java
package com.tio.mail.wing.handler;

import java.nio.ByteBuffer;
import java.util.List;

import com.litongjava.aio.Packet;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.core.ChannelContext;
import com.litongjava.tio.core.Tio;
import com.litongjava.tio.core.TioConfig;
import com.litongjava.tio.core.exception.LengthOverflowException;
import com.litongjava.tio.core.exception.TioDecodeException;
import com.litongjava.tio.core.utils.ByteBufferUtils;
import com.litongjava.tio.server.intf.ServerAioHandler;
import com.tio.mail.wing.packet.Pop3Packet;
import com.tio.mail.wing.service.MailboxService;
import com.tio.mail.wing.service.UserService;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Pop3ServerAioHandler implements ServerAioHandler {

  private UserService userService = Aop.get(UserService.class);

  private MailboxService mailboxService = Aop.get(MailboxService.class);

  private static final String CHARSET = "UTF-8";

  /**
   * 解码:从 ByteBuffer 中解析出以 \r\n 结尾的一行命令
   */
  @Override
  public Packet decode(ByteBuffer buffer, int limit, int position, int readableLength, ChannelContext channelContext) throws TioDecodeException {
    // Tio内置了行解码器,非常方便
    String line = null;
    try {
      line = ByteBufferUtils.readLine(buffer, CHARSET);
    } catch (LengthOverflowException e) {
      e.printStackTrace();
    }

    // 如果 line 为 null,表示数据不完整,不是一个完整的行,需要等待更多数据
    if (line == null) {
      return null;
    }

    // 返回一个包含该行命令的 Packet
    return new Pop3Packet(line);
  }

  /**
   * 编码:将响应字符串转换为 ByteBuffer
   */
  @Override
  public ByteBuffer encode(Packet packet, TioConfig tioConfig, ChannelContext channelContext) {
    Pop3Packet pop3Packet = (Pop3Packet) packet;
    String line = pop3Packet.getLine();
    try {
      byte[] bytes = line.getBytes(CHARSET);
      ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
      buffer.put(bytes);
      return buffer;
    } catch (Exception e) {
      log.error("Encoding error", e);
      return null;
    }
  }

  /**
   * 消息处理:根据收到的命令和当前会话状态进行响应
   */
  @Override
  public void handler(Packet packet, ChannelContext channelContext) throws Exception {
    Pop3Packet pop3Packet = (Pop3Packet) packet;
    String commandLine = pop3Packet.getLine().trim();
    log.info("POP3 <<< {}", commandLine);

    // 获取或创建会日志上下文
    Pop3SessionContext sessionContext = (Pop3SessionContext) channelContext.get("sessionContext");

    String[] parts = commandLine.split("\\s+", 2);
    String command = parts[0].toUpperCase();

    // 根据会话状态处理命令
    switch (sessionContext.getState()) {
    case AUTHORIZATION:
      handleAuthorizationState(command, parts, channelContext, sessionContext);
      break;
    case TRANSACTION:
      handleTransactionState(command, parts, channelContext, sessionContext);
      break;
    case UPDATE:
      // 在 UPDATE 状态,通常只响应 QUIT
      if ("QUIT".equals(command)) {
        handleQuit(channelContext, sessionContext);
      } else {
        Pop3SessionContext.sendErr(channelContext, "Command not allowed in UPDATE state.");
      }
      break;
    }
  }

  private void handleAuthorizationState(String command, String[] parts, ChannelContext channelContext, Pop3SessionContext sessionContext) {
    switch (command) {
    case "CAPA":
      // 1. 先发送 +OK 响应头
      Pop3SessionContext.sendOk(channelContext, "Capability list follows");

      // 2. 构造多行响应体
      StringBuilder capaBuilder = new StringBuilder();
      capaBuilder.append("TOP\r\n"); // 声明支持 TOP 命令
      capaBuilder.append("USER\r\n"); // 声明支持 USER/PASS 认证
      capaBuilder.append("UIDL\r\n"); // 声明支持 UIDL 命令
      capaBuilder.append("PIPELINING\r\n"); // 声明支持管道,可以提高效率
      // capaBuilder.append("STLS\r\n");  // 如果未来支持 STARTTLS,可以取消这行注释
      capaBuilder.append("."); // 以点结束多行响应

      // 3. 发送响应体
      Tio.send(channelContext, new Pop3Packet(capaBuilder.toString() + "\r\n"));
      // 注意:因为我们自己拼接了多行响应,所以不需要用 sendData 方法,
      // 并且日志也应该在发送时手动打印,以保持一致性。
      log.info("POP3 >>>\n{}", capaBuilder.toString().trim());
      break;
    case "USER":
      if (parts.length < 2) {
        Pop3SessionContext.sendErr(channelContext, "Username required.");
        return;
      }
      sessionContext.setUsername(parts[1]);
      Pop3SessionContext.sendOk(channelContext, "Password required for " + parts[1]);
      break;
    case "PASS":
      if (sessionContext.getUsername() == null) {
        Pop3SessionContext.sendErr(channelContext, "USER command first.");
        return;
      }
      if (parts.length < 2) {
        Pop3SessionContext.sendErr(channelContext, "Password required.");
        return;
      }
      if (userService.authenticate(sessionContext.getUsername(), parts[1])) {
        sessionContext.setState(Pop3SessionContext.State.TRANSACTION);
        Pop3SessionContext.sendOk(channelContext, "Mailbox open.");
      } else {
        Pop3SessionContext.sendErr(channelContext, "Authentication failed.");
        sessionContext.setUsername(null); // 认证失败,清空用户名
      }
      break;
    case "QUIT":
      handleQuit(channelContext, sessionContext);
      break;
    default:
      Pop3SessionContext.sendErr(channelContext, "Unknown command or command not allowed.");
    }
  }

  private void handleTransactionState(String command, String[] parts, ChannelContext channelContext, Pop3SessionContext sessionContext) {
    String username = sessionContext.getUsername(); // 获取当前用户名
    switch (command) {
    case "STAT":
      int[] stat = mailboxService.getStat(sessionContext.getUsername());
      Pop3SessionContext.sendOk(channelContext, stat[0] + " " + stat[1]);
      break;
    case "TOP":
      if (parts.length < 2) {
        Pop3SessionContext.sendErr(channelContext, "Message number and number of lines required.");
        return;
      }
      String[] topArgs = parts[1].split("\\s+");
      if (topArgs.length < 2) {
        Pop3SessionContext.sendErr(channelContext, "Message number and number of lines required.");
        return;
      }

      try {
        int msgId = Integer.parseInt(topArgs[0]);
        int lines = Integer.parseInt(topArgs[1]);

        String content = mailboxService.getMessageContent(username, msgId);
        if (content != null) {
          Pop3SessionContext.sendOk(channelContext, "Top of message follows");

          // 简单的模拟实现:返回邮件头和正文的前几行
          String[] contentLines = content.split("\r\n");
          StringBuilder topResponse = new StringBuilder();

          boolean inBody = false;
          int bodyLinesCount = 0;

          for (String line : contentLines) {
            topResponse.append(line).append("\r\n");
            if (line.isEmpty()) { // 空行是邮件头和体的分隔符
              inBody = true;
            }
            if (inBody && !line.isEmpty()) {
              bodyLinesCount++;
            }
            if (inBody && bodyLinesCount >= lines) {
              break; // 正文行数已达到要求
            }
          }
          topResponse.append(".");

          Tio.send(channelContext, new Pop3Packet(topResponse.toString() + "\r\n"));
          log.info("POP3 >>>\n{}", topResponse.toString().trim());

        } else {
          Pop3SessionContext.sendErr(channelContext, "No such message.");
        }

      } catch (NumberFormatException e) {
        Pop3SessionContext.sendErr(channelContext, "Invalid arguments for TOP command.");
      }
      break;
    case "LIST":
      List<Integer> sizes = mailboxService.listMessages(username);
      Pop3SessionContext.sendOk(channelContext, sizes.size() + " messages");

      StringBuilder listResponse = new StringBuilder();
      for (int i = 0; i < sizes.size(); i++) {
        listResponse.append(i + 1).append(" ").append(sizes.get(i)).append("\r\n");
      }
      listResponse.append(".");

      Tio.send(channelContext, new Pop3Packet(listResponse.toString() + "\r\n"));
      log.info("POP3 >>>\n{}", listResponse.toString().trim());
      break;

    case "UIDL":
      List<String> uids = mailboxService.listUids(username);
      Pop3SessionContext.sendOk(channelContext, "Unique-ID listing follows");

      StringBuilder uidlResponse = new StringBuilder();
      for (int i = 0; i < uids.size(); i++) {
        // 序号 (i+1) 和 唯一ID
        uidlResponse.append(i + 1).append(" ").append(uids.get(i)).append("\r\n");
      }
      uidlResponse.append(".");

      Tio.send(channelContext, new Pop3Packet(uidlResponse.toString() + "\r\n"));
      log.info("POP3 >>>\n{}", uidlResponse.toString().trim());
      break;

    case "RETR":
      if (parts.length < 2) {
        Pop3SessionContext.sendErr(channelContext, "Message ID required.");
        return;
      }
      try {
        int msgId = Integer.parseInt(parts[1]);
        String content = mailboxService.getMessageContent(sessionContext.getUsername(), msgId);
        if (content != null) {
          Pop3SessionContext.sendOk(channelContext, "Message " + msgId + " follows");
          Pop3SessionContext.sendData(channelContext, content);
        } else {
          Pop3SessionContext.sendErr(channelContext, "No such message.");
        }
      } catch (NumberFormatException e) {
        Pop3SessionContext.sendErr(channelContext, "Invalid message ID.");
      }
      break;
    case "DELE":
      // TODO: 实现标记删除逻辑
      Pop3SessionContext.sendOk(channelContext, "Message marked for deletion.");
      break;
    case "NOOP":
      Pop3SessionContext.sendOk(channelContext, "");
      break;
    case "RSET":
      // TODO: 实现取消删除标记的逻辑
      Pop3SessionContext.sendOk(channelContext, "Deletion marks removed.");
      break;
    case "QUIT":
      handleQuit(channelContext, sessionContext);
      break;
    default:
      Pop3SessionContext.sendErr(channelContext, "Unknown command.");
    }
  }

  private void handleQuit(ChannelContext channelContext, Pop3SessionContext sessionContext) {
    sessionContext.setState(Pop3SessionContext.State.UPDATE);
    // TODO: 在这里执行真正的删除操作
    Pop3SessionContext.sendOk(channelContext, "tio-mail-wing POP3 server signing off.");
    Tio.close(channelContext, "Client requested QUIT.");
  }
}

Pop3ServerConfig

// src/main/java/com/tio/mail/wing/config/Pop3ServerConfig.java
package com.tio.mail.wing.config;

import java.io.IOException;

import com.litongjava.annotation.AConfiguration;
import com.litongjava.annotation.Initialization;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.tio.server.ServerTioConfig;
import com.litongjava.tio.server.TioServer;
import com.litongjava.tio.server.intf.ServerAioHandler;
import com.litongjava.tio.utils.environment.EnvUtils;
import com.tio.mail.wing.handler.Pop3ServerAioHandler;
import com.tio.mail.wing.listener.Pop3ServerAioListener;

import lombok.extern.slf4j.Slf4j;

@AConfiguration
@Slf4j
public class Pop3ServerConfig {

  @Initialization
  public void startPop3Server() {

    ServerAioHandler serverHandler = Aop.get(Pop3ServerAioHandler.class);
    Pop3ServerAioListener pop3ServerAioListener = Aop.get(Pop3ServerAioListener.class);

    // 配置对象
    ServerTioConfig serverTioConfig = new ServerTioConfig("pop3-server");
    serverTioConfig.setServerAioHandler(serverHandler);
    serverTioConfig.setServerAioListener(pop3ServerAioListener);
    serverTioConfig.checkAttacks = false;

    // 设置心跳,-1 取消心跳
    serverTioConfig.setHeartbeatTimeout(-1);

    // TioServer对象
    TioServer tioServer = new TioServer(serverTioConfig);

    // 启动服务
    try {
      int port = EnvUtils.getInt("mail.server.pop3.port", 110);
      tioServer.start(null, port);
      log.info("Started POP3 server on port: {}", port);
    } catch (IOException e) {
      log.error("Failed to start POP3 server", e);
    }
  }
}

测试

telnet

现在,你的 POP3 服务器已经具备了基础的协议交互能力。你可以使用 telnet 工具来手动测试它。

前提:

  1. 确保你的 tio-mail-wing 项目正在运行。
  2. 打开你的命令行工具(Windows 的 CMD/PowerShell,macOS/Linux 的 Terminal)。

测试步骤:

  1. 连接服务器 输入以下命令并回车。如果你的端口不是 110,请替换。

    telnet localhost 110
    

    如果连接成功,服务器会返回欢迎信息:

    +OK tio-mail-wing POP3 server ready.
    
  2. 发送用户名 输入 USER 命令。

    USER user1@tio.com
    

    服务器响应:

    +OK Password required for user1@tio.com
    
  3. 发送密码 输入 PASS 命令。

    PASS 00000000
    

    服务器响应,表示登录成功:

    +OK Mailbox open.
    
  4. 查看邮箱状态 输入 STAT 命令。

    STAT
    

    服务器返回邮件数量和总大小(来自我们模拟的 MailboxService):

    +OK 2 320
    
  5. 列出邮件列表 输入 LIST 命令。

    LIST
    

    服务器返回多行响应:

    +OK 2 messages
    1 120
    2 200
    .
    
  6. 读取第一封邮件 输入 RETR 1 命令。

    RETR 1
    

    服务器返回邮件的完整内容:

    +OK Message 1 follows
    From: sender@example.com
    To: user1@tio.com
    Subject: Test Mail 1
    
    This is the body of the first email.
    .
    
  7. 退出 输入 QUIT 命令。

    QUIT
    

    服务器响应并关闭连接:

    +OK tio-mail-wing POP3 server signing off.
    

    telnet 会话将在此处终止。

如果你能顺利完成以上所有步骤,并看到预期的响应,那么恭喜你,第一阶段的 POP3 服务核心协议实现已经成功完成!

下一步就是将 UserService 和 MailboxService 对接到真实的数据库或文件系统(如 Maildir 格式)。

curl

C:\Users\Administrator>curl -v --url "pop3://localhost:110" --user "user1@tio.com:00000000"
* Host localhost:110 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:110...
* Connected to localhost (::1) port 110
< +OK tio-mail-wing POP3 server ready.
> CAPA
< +OK Capability list follows
< TOP
< USER
< UIDL
< PIPELINING
< .
> USER user1@tio.com
< +OK Password required for user1@tio.com
> PASS pass1
< +OK Mailbox open.
> LIST
< +OK 2 messages
1 105
2 97
* Connection #0 to host localhost left intact

C:\Users\Administrator>curl -v --url "pop3://localhost:110/1" --user "user1@tio.com:00000000"
* Host localhost:110 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
*   Trying [::1]:110...
* Connected to localhost (::1) port 110
< +OK tio-mail-wing POP3 server ready.
> CAPA
< +OK Capability list follows
< TOP
< USER
< UIDL
< PIPELINING
< .
> USER user1@tio.com
< +OK Password required for user1@tio.com
> PASS pass1
< +OK Mailbox open.
> RETR 1
< +OK Message 1 follows
From: sender@example.com
To: user1@tio.com
Subject: Test Mail 1

This is the body of the first email.
* Connection #0 to host localhost left intact
Edit this page
Last Updated:
Contributors: Tong Li
Prev
tio-mail-wing简介
Next
使用 getmail 验证 tio-mail-wing POP3 服务