Python网络
原始Python服务器
不使用框架,甚至不使用Python标准库中的高级包,只使用标准库中的socket接口,写一个Python服务器。
socket接口是实际上是操作系统提供的系统调用。socket的使用并不局限于Python语言,你可以用C或者JAVA来写出同样的socket服务器,而所有语言使用socket的方式都类似(Apache就是使用C实现的服务器)。
TCP/IP和socket
socket是进程间通信的一种方法,它是基于网络传输协议的上层接口。socket有许多种类型,比如基于TCP协议或者UDP协议(两种网络传输协议)。其中又以TCP socket最为常用。
TCP socket与双向管道(duplex PIPE)有些类似,一个进程向socket的一端写入或读取文本流,而另一个进程可以从socket的另一端读取或写入,比较特别是,这两个建立socket通信的进程可以分别属于两台不同的计算机。
所谓的TCP协议,就是规定了一些通信的守则,以便在网络环境下能够有效实现上述进程间通信过程。双向管道(duplex PIPE)存活于同一台电脑中,所以不必区分两个进程的所在计算机的地址,而socket必须包含有地址信息,以便实现网络通信。
一个socket包含四个地址信息: 两台计算机的IP地址和两个进程所使用的端口(port)。IP地址用于定位计算机,而port用于定位进程 (一台计算机上可以有多个进程分别使用不同的端口)。
TCP socket
在互联网上,我们可以让某台计算机作为服务器。服务器开放自己的端口,被动等待其他计算机连接。当其他计算机作为客户,主动使用socket连接到服务器的时候,服务器就开始为客户提供服务。
在Python中,我们使用标准库中的socket包来进行底层的socket编程。
首先是服务器端,我们使用**bind()方法来赋予socket以固定的地址和端口,并使用listen()方法来被动的监听该端口。当有客户尝试用connect()方法连接的时候,服务器使用accept()**接受连接,从而建立一个连接的socket
服务端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
import socket
HOST = '' PORT = 8085 reply = 'Yes'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST,PORT)) s.listen(3) while True: conn,addr = s.accept() request = conn.recv(1024) print('connected by: ', addr) print('request is: ', request) conn.sendall(reply) conn.close()
|
客户端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
import socket HOST = '192.168.253.134' PORT = 8085 request = 'Can you hear me?'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST,PORT)) s.sendall(request) reply = s.recv(1024) print('reply is: ',reply) s.close()
|
我们对socket的两端都可以调用**recv()方法来接收信息,调用sendall()方法来发送信息。这样,我们就可以在分处于两台计算机的两个进程间进行通信了。当通信结束的时候,我们使用close()**方法来关闭socket连接。

基于TCP socket的HTTP服务器
socket传输自由度太高,从而带来很多安全和兼容的问题。我们往往利用一些应用层的协议(比如HTTP协议)来规定socket 使用规则,以及所传输信息的格式。
HTTP协议利用**请求-回应(request-response)**的方式来使用TCP socket。客户端向服务器发一段文本作为request,服务器端在接收到request之后,向客户端发送一段文本作为response。在完成了这样一次request-response交易之后,TCP socket被废弃。下次的request将建立新的socket。request和response本质上说是两个文本,只是HTTP协议对这两个文本都有一定的格式要求。
如下为一个HTTP服务器案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
|
import socket
HOST = '' PORT = 8086
text_content = ''' HTTP/1.x 200 OK Content-Type: text/html
<head> <title>NI9NE</title> </head> <html> <p> This is NI9NE's Python Http Server</p> <img src='ni9ne.png'/> </html> '''
f = open('ni9ne.png','rb')
pic_content = ''' HTTP/1.x 200 OK Content-Type: image/png
''' pic_content = pic_content + f.read() f.close()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST,PORT))
while True: s.listen(3) conn,addr = s.accept() request = conn.recv(1024) request_info = request.split(' ') method = request_info[0] src = request_info[1] if method == 'GET': if src == '/ni9ne.png': content = pic_content else: content = text_content print('Connected by: ', addr) print('Request is: ', request) conn.sendall(content) conn.close()
|

