05年的时候写了一个邮箱客户端程序。当时主要目的是研究POP3和SMTP协议,同时锻炼自己的网络编程能力。当然了,如果自己写的邮箱客户端能够满足自身的日常工作需要,而不是频繁的登录不同的网页邮箱,那就再好不过了。时隔16年,给popmail增加了SSL(TLS 1.2)会话,感觉安全了一点,邮件再也不用裸奔了,看到16年前的代码,非常感慨,随便写写,特此纪念。
POP3和SMTP这两个协议本身都很简单,但实现起来还是有很大难度,尤其是你希望把它写的健壮、易用或者产品化的时候。
比如HTTP协议也很简单,写个简单的socket程序通过GET命令就能把网页给down下来。但接收大的网络资源就复杂多了。何时解析、如何解析完整的HTTP响应头,就是个头疼问题。因为你不能指望一次recv就能接收完所有响应数据,也不能指望服务器先发送HTTP响应头,然后再发送响应数据。只有把HTTP响应头彻底解析了,我们才能知道后续接收的Body数据有多大,何时才能接收完毕。
比如通过响应头的"Content-Length"字段,才能知道后续Body的大小。这个大小可能超过了你之前开辟的接收数据缓存区大小。当然你可以在得知Body大小后,重新开辟一个与"Content-Length"一样大小的缓存区。但这样做显然是不明智的,比如你get的是一部4K高清蓝光小电影,蓝光电影不一定能get到,蓝屏电脑倒有可能get到。。。。。。
遇到服务器明确给出"Content-Length"字段,是一件值得额手称庆的大喜事,但不是每个IT民工都这么幸运。如果遇到的是不靠谱的服务器,发送的是"Transfer-Encoding: chunked",那你就必须锻炼自己真正的解析和组织能力了。这些分块传输的数据,显然不会以你接收的节奏到达你的缓冲区,比如先接收到一个block块大小,然后是一个完整的块数据,很有可能你会接收到多个块或者不完整的块,这就需要你站在宏观的角度把他们拼接起来。
如果你遇到的是甩的一米的服务器,它不仅给你的是chunked,而且还增加了"Content-Encoding: gzip",那么你就需要拼接后进行解压,当然你也可能遇到的是"deflate"压缩。
题外话:我一直困惑的是HTTP协议为何不是对分块数据单独gzip压缩然后传输,而只能是整体gzip压缩后再分块传输。这个对大资源传输很关键,比如上面的4K高清蓝光小电影,显然不能通过gzip+chunked方式传输,土豪服务器例外。
当然你也可以用开源的llhttp来解析收到的http数据,从而避免上述可能会遇到的各种坑。最新版本的nodejs中就使用llhttp代替之前的的http-parser,据说解析效率有大幅提升。为此我下载了nodejs源码,并编译了一把,这是一个快乐的过程,因为你可以看到v8引擎,openssl,zlib等各种开源库。。。。,不过llhttp只负责解析,不负责缓存,因此你还是需要在解析的过程中,进行数据缓存。这是我写的通过llhttp解析http响应数据的案例:
基于SSL(TLS)的HTTPS网页下载——如何编写健壮的可靠的网页下载
这里面有我封装好的sslite.dll库可以方便的帮助你进行SSL客户端通信,目前支持TLS1.2,我的popmail因为使用sslite库才把衣服穿上避免了裸奔。
花开两朵各表一枝,还是回到POP3/SMTP上来。
相较而言,实现POP3要比实现SMTP复杂,这个复杂不是指协议本身有多复杂,也不是POP3比SMTP多了几个命令,而是指用代码实现协议的难度,尤其是解析难度。POP3是接收协议,SMTP是发送协议,总体而言发送比接收要简单很多。
因为发送数据的时候你可以耍流氓,不管服务器的死活,可以变态的1个字节1个字节发数据,也可以忽长忽短的发数据,从而让服务器猜不透你,直到把数据全部发送完毕。但接收数据就复杂多了,比如上面提到的如何接收HTTP响应数据,现在需要你来面对猜不透的服务器了,因为服务器也可能耍流氓。
早期的email协议只支持ASCII码这种纯文本传输,后来随着富文本的出现,图像、文件也迫切需要通过email进行传输。这时MIME协议诞生了,MIME的出现更多的是一种向下兼容的无奈,而不是革命。通过对二进制数据或非ASCII码数据进行base64或quoted-printable编码,来实现纯ASCII码的传输。显然这种方式会让你的邮件体变大,传输效率下降。
传输下降只是一方面,解析MIME格式的邮件也是一件头疼的事,对于"Content-Type: multipart/mixed"来说,需要根据boundary值,将不同mixed part邮件体给解析出来。同时你要面临各种编码格式,否则你的邮件标题、附件名称就会出现乱码,比如这种:"=?gb2312?B?=",比如这种:"=?gb2312?Q?",又比如这种"=?unicode-1-1-utf-7?q?"。。。。,当时还还遇到过"Content-type: message/rfc822",或者"content-disposition : inline"字段,完全处于懵B状态。05年的时候,网上资料特别少,也不认识张小龙,张小龙不仅是微信之父,更是Foxmail之母。如果当时能联系上,可能会发微信咨询一下,或者qq加好友哦。。。。。。,但在当年只能连蒙带猜,遇到一次乱码就猜测着解析看看,敢于在踩雷中解析,在解析中踩雷。
当年写邮箱客户端使用的是经典的VC6.0,也许很多90后、00后、10后没有听说过它,没关系,没关系,下面就是用VC6编写的popmail,不高调,不奢华,一眼就能看出vc6的影子。它支持多邮箱账户、邮件自动接收、快速查看邮件列表、远程删除邮件,自动回复等人民群总喜闻乐见却基本不用的功能。
1、主界面
2、多邮箱账户配置
3、账户配置
4、自动规则设定
5、接收邮件
6、快速查看
为什么不能把界面写的多次多彩呢?VC6写的程序就是这样的,古朴大方。。。。。