人生苦短,我用python。

一、并发服务器

1、单进程服务器

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
from socket import *

serSocket = socket(AF_INET, SOCK_STREAM)

# 重复使用绑定的信息
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

localAddr = ('', 7788)
serSocket.bind(localAddr)

serSocket.listen(5)

while True:
print('-----主进程,,等待新客户端的到来------')

newSocket, destAddr = serSocket.accept()

print('-----主进程,,接下来负责数据处理[%s]-----'%str(destAddr))

try:
while True:
recvData = newSocket.recv(1024)

if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr), recvData.decode()))
else:
print('[%s]客户端已经关闭'%str(destAddr))
break
finally:
newSocket.close()

serSocket.close()

同一时刻只能为一个客户进行服务,不能同时为多个客户服务

2、多进程服务器

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
from socket import *
from multiprocessing import Process
from time import sleep

# 处理客户端的请求并为其服务
def dealWithClient(newSocket, destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr),recvData.decode()))
else:
print('[%s]客户端已经关闭'%str(destAddr))
break
newSocket.close()

def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)

try:
while True:
print('-----主进程,,等待新客户端的到来------')
newSocket, destAddr = serSocket.accept()
print('-----主进程,,接下来创建一个新的进程负责数据处理[%s]-----'%str(destAddr))

client = Process(target=dealWithClient, args=(newSocket,destAddr))
client.start()
#因为已经向子进程中copy了一份(引用),并且父进程中这个套接字也没有用处了
#所以关闭
newSocket.close()
finally:
#当为所有的客户端服务完之后再进行关闭,表示不再接收新的客户端的链接
serSocket.close()

if __name__ == '__main__':
main()

通过为每个客户端创建一个进程的方式,能够同时为多个客户端进行服务。

当客户端不是特别多的时候,这种方式还行,如果有几百上千个,就不可取了,因为每次创建进程等过程需要好较大的资源。

3、多线程服务器

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
from socket import *
from threading import Thread
from time import sleep

# 处理客户端的请求并为其服务
def dealWithClient(newSocket, destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr),recvData.decode()))
else:
print('[%s]客户端已经关闭'%str(destAddr))
break
newSocket.close()

def main():
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
localAddr = ('', 7788)
serSocket.bind(localAddr)
serSocket.listen(5)

try:
while True:
print('-----主进程,,等待新客户端的到来------')
newSocket, destAddr = serSocket.accept()
print('-----主进程,,接下来创建一个新的线程负责数据处理[%s]-----'%str(destAddr))

client = Thread(target=dealWithClient, args=(newSocket,destAddr))
client.start()
#因为线程中共享这个套接字,如果关闭了会导致这个套接字不可用,
#但是此时在线程中这个套接字可能还在收数据,因此不能关闭
#newSocket.close()
finally:
#当为所有的客户端服务完之后再进行关闭,表示不再接收新的客户端的链接
serSocket.close()

if __name__ == '__main__':
main()

4、单进程服务器-非堵塞模式

单进程非堵塞也可以实现并发服务,用循环的方式不停的接收连接和用循环的方式不停的处理数据,连接越多效率越低。

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
#coding=utf-8

import time
from socket import *

def main():
#1.创建socket
serSocket = socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
#2. 绑定本地ip以及port
localAddr = ('', 7788)
serSocket.bind(localAddr)
#3. 让这个socket 变为非堵塞
serSocket.setblocking(False)
#4. 将socket变为监听(被动)套接字
serSocket.listen(1000)
# 用来保存所有已经连接的客户端的信息
g_socketList = []

while True:
try:
#等待一个新的客户端的到来(即完成3次握手的客户端)
clientSocket, clientAddr = serSocket.accept()
#设置为非堵塞后,如果accept时,恰巧没有客户端connect,那么accept会产生一个异常,所以需要try来进行处理
except Exception as result:
# 这里不处理非阻塞异常,保证循环正常进行
pass
else:
print("一个新的客户端到来:%s"%str(clientAddr))
#子连接也设置为非阻塞
clientSocket.setblocking(False)
g_socketList.append((clientSocket, clientAddr))

#用来存储需要删除的客户端信息
g_needDelClientInfoList = []
for clientSocket,clientAddr in g_socketList:
try:
recvData = clientSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(clientAddr), recvData))
#如果客户端发来的消息为空,则证明该连接已中断
else:
print('[%s]客户端已经关闭'%str(clientAddr))
clientSocket.close()
g_needDelClientInfoList.append((clientSocket,clientAddr))
except Exception as result:
#同理,子连接的非阻塞异常也不处理,保证循环进行
pass