socket包是比较底层的包。Python标准库中还有高层的包,比如SocketServer,SimpleHTTPServer,CGIHTTPServer,cgi。
Python服务器进化
支持POST
即从POST请求中提取数据,再显示在屏幕上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
import socket
HOST = '' PORT = 8087
text_content = ''' HTTP/1.x 200 OK Content-Type: text/html
<head> <title>NI9NE</title> </head> <html> <p> This is NI9NE's Python Http Server</p> <img src='ni9ne.png'/> <form name="input" action="/" method="post"> First name:<input type="text" name="firstname"><br> <input type="submit" value="Submit"> </form> </html> '''
f = open('ni9ne.png','rb') pic_content = ''' HTTP/1.x 200 OK Content-Type: image/jpg
''' pic_content = pic_content + f.read()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT))
while True: s.listen(3) conn, addr = s.accept() request = conn.recv(1024)
method = request.split(' ')[0] src = request.split(' ')[1]
print 'Connected by', addr print 'Request is:', request
if method == 'GET':
if src == '/ni9ne.png': content = pic_content else: content = text_content
conn.sendall(content)
if method == 'POST': form = request.split('\r\n') print form idx = form.index('') print idx entry = form[idx:]
value = entry[-1].split('=')[-1] conn.sendall(text_content + '\n <p>post value is: ' + value + '</p>') conn.close()
|

