手把手教你写一个简单的SMTP服务器(三)
Contents
- 手把手教你写一个简单的SMTP服务器(一)
- 手把手教你写一个简单的SMTP服务器(二)
- 手把手教你写一个简单的SMTP服务器(三)
- 手把手教你写一个简单的SMTP服务器(四)
- 手把手教你写一个简单的SMTP服务器(五)
在教程二中我们已经完成了最重要的工作,构建了最基本的虚基类State
。回顾在教程二中我们对于状态的定义,我们拟定义如下6个类来表征状态机中的状态:
IdleState
EhloState
MailState
RcptState
DataStartState
DataDoneState
1 | // miniSMTPServer/context/state.hpp |
然后我们需要实现其方法,在现在这个阶段,我们当让其做空操作,或者返回默认值。
1 | // miniSMTPServer/context/state.cpp |
根据教程二中设计,我们可以知道当服务器端收到客户端的命令时,其会调用transitive
方法进行状态的转换,一个思路是我们可以使用make_unique
方法构造一个完整的新类来表述状态,但是这样不是很高效。为了提高效率,我们应该使用全局的生命周期的变量,因此我们可以构造一个新类States
包含指向6个已经存在的类的指针,我们使用unique_ptr
管理这些指针,从而避免内存的泄漏。
1 | // miniSMTPServer/context/state.hpp |
1 | // miniSMTPServer/context/state.cpp |
完成了这一步,我们就可以修改State
类的transitiveFromQuit
方法:
1 | // miniSMTPServer/context/state.cpp |
至此,我们完成了我们的状态机的雏形。同时你也可以通过执行命令git checkout all-states
获得上面的代码。
IdleState
当状态机一启动时,其应该位于IdleState
状态,其能够接收4个命令:RSET
,NOOP
,QUIT
以及EHLO
命令。其中NOOP
和QUIT
已经统一处理了,我们并不需要关心。在后面的状态中,我们会忽略这两个命令。对于RSET
命令来说,其在这个状态没有任何的作用(严格来说,不是没有作用,而是从IdleState
转化为IdleState
)。对于EHLO
命令来说,其应该从IdleState
状态转化为EhloState
。在完成这个工作之前,我们首先使用ABNF范式定义RSET
等命令的请求和响应,为了简单起见,本教程直接设置了一个静态的”127.0.0.1”。
1 | rset-request = "RSET" CRLF |
因此,我们首先更新State
基类中的isCorrectParameters
方法。其操作很简单,对于NOOP
,QUIT
和RSET
而言,这些命令都不需要任何的参数。而对于EHLO
命令,我们直接严格按照ABNF定义即可。
1 | std::optional<std::string> State::isCorrectParameters(std::vector<std::string> ¶meters) { |
你可能觉得这个功能很简单,但是就我个人而言,我认为此处我们应该写一点单元测试保证这个函数的正确性。显然,用cpp写单元测试也是相当的繁琐。首先,你需要在miniSMTPServer/context
目录下创建一个新的目录tests
。然后在tests
目录下添加如下的文件:
CMakeLists.txt
stateTest.cpp
对于位于miniSMTPServer/context/tests
目录下的CMakeLists.txt
,添加如下的代码:
1 | # miniSMTPServer/context/tests/CMakeLists.txt |
然后修改位于miniSMTPServer/context
目录下的CMakeLists.txt
文件,添加如下的代码:
1 | # miniSMTPServer/context/CMakeLists.txt |
然后我们就可以开始写单元测试了,我们目前的单元测试主要关心State::isCorrectParameters
方法。
1 | // miniSMTPServer/context/tests/stateTest.cpp |
上述代码提供的单元测试十分简单。不需要进行讲解,从后面开始,我们会以测试驱动来撰写代码,也就是我们先写测试文件再写相应的功能,当我们的代码能够通过测试后也就证明我们的代码是正确的。
现在我们要开始实现IdleState
类中的方法。首先我们应该思考IdleState
中允许存在什么命令,由上面的讲述可知,IdleState
允许的命令与State
中的allowed
一致,所以对其构造函数我们可以不做任何的处理,那么关键的地方就在于transitive
方法的实现。我们首先实现如下的单元测试的代码:
1 | // miniSMTPServer/context/tests/stateTest.cpp |
测试的代码很简单,你可以编译然后使用ctest --output-on-failure
可以发现很多错误,那么就让我们来更新IdleState::transitive
方法,其更新的很简单,首先使用基类的方法transitiveHelper
。然后只需要处理EHLO
命令即可,因为QUIT
和NOOP
命令已经在transitiveHelper
方法实现了,而RSET
方法对于idleState
没有任何作用。同时我也添加了许多其他测试。
1 | // miniSMTPServer/context/tests/stateTest.cpp |
再完成了测试代码的编写后,我们开始修改IdleState::transitive
方法:
1 | // miniSMTPServer/context/state.cpp |
然后我们编译代码运行测试:
1 | cd build && make -j12 && ctest --output-on-failure |
其结果如下:
1 | 80% tests passed, 1 tests failed out of 5 |
产生这样的结果并不是我们代码的问题,而是由于commands
变量的定义在教程二中没有完全定义,因此修改其定义如下:
1 | // miniSMTPServer/context/state.cpp |
然后我们重新编译代码运行测试:
1 | cd build && make -j12 && ctest --output-on-failure |
你会发现我们能够通过所有的测试。
小结
本节我们主要完善了我们的状态机并实现了第一个状态IdleState
的操作,并通过测试驱动的方式写出了我们的代码。在后续的教程中我们将继续完善其他的状态操作。如果你产生了任何的问题,你可以运行如下的命令:
1 | git checkout idle-state |