#删除中断的连接
for needDelClientInfo in g_needDelClientInfoList:
g_needDelClientInfoList.remove(needDelClientInfo)

if __name__ == '__main__':
main()

5、单进程服务器-select版

网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。

这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是依次进行判断的。

select优缺点:

优点:

  • select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。

缺点:

  • select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
  • 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
  • 对socket进行扫描时是依次扫描的,即采用轮询的方法,效率较低。
  • 当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。

(1)select 回显服务器

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
import select
import socket
import sys


server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('', 7788))
server.listen(5)

inputs = [server, sys.stdin]

running = True

while True:

# 调用 select 函数,阻塞等待
readable, writeable, exceptional = select.select(inputs, [], [])

# 数据抵达,循环
for sock in readable:

# 监听到有新的连接
if sock == server:
conn, addr = server.accept()
# select 监听的socket
inputs.append(conn)

# 监听到键盘有输入
elif sock == sys.stdin:
cmd = sys.stdin.readline()
running = False
break

# 有数据到达
else:
# 读取客户端连接发送的数据
data = sock.recv(1024)
if data:
sock.send(data)
else:
# 移除select监听的socket
inputs.remove(sock)
sock.close()

# 如果检测到用户输入敲击键盘,那么就退出
if not running:
break

server.close()

(2)包含writeList的服务器

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
66
67
68
69
70
71
72
73
74
75
76
77
#coding=utf-8
import socket
import Queue
from select import select

SERVER_IP = ('', 9999)

# 保存客户端发送过来的消息,将消息放入队列中
message_queue = {}
input_list = []
output_list = []

if __name__ == "__main__":
server = socket.socket()
server.bind(SERVER_IP)
server.listen(10)
# 设置为非阻塞
server.setblocking(False)

# 初始化将服务端加入监听列表
input_list.append(server)

while True:
# 开始 select 监听,对input_list中的服务端server进行监听
stdinput, stdoutput, stderr = select(input_list, output_list, input_list)

# 循环判断是否有客户端连接进来,当有客户端连接进来时select将触发
for obj in stdinput:
# 判断当前触发的是不是服务端对象, 当触发的对象是服务端对象时,说明有新客户端连接进来了
if obj == server:
# 接收客户端的连接, 获取客户端对象和客户端地址信息
conn, addr = server.accept()
print("Client %s connected! "%str(addr))
# 将客户端对象也加入到监听的列表中, 当客户端发送消息时 select 将触发
input_list.append(conn)
# 为连接的客户端单独创建一个消息队列,用来保存客户端发送的消息
message_queue[conn] = Queue.Queue()

else:
# 由于客户端连接进来时服务端接收客户端连接请求,将客户端加入到了监听列表中(input_list),客户端发送消息将触发
# 所以判断是否是客户端对象触发
try:
recv_data = obj.recv(1024)
# 客户端未断开
if recv_data:
print("received %s from client %s"%(recv_data, str(addr)))
# 将收到的消息放入到各客户端的消息队列中
message_queue[obj].put(recv_data)

# 将回复操作放到output列表中,让select监听
if obj not in output_list:
output_list.append(obj)

except ConnectionResetError:
# 客户端断开连接了,将客户端的监听从input列表中移除
input_list.remove(obj)
# 移除客户端对象的消息队列
del message_queue[obj]
print("\n[input] Client %s disconnected"%str(addr))

# 如果现在没有客户端请求,也没有客户端发送消息时,开始对发送消息列表进行处理,是否需要发送消息
for sendobj in output_list:
try:
# 如果消息队列中有消息,从消息队列中获取要发送的消息
if not message_queue[sendobj].empty():
# 从该客户端对象的消息队列中获取要发送的消息
send_data = message_queue[sendobj].get()
sendobj.send(send_data)
else:
# 将监听移除等待下一次客户端发送消息
output_list.remove(sendobj)

except ConnectionResetError:
# 客户端连接断开了
del message_queue[sendobj]
output_list.remove(sendobj)
print("\n[output] Client %s disconnected"%str(addr))

6、单进程服务器-epoll版

epoll的优点:

  • 没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024
  • 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。

说明:

  • EPOLLIN (可读)
  • EPOLLOUT (可写)
  • EPOLLET (ET模式)

epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

  • LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
  • ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
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
66
67
68
import socket
import select

# 创建套接字
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 设置可以重复使用绑定的信息
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

# 绑定本机信息
s.bind(("",7788))

