如何用python构建邮件服务器
其实掌握一个类似框架的高级工具是有用的,但是基础的东西可以让你永远不被淘汰,不要被工具限制。
今天我们不用框架或者Python标准库中的高级包,只用标准库中的socket接口来写一个Python服务器。
框架和底层
在当今这个Python服务器框架(如Django、Twisted、web.py等)盛行的时代。)泛滥,从底层套接字开始写服务器似乎是一种吃力不讨好的愚蠢做法。
框架的意义在于掩盖底层细节,提供一套对开发者更友好的API,处理MVC等布局问题。
该框架允许我们快速构建一个成熟的Python服务器。但是,框架本身也依赖于底层(比如socket)。了解底层套接字不仅可以帮助我们更好地使用框架,还可以让我们了解框架是如何设计的。
此外,如果您对底层套接字编程和其他系统编程有很好的了解,您可以设计和开发自己的框架。
如果你能从底层socket开始,实现一个完整的Python服务器,支持用户层的协议,处理MVC(模型-视图-控制)和多线程等问题,整理出一套清晰的函数或类作为接口(API)呈现给用户,你就相当于设计了一个框架。
套接字接口实际上是操作系统提供的系统调用。
socket的使用不仅限于Python语言。你可以用C或者Java写同一个socket服务器,所有语言使用socket的方式都差不多(Apache是用C实现的服务器)。
但是你不能跨语言使用框架。
框架的好处是帮助你处理一些细节,从而实现快速开发,但同时也受到Python自身性能的限制。
我们看到很多成功的网站都是用动态语言(比如Python、Ruby或者PHP,比如twitter、facebook)快速开发出来的。网站成功后,将代码转换成C、JAVA等一些高效的语言,让服务器更高效的面对每天上亿的请求。
在这种情况下,底层的重要性远远超出了框架。
TCP/IP和套接字简介
回到我们的任务。
我们需要了解一些网络传输的知识,尤其是TCP/IP协议和socket。
Socket是进程间通信的一种方法,是基于网络传输协议的上层接口。
套接字的类型很多,比如基于TCP协议或者UDP协议(两种网络传输协议),其中TCP套接字是最常用的。
TCP套接字有点类似于双工管道。一个进程向套接字的一端写入或读取文本流,而另一个进程可以从套接字的另一端读取或写入。特别是,这两个建立套接字通信的进程可以属于两台不同的计算机。
TCP协议提供了一些通信规则,使得上述进程间的通信过程可以在网络环境下有效实现。
双工管道存在于同一台计算机中,不需要区分两个进程所在计算机的地址,套接字必须包含地址信息才能实现网络通信。
一个套接字包含四个地址信息:两台计算机的IP地址和两个进程使用的端口。IP地址用于定位计算机,而端口用于定位进程(多个进程可以在一台计算机上使用不同的端口)。
TCP套接字
在互联网上,让一台电脑充当服务器。
服务器打开自己的端口,被动等待其他计算机连接。
当其他计算机作为客户主动使用socket连接服务器时,服务器就开始为客户提供服务。
在Python中,我们使用标准库中的套接字包进行底层套接字编程。
首先是服务器端,我们用bind()方法给socket一个固定的地址和端口,用listen()方法被动监听端口。
当客户尝试使用connect()方法连接时,服务器使用accept()来接受连接,从而建立一个连接的套接字:
Socket.Socket()创建一个socket对象,解释socket使用IPv4(AF_INET,IP version 4)和TCP协议(SOCK_STREAM)。
然后使用另一台计算机作为客户,我们主动使用connect()方法搜索服务器的IP地址(在Linux中,可以使用$ifconfig查询自己的IP地址)和端口,让客户找到服务器并建立连接:
在上面的例子中,我们可以调用recv()方法在套接字两端接收信息,调用sendall()方法发送信息。
这样,我们可以在两台计算机上的两个进程之间进行通信。
当通信结束时,我们使用close()方法关闭套接字连接。
(如果没有两台电脑做实验,也可以把客户端IP要连接的IP改成“127.0.0.1”,这是一个连接本地主机的专用IP地址。)
基于TCP套接字的HTTP服务器
在上面的例子中,我们已经可以使用TCP套接字在两台远程计算机之间建立连接。
但是socket传输的自由度太高,带来了很多安全性和兼容性问题。
我们经常使用一些应用层协议(比如HTTP协议)来规定socket的使用规则和传输信息的格式。
HTTP协议以请求-响应的方式使用TCP套接字。
客户端向服务器发送一段文本作为请求,服务器收到请求后向客户端发送一段文本作为响应。
在完成这样一个请求-响应事务后,TCP socket就被放弃了。
下一个请求将创建一个新的套接字。
请求和响应本质上是两种文本,但是HTTP协议对两种文本都有一定的格式要求。
请求& lt——& gt;反应
现在,让我们编写一个HTTP服务器:
HTTP服务器程序说明
正如我们在上面看到的,服务器将根据请求向客户发送两个消息中的一个,text_content和pic_content,作为响应文本。
整个响应分为三个部分:起始行、头部信息和正文。起跑线是第一行:
实际上是用空格分成三段,HTTP/1.x表示使用的HTTP版本,200表示状态码,200是HTTP协议中规定的,表示服务器正常接收和处理请求,OK是状态码,供人读取。
标题信息跟在起始行之后,在它和正文之间有一个空行。
这里的text_content或者pic_content只有一行头信息,用来表示主要信息的text_content类型是html文本:
pic _ Content(Content-Type:image/jpg)的头信息表示主体的类型为jpg图片(image/jpg)。
主要信息是html或jpg文件的内容。
(注意,对于jpg文件,为了与windows兼容,我们以“rb”模式打开。因为在windows下,jpg被认为是二进制文件,在UNIX系统下,不需要区分文本文件和二进制文件。)
我们没有写客户端程序,后面会用浏览器做客户端。
客户端程序将请求发送到服务器。
虽然请求可以像响应一样分为三个部分,但是请求的格式与响应的格式并不相同。
请求由客户端发送到服务器。例如,下面是一个请求:
起始行可以分为三部分,第一部分是请求方法,第二部分是URL,第三部分是HTTP版本。
请求方法可以有GET、PUT、POST、DELETE和HEAD。最常用的是GET和POST。
GET是请求服务器发送资源给客户,POST是请求服务器接收客户发送的数据。
当我们打开一个网页时,我们通常使用GET方法;当我们填写表格并提交时,我们通常使用POST方法。
第二部分是URL,通常指向一个资源(服务器上的资源或者其他地方的资源)。和现在一样,它是指向当前服务器的当前目录的test.jpg。
根据HTTP协议的规定,服务器需要根据请求执行某些操作。
在服务器程序中我们可以看到,我们的Python程序首先检查请求的方法,然后根据不同的URL生成不同的响应(text_content _ content或者pic_content)。
随后,该响应被发送回客户端。
体验浏览器
为了配合上面的服务器程序,我在放置Python程序的文件夹中保存了一个test.jpg图片文件。
我们在终端上运行Python程序作为服务器,然后打开一个浏览器作为客户端。
(有时间的话也可以用Python写个客户端。原理类似于上面的TCP socket的客户端程序。)
在浏览器的地址栏中,输入:
(当然也可以用电脑,输入服务器的IP地址。)
好了,我已经有了一个用Python实现的、用socket编写的服务器。
从终端上,我们可以看到浏览器实际上发出了两个请求。
第一个请求是(关键信息在起始行,这个请求的主体是空的):
根据这个请求,我们的Python程序将text_content的内容发送到服务器。
在浏览器接收到text_content之后,它发现
在分析了初始行之后,我们的Python程序发现/test.jpg满足if条件,因此它将pic_content发送给客户。
最后,浏览器根据html语言的语法以适当的方式显示html文本和图片。
探索的方向
1)在我们上面的服务器程序中,我们使用了一个while循环来保持服务器工作。
其实我们也可以根据多线程的知识把while循环中的内容改成多进程或者多线程工作。
2)我们的服务器程序还不完善,可以让我们的Python程序调用Python的其他函数来实现更复杂的功能。例如,制作一个时间服务器,让服务器向客户返回日期和时间。也可以用Python自己的数据库实现一个完整的LAMP服务器。
3) socket包是比较低级的包。Python标准库中也有高级包,比如SocketServer、SimpleHTTPServer、cgiHTTPServer、CGI。所有这些软件包都在帮助我们更容易地使用socket。如果你已经了解socket,那么这些包就很好理解了。有了这些高级包,你就可以编写一个相当成熟的服务器了。
4)历经千辛万苦,你可能会发现框架是如此的方便,于是你决定使用它。或者,你已经有了参与框架开发的热情。