post包 request.split(' ') 结果
1 2
| ['POST', '/', 'HTTP/1.1\r\nHost:', '192.168.253.134:8087\r\nConnection:', 'keep-alive\r\nContent-Length:', '14\r\nPragma:', 'no-cache\r\nCache-Control:', 'no-cache\r\nUpgrade-Insecure-Requests:', '1\r\nOrigin:', 'http://192.168.253.134:8087\r\nContent-Type:', 'application/x-www-form-urlencoded\r\nUser-Agent:', 'Mozilla/5.0', '(Windows', 'NT', '10.0;', 'Win64;', 'x64)', 'AppleWebKit/537.36', '(KHTML,', 'like', 'Gecko)', 'Chrome/98.0.4758.82', 'Safari/537.36\r\nAccept:', 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nReferer:', 'http://192.168.253.134:8087/\r\nAccept-Encoding:', 'gzip,', 'deflate\r\nAccept-Language:', 'zh-CN,zh;q=0.9\r\n\r\nfirstname=1232']
|
使用SocketServer
基于TCP协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
|
import SocketServer as socketserver
HOST = '' PORT = 8086
text_content = ''' HTTP/1.x 200 OK Content-Type: text/html
<head> <title>NI9NE</title> </head> <html> <p> This is NI9NE's Python Http Server</p> <img src='ni9ne.png'/> <form name="input" action="/" method="post"> first name: <input type="text" name="firstname"><br/> <input type="submit" value="Submit"> </form> </html> '''
f = open('ni9ne.png','rb') pic_content = ''' HTTP/1.x 200 OK Content-Type: image/png
''' pic_content = pic_content + f.read() f.close()
class MyTCPHandle(socketserver.BaseRequestHandler): def handle(self): request = self.request.recv(1024)
print('Connected by', self.client_address[0]) print('Request is:', request)
method = request.split(' ')[0] src = request.split(' ')[1]
if method == 'GET': if src == '/ni9ne.png': content = pic_content else: content = text_content self.request.sendall(content)
if method == 'POST': form = request.split('\r\n') idx = form.index('') entry = form[idx:]
value = entry[-1].split('=')[-1] self.request.sendall(text_content + '\n <p>post value is: ' + value + '</p>')
server = socketserver.TCPServer((HOST,PORT),MyTCPHandle)
server.serve_forever()
|

SimpleHTTPServer: 使用静态文件来回应请求
HTTP协议基于TCP协议,但增加了更多的规范。这些规范,虽然限制了TCP协议的功能,但大大提高了信息封装和提取的方便程度。
对于一个HTTP请求(request)来说,它包含有两个重要信息:请求方法和URL。
| 请求方法(request method) |
URL |
操作 |
| GET |
/ |
发送text_content |
| GET |
/ni9ne.png |
发送pic_content |
| POST |
/ |
分析request主体中包含的value(实际上是我们填入表格的内容); 发送text_content和value |
可以使用SimpleHTTPServer包和CGIHTTPServer包来规定针对不同请求的操作。
其中,SimpleHTTPServer可以用于处理GET方法和HEAD方法的请求。它读取request中的URL地址,找到对应的静态文件,分析文件类型,用HTTP协议将文件发送给客户。

index.html
1 2 3 4 5 6 7 8 9 10 11
| <head> <title>NI9NE</title> </head> <html> <p> This is NI9NE's Python Http Server</p> <img src='ni9ne.png'/> <form name="input" action="/" method="post"> first name: <input type="text" name="firstname"><br/> <input type="submit" value="Submit"> </form> </html>
|
socket_simplehttp.py
1 2 3 4 5 6 7 8 9 10 11 12
|
import SocketServer as socketserver import SimpleHTTPServer
HOST = '' PORT = 8086
server = socketserver.TCPServer((HOST,PORT), SimpleHTTPServer.SimpleHTTPRequestHandler)
server.serve_forever()
|

这里的程序不能处理POST请求。可以使用使用CGI来弥补这个缺陷。

CGIHTTPServer:使用静态文件或者CGI来回应请求
1 2 3
| 文档: https://docs.python.org/zh-cn/3/library/http.server.html#http.server.CGIHTTPRequestHandler https://docs.python.org/zh-cn/3/library/http.server.html#http.server.SimpleHTTPRequestHandler
|
CGIHTTPServer包中的CGIHTTPRequestHandler类继承自SimpleHTTPRequestHandler类,所以可以用来代替上面的例子,来提供静态文件的服务。此外,CGIHTTPRequestHandler类还可以用来运行CGI脚本。
CGI (Common Gateway Interface)。CGI是服务器和应用脚本之间的一套接口标准。它的功能是让服务器程序运行脚本程序,将程序的输出作为response发送给客户。总体的效果,是允许服务器动态的生成回复内容,而不必局限于静态文件。
支持CGI的服务器程接收到客户的请求,根据请求中的URL,运行对应的脚本文件。服务器会将HTTP请求的信息和socket信息传递给脚本文件,并等待脚本的输出。脚本的输出封装成合法的HTTP回复,发送给客户。
服务器和CGI脚本之间的通信要符合CGI标准。CGI的实现方式有很多,比如说使用Apache服务器与Perl写的CGI脚本,或者Python服务器与shell写的CGI脚本。
为了使用CGI,需要使用BaseHTTPServer包中的HTTPServer类来构建服务器。

index.html
CGIHTTPRequestHandler默认当前目录下的cgi-bin和ht-bin文件夹中的文件为CGI脚本,而存放于其他地方的文件被认为是静态文件。因此,我们需要修改一下index.html,将其中form元素指向的action改为cgi-bin/post.py。
1 2 3 4 5 6 7 8 9 10 11
| <head> <title>NI9NE</title> </head> <html> <p> This is NI9NE's Python Http Server</p> <img src='ni9ne.png'/> <form name="input" action="cgi-bin/post.py" method="post"> first name: <input type="text" name="firstname"><br/> <input type="submit" value="Submit"> </form> </html>
|
CGIHTTPServer.py
1 2 3 4 5 6 7 8 9 10 11
|
import BaseHTTPServer import CGIHTTPServer
HOST = '' PORT = 8086
server = BaseHTTPServer.HTTPServer((HOST,PORT), CGIHTTPServer.CGIHTTPRequestHandler) server.serve_forever()
|
post.py
1 2 3 4 5 6 7 8 9 10
|
import cgi form = cgi.FieldStorage()
print("Content-Type: text/html") print('') print("<p>Hello world!</p>") print("<p>" + repr(form['firstname']) + "</p>")
|