# 变为被动
s.listen(10)

# 创建一个epoll对象
epoll=select.epoll()

# 测试,用来打印套接字对应的文件描述符
# print(s.fileno())
# print(select.EPOLLIN|select.EPOLLET)

# 注册事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已经注册过,则会发生异常
# 将创建的套接字添加到epoll的事件监听中
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)

connections = {}
addresses = {}

# 循环等待客户端的到来或者对方发送数据
while True:

# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
epoll_list=epoll.poll()

# 对事件进行判断
for fd,events in epoll_list:
#print(fd)
#print(events)

# 如果是socket创建的套接字被激活
if fd == s.fileno():
conn,addr=s.accept()

print('有新的客户端到来%s'%str(addr))

# 将 conn 和 addr 信息分别保存起来
connections[conn.fileno()] = conn
addresses[conn.fileno()] = addr

# 向 epoll 中注册 连接 socket 的 可读 事件
epoll.register(conn.fileno(), select.EPOLLIN|select.EPOLLET)
elif events == select.EPOLLIN:
# 从激活 fd 上接收
recvData = connections[fd].recv(1024)

if len(recvData)>0:
print('recv:%s'%recvData)
else:
# 从 epoll 中移除该 连接 fd
epoll.unregister(fd)

# server 侧主动关闭该 连接 fd
connections[fd].close()

print("%s---offline---"%str(addresses[fd]))

7、单进程服务器-gevent版

协程方式实现并发服务器。

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
import sys
import time
import gevent
from gevent import socket,monkey

#有IO操作时需要这一句
monkey.patch_all()

def handle_request(conn):
while True:
data = conn.recv(1024)
if not data:
conn.close()
break
print('recv:', data)
conn.send(data)

def server(port):
s = socket.socket()
s.bind(('', port))
s.listen(5)
while True:
cli,addr = s.accept()
gevent.spawn(handle_request, cli)

if __name__ == '__main__':
server(7788)

二、web服务器

1、静态服务器-显示固定页面

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
# coding=utf-8

import socket
from multiprocessing import Process

def handleClient(clientSocket):
'用一个新的进程,为一个客户端进行服务'
recvData = clientSocket.recv(2014)
requestHeaderLines = recvData.splitlines()
for line in requestHeaderLines:
print(line.decode())

responseHeaderLines = 'HTTP/1.1 200 ok\r\n'
responseHeaderLines += '\r\n'
responseBody = 'hello world'

response = responseHeaderLines + responseBody
clientSocket.send(response.encode())
clientSocket.close()

def main():
'作为程序的主控制入口'
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind(('', 7788))
serverSocket.listen(10)

while True:
clientSocket, clientAddr = serverSocket.accept()
clientP = Process(target=handleClient, args=(clientSocket,))
clientP.start()
clientSocket.close()

if __name__ == '__main__':
main()

在浏览器上输入地址‘localhost:7788’进行验证。

2、静态服务器-显示需要页面

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
# coding=utf-8

import re
import socket
from multiprocessing import Process

def handleClient(clientSocket):
'用一个新的进程,为一个客户端进行服务'
recvData = clientSocket.recv(2014)
requestHeaderLines = recvData.splitlines()
for line in requestHeaderLines:
print(line.decode())

httpRequestMethodLine = requestHeaderLines[0]
getFileName = re.match("[^/]+(/[^ ]*)", httpRequestMethodLine.decode()).group(1)
print("file name is ===>%s"%getFileName) # for test

if getFileName == '/':
getFileName = documentRoot + '/index.html'
else:
getFileName = documentRoot + getFileName
print("file name is ===2>%s"%getFileName) #for test

try:
f = open(getFileName)
except IOError:
responseHeaderLines = "HTTP/1.1 404 not found\r\n"
responseHeaderLines += "\r\n"
responseBody = "====sorry ,file not found===="
else:
responseHeaderLines = "HTTP/1.1 200 OK\r\n"
responseHeaderLines += "\r\n"
responseBody = f.read()
f.close()
finally:
response = responseHeaderLines + responseBody
clientSocket.send(response.encode())
clientSocket.close()

def main():
'作为程序的主控制入口'
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
serverSocket.bind(('', 7788))
serverSocket.listen(10)

while True:
clientSocket, clientAddr = serverSocket.accept()
clientP = Process(target=handleClient, args=(clientSocket,))
clientP.start()
clientSocket.close()

#这里配置服务器
documentRoot = './html'

if __name__ == '__main__':
main()

在项目的./html路径下放置index.html文件,在浏览器上输入地址‘localhost:7788’进行验证。

3、静态服务器-使用类

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# coding=utf-8

import re
import sys
import socket
from multiprocessing import Process

class WSGIServer(object):
addressFamily = socket.AF_INET
socketType = socket.SOCK_STREAM
requestQueueSize = 5

def __init__(self, server_address):
#创建一个tcp套接字
self.listenSocket = socket.socket(self.addressFamily, self.socketType)
#允许重复使用上次的套接字绑定的port
self.listenSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
#绑定
self.listenSocket.bind(server_address)
#变为被动,并制定队列的长度
self.listenSocket.listen(self.requestQueueSize)

def serverForever(self):
'循环运行web服务器,等待客户端的链接并为客户端服务'
while True:
#等待新客户端到来
self.clientSocket, client_address = self.listenSocket.accept()
#多进程服务器,并发服务器于多个客户端
newClientProcess = Process(target=self.handleRequest)
newClientProcess.start()
#因为创建的新进程中,会对这个套接字+1,所以需要在主进程中减去依次,即调用一次close
self.clientSocket.close()

def handleRequest(self):
'用一个新的进程,为一个客户端进行服务'
recvData = self.clientSocket.recv(2014)
requestHeaderLines = recvData.splitlines()
for line in requestHeaderLines:
print(line.decode())

httpRequestMethodLine = requestHeaderLines[0]
getFileName = re.match("[^/]+(/[^ ]*)", httpRequestMethodLine.decode()).group(1)
print("file name is ===>%s"%getFileName) # for test

if getFileName == '/':
getFileName = documentRoot + '/index.html'
else:
getFileName = documentRoot + getFileName
print("file name is ===2>%s"%getFileName) #for test

try:
f = open(getFileName)
except IOError:
responseHeaderLines = "HTTP/1.1 404 not found\r\n"
responseHeaderLines += "\r\n"
responseBody = "====sorry ,file not found===="
else:
responseHeaderLines = "HTTP/1.1 200 OK\r\n"
responseHeaderLines += "\r\n"
responseBody = f.read()
f.close()
finally:
response = responseHeaderLines + responseBody
self.clientSocket.send(response.encode())
self.clientSocket.close()

#设定服务器的端口
serverAddr = (HOST, PORT) = '', 7788
#设置服务器服务静态资源时的路径
documentRoot = './html'

def makeServer(serverAddr):
server = WSGIServer(serverAddr)
return server

def main():
httpd = makeServer(serverAddr)
print('web Server: Serving HTTP on port %d ...\n'%PORT)
httpd.serverForever()

if __name__ == '__main__':
main()

在项目的./html路径下放置index.html文件,在浏览器上输入地址‘localhost:7788’进行验证。

4、动态服务器资源请求

(1)浏览器请求动态页面过程

浏览器请求动态页面过程

(2)WSGI

怎么在你刚建立的Web服务器上运行一个Django应用和Flask应用,如何不做任何改变而适应不同的web架构呢?

在以前,选择 Python web 架构会受制于可用的web服务器,反之亦然。如果架构和服务器可以协同工作,那就好了。

但有可能面对(或者曾有过)一个的问题,当要把一个服务器和一个架构结合起来时,却发现他们不是被设计成协同工作的。

那么,怎么可以不修改服务器和架构代码而确保可以在多个架构下运行web服务器呢?答案就是 Python Web Server Gateway Interface (或简称 WSGI,读作“wizgy”)。

WSGI允许开发者将选择web框架和web服务器分开。可以混合匹配web服务器和web框架,选择一个适合的配对。比如,可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, 或 Pyramid。真正的混合匹配,得益于WSGI同时支持服务器和架构:

WSGI同时支持服务器和架构

web服务器必须具备WSGI接口,所有的现代Python Web框架都已具备WSGI接口,它让你不对代码作修改就能使服务器和特点的web框架协同工作。

WSGI由web服务器支持,而web框架允许你选择适合自己的配对,但它同样对于服务器和框架开发者提供便利使他们可以专注于自己偏爱的领域和专长而不至于相互牵制。其他语言也有类似接口:java有Servlet API,Ruby 有 Rack。

(3)定义WSGI接口

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello World!”:

1
2
3
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return 'Hello World!'

application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

  • environ:一个包含所有HTTP请求信息的dict对象;
  • start_response:一个发送HTTP响应的函数。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,把底层web服务器解析部分和应用程序逻辑部分进行了分离,这样开发者就可以专心做一个领域了

不过,等等,这个application()函数怎么调用?如果我们自己调用,两个参数environ和start_response我们没法提供,返回的str也没法发给浏览器。

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器。而我们此时的web服务器项目的目的就是做一个极可能解析静态网页还可以解析动态网页的服务器

5、动态服务器-基本实现

WSGI规范中application函数的数据传递过程:

WSGI规范中application函数的数据传递过程

(1)web server 代码:

server.py

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# coding:utf-8

import socket
import re
import sys

from multiprocessing import Process

# 设置静态文件根目录
HTML_ROOT_DIR = "./html"
WSGI_PYTHON_DIR = "./wsgipython"


class HTTPServer(object):
""""""
def __init__(self):
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

def start(self):
self.server_socket.listen(128)
while True:
client_socket, client_address = self.server_socket.accept()
# print("[%s, %s]用户连接上了" % (client_address[0],client_address[1]))
print("[%s, %s]用户连接上了" % client_address)
handle_client_process = Process(target=self.handle_client, args=(client_socket,))
handle_client_process.start()
client_socket.close()

def start_response(self, status, headers):
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header
self.response_headers = response_headers

def handle_client(self, client_socket):
"""处理客户端请求"""
# 获取客户端请求数据
request_data = client_socket.recv(1024)
print("request data:", request_data)
request_lines = request_data.splitlines()
for line in request_lines:
print(line)

# 解析请求报文
# 'GET / HTTP/1.1'
request_start_line = request_lines[0]
# 提取用户请求的文件名
print("*" * 10)
print(request_start_line.decode("utf-8"))
file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
method = re.match(r"(\w+) +/[^ ]* ", request_start_line.decode("utf-8")).group(1)

# "/ctime.py"
# "/sayhello.py"
if file_name.endswith(".py"):
try:
m = __import__(file_name[1:-3])
except Exception:
self.response_headers = "HTTP/1.1 404 Not Found\r\n"
response_body = "not found"
else:
env = {
"PATH_INFO": file_name,
"METHOD": method
}
response_body = m.application(env, self.start_response)

response = self.response_headers + "\r\n" + response_body
else:
if "/" == file_name:
file_name = "/index.html"

# 打开文件,读取内容
try:
file = open(HTML_ROOT_DIR + file_name, "rb")
except IOError:
response_start_line = "HTTP/1.1 404 Not Found\r\n"
response_headers = "Server: My server\r\n"
response_body = "The file is not found!"
else:
file_data = file.read()
file.close()

# 构造响应数据
response_start_line = "HTTP/1.1 200 OK\r\n"
response_headers = "Server: My server\r\n"
response_body = file_data.decode("utf-8")

response = response_start_line + response_headers + "\r\n" + response_body
print("response data:", response)

# 向客户端返回响应数据
client_socket.send(bytes(response, "utf-8"))

# 关闭客户端连接
client_socket.close()

def bind(self, port):
self.server_socket.bind(("", port))


def main():
sys.path.insert(1, WSGI_PYTHON_DIR)
http_server = HTTPServer()
# http_server.set_port
http_server.bind(7788)
http_server.start()


if __name__ == "__main__":
main()

(2)架构部分代码

  • ./html 路径下添加静态文件index.html
  • ./wsgipython 路径下添加动态文件 ctime.py 和 sayhello.py

./wsgipython/ctime.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# coding:utf-8

import time

# "/ctime.py?timezone=e8"
# "/ctime.py?timezone=e1"

def application(env, start_response):
# env.get("Method")
# env.get("PATH_INFO")
# env.get("QUERY_STRING")

status = "200 OK"

headers = [
("Content-Type", "text/plain")
]

start_response(status, headers)

return time.ctime()

./wsgipython/sayhello.py

1
2
3
4
5
def application(env, start_response):
status = "404 Not Found"
headers = []
start_response(status, headers)
return "file not found"

浏览器输入地址‘localhost:7788’ 或 ‘localhost:7788/index.html’验证访问静态页面。
浏览器输入地址‘localhost:7788/ctime.py’ 或 ‘localhost:7788/sayhello.py’验证访问动态页面。

6、动态服务器-框架编写

MyWebServer.py

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# coding:utf-8

import socket
import re
import sys
from multiprocessing import Process
from MyWebFramework import Application

# 设置静态文件根目录
HTML_ROOT_DIR = "./html"

WSGI_PYTHON_DIR = "./wsgipython"


class HTTPServer(object):
""""""
def __init__(self, application):
"""构造函数, application指的是框架的app"""
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.app = application

def start(self):
self.server_socket.listen(128)
while True:
client_socket, client_address = self.server_socket.accept()
# print("[%s, %s]用户连接上了" % (client_address[0],client_address[1]))
print("[%s, %s]用户连接上了" % client_address)
handle_client_process = Process(target=self.handle_client, args=(client_socket,))
handle_client_process.start()
client_socket.close()

def start_response(self, status, headers):
response_headers = "HTTP/1.1 " + status + "\r\n"
for header in headers:
response_headers += "%s: %s\r\n" % header
self.response_headers = response_headers

def handle_client(self, client_socket):
"""处理客户端请求"""
# 获取客户端请求数据
request_data = client_socket.recv(1024)
print("request data:", request_data)
request_lines = request_data.splitlines()
for line in request_lines:
print(line)

# 解析请求报文
# 'GET / HTTP/1.1'
request_start_line = request_lines[0]
# 提取用户请求的文件名
print("*" * 10)
print(request_start_line.decode("utf-8"))
file_name = re.match(r"\w+ +(/[^ ]*) ", request_start_line.decode("utf-8")).group(1)
method = re.match(r"(\w+) +/[^ ]* ", request_start_line.decode("utf-8")).group(1)

env = {
"PATH_INFO": file_name,
"METHOD": method
}
response_body = self.app(env, self.start_response)

response = self.response_headers + "\r\n" + response_body

# 向客户端返回响应数据
client_socket.send(bytes(response, "utf-8"))

# 关闭客户端连接
client_socket.close()

def bind(self, port):
self.server_socket.bind(("", port))


def main():
sys.path.insert(1, WSGI_PYTHON_DIR)
if len(sys.argv) < 2:
sys.exit("python MyWebServer.py Module:app")
# python MyWebServer.py MyWebFrameWork:app
module_name, app_name = sys.argv[1].split(":") # sys.args可以获得命令行传入的参数
# module_name = "MyWebFrameWork"
# app_name = "app"
m = __import__(module_name)
app = getattr(m, app_name) # 可以获取模块属性
http_server = HTTPServer(app)
# http_server.set_port
http_server.bind(7788)
http_server.start()


if __name__ == "__main__":
main()

MyWebFramework.py

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# coding:utf-8

import time

# from MyWebServer import HTTPServer


# 设置静态文件根目录
HTML_ROOT_DIR = "./html"


class Application(object):
"""框架的核心部分,也就是框架的主题程序,框架是通用的"""
def __init__(self, urls):
# 设置路由信息
self.urls = urls

def __call__(self, env, start_response):
path = env.get("PATH_INFO", "/")
# /static/index.html
if path.startswith("/static"):
# 要访问静态文件
file_name = path[7:]
# 打开文件,读取内容
try:
file = open(HTML_ROOT_DIR + file_name, "rb")
except IOError:
# 代表未找到路由信息,404错误
status = "404 Not Found"
headers = []
start_response(status, headers)
return "not found"
else:
file_data = file.read()
file.close()

status = "200 OK"
headers = []
start_response(status, headers)
return file_data.decode("utf-8")

for url, handler in self.urls:
#("/ctime", show_ctime)
if path == url:
return handler(env, start_response)

# 代表未找到路由信息,404错误
status = "404 Not Found"
headers = []
start_response(status, headers)
return "not found"


def show_ctime(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return time.ctime()


def say_hello(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return "hello world"

def say_haha(env, start_response):
status = "200 OK"
headers = [
("Content-Type", "text/plain")
]
start_response(status, headers)
return "haha"

urls = [
("/", show_ctime),
("/ctime", show_ctime),
("/sayhello", say_hello),
("/sayhaha", say_haha),
]
app = Application(urls)


# if __name__ == "__main__":
# urls = [
# ("/", show_ctime),
# ("/ctime", show_ctime),
# ("/sayhello", say_hello),
# ("/sayhaha", say_haha),
# ]
# app = Application(urls)
# http_server = HTTPServer(app)
# http_server.bind(8000)
# http_server.start()

在命令行中输入命令 python MyWebServer.py MyWebFrameWork:app,启动 MyWebServer 服务来调用 MyWebFrameWork 框架中的 app。
在浏览器中输入地址 ‘localhost:7788/ctime’ 或 ‘localhost:7788/sayhello’来验证。

持续更新…

最后更新: 2018年12月03日 15:57

原始链接: http://pythonfood.github.io/2017/12/31/服务器/

× 多少都行~
打赏二维码