一、socket的http套路
web应用本质上是一个socket服务端,用户的浏览器是一个socket客户端。socket处在应用层与传输层之间,是操作系统中I/O系统的延伸部分(接口),负责系统进程和应用之间的通信。
HTTP协议又称超文本传输协议。
![Django(一):从socket到MVC第1张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第2张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 //浏览器发送一个HTTP请求; 2 //服务器收到请求,根据请求信息,进行函数处理,生成一个HTML文档; 3 //服务器把HTML文档作为HTTP响应的Body发送给浏览器; 4 //浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示;
1、客户端套路解析
![Django(一):从socket到MVC第3张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第4张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket, ssl 2 #socket 是操作系统用来进行网络通信的底层方案,用来发送/接收数据 3 4 s =socket.socket(socket.AF_INET, socket.SOCK_STREAM) 5 #socket.AF_INET表示是ipv4协议,socket.SOCK_STREAM表示是tcp协议,这两个值是默认的。 6 #s = socket.socket() 7 #上面只能连接http,如果连接https,需要用s = ssl.wrap_socket(socket.socket()) 8 9 host, port = ("g.cn", 80) 10 s.connect((host, port)) #连接主机,参数是主机的和端口 11 12 ip, port =s.getsockname() 13 print('本机 ip 和 port {} {}'.format(ip, port)) #查看本机的ip和端口 14 15 #构造一个http请求 16 http_request = 'GET / HTTP/1.1 host:{} '.format(host) 17 18 #发送HTTP请求给服务器 19 #send 函数只接收bytes作为参数,所以要重编码为utf-8。实际上,web数据传送都是utf-8编码的字节流数据。 20 21 request = http_request.encode(encoding='utf_8', errors='strict') 22 print("发送请求", request) 23 s.send(request) 24 25 26 #接收服务器的相应数据 27 response = s.recv(1024) #buffer_size=1024,只接收1024个字节,多余的数据就不接收了。粘包。 28 29 #输出响应的数据,bytes类型 30 #print("响应", response) 31 #再讲response的utf-8编码的字节流数据进行解码(实际上是转换成unicode编码的字符串,因为读进了内存)。 32 print("响应的 str 格式: ", end=" ") 33 print(response.decode(encoding='utf-8'))
2、服务器套路解析
![Django(一):从socket到MVC第5张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第6张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket 2 3 host, port = '', 2000 4 #服务器的host为空字符串,表示接受任意ip地址的连接 5 6 s =socket.socket() 7 s.bind((host, port)) #监听 8 9 #用一个无线循环来接受数据 10 whileTrue: 11 print("before listen") 12 s.listen(5) 13 14 #接收请求 15 connection, address =s.accept() 16 #connection是一个socket.socket对象 17 print("after listen") 18 19 #接收请求 20 request = connection.recv(1024) #只接收1024个字节 21 print("ip and request, {} {}".format(address, request.decode("utf-8"))) 22 23 #构造响应 24 response = b"HTTP/1.1 200 OK <h1>Hello World!</h1>" 25 26 #用sendall发送响应数据 27 connection.sendall(response) 28 29 #关闭连接 30 connection.close() 31
在浏览器中输入localhost:2000,看到Hello World。后台打印的结果如下:
![Django(一):从socket到MVC第7张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第8张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 before listen 2 after listen 3 ip and request, ('127.0.0.1', 54275) 4 GET / HTTP/1.1 5 Host: localhost:2000 6 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 Firefox/59.0 7 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 9 Accept-Encoding: gzip, deflate 10 Cookie: username-localhost-8888="2|1:0|10:1524886551|23:username-localhost-8888|44:ZTc2ZjE3MjIxMTMwNDIxYzg3OTZmMDlkMDdhNzhjMjI=|b432650e1e450be30083dd567068aebd47dc8d5b7167b068610af60db4c5a35d"; _xsrf=2|fc2cd1bd|88c10f771944fea069e65d5e767b6621|1524882104 11 Connection: keep-alive 12 Upgrade-Insecure-Requests: 1
粘包处理
![Django(一):从socket到MVC第9张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第10张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 粘包处理: 2 3 buffer_size = 1023 4 r = b'' 5 whileTrue: 6 request =connection.recv(buffer_size) 7 r +=request 8 if len(request) <buffer_size: 9 break
3、HTTP请求内容解析
![Django(一):从socket到MVC第11张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第12张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 """ 2 HTTP头 3 http://localhost:2000/,浏览器默认会隐藏http://和末尾的/ 4 5 GET / HTTP/1.1 6 # GET表示请求方式, / 表示请求的资源路径, HTTP/1.1 协议版本 7 8 Host: localhost:2000 9 # 主机地址和端口 10 11 Connection: keep-alive 12 # keep-alive保持连接状态,它表示http连接(不用TCP请求断开再请求);close每次离开就关闭连接 13 14 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:59.0) Gecko/20100101 Firefox/59.0 15 # 用户代理,浏览器标识。可以伪造浏览器。 16 17 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 18 # 浏览器接收的数据类型。左边是数据类型,右边是解析时的权重(优先级)。 19 20 Accept-Encoding: gzip, deflate 21 # 可解压的压缩文件类型;只表示能解压该类型的压缩文件,不代表拒绝接收其它类型的压缩文件。 22 23 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 24 # 支持的语言类型及其解析权重。 25 26 Cookie: username-localhost-8888="2|1:0|10:1524886551|23:username-localhost-8888|44:ZTc2ZjE3MjIxMTMwNDIxYzg3OTZmMDlkMDdhNzhjMjI=|b432650e1e450be30083dd567068aebd47dc8d5b7167b068610af60db4c5a35d"; _xsrf=2|fc2cd1bd|88c10f771944fea069e65d5e767b6621|1524882104 27 # 浏览器缓存。 28 29 /favicon.ico 30 # url地址图标,非必须。 31 32 # Header里可以添加任意的内容。 33 """
二、socket的udp和tcp套路
1、udp的客户端和服务端写法
客户端
![Django(一):从socket到MVC第13张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第14张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket 2 #upd链接 3 #SOCK_DGRAM:数据报套接字,主要用于UDP协议 4 udpSocket =socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 5 6 #关闭防火墙 7 #同一网段(局域网)下,主机的ip地址和端口号. 8 sendAddr = ('192.168.10.247', 8080) 9 10 #绑定端口:写的是自己的ip和固定的端口,一般是写在sever端. 11 udpSocket.bind(('', 9900)) 12 13 #sendData = bytes(input('请输入要发送的数据:'), 'gbk') 14 #gbk, utf8, str 15 sendData = input('请输入要发送的数据:').encode('gbk') 16 17 #使用udp发送数据,每一次发送都需要写上接收方的ip地址和端口号 18 udpSocket.sendto(sendData, sendAddr) 19 #udpSocket.sendto(b'hahahaha', ('192.168.10.247', 8080)) 20 21 udpSocket.close()
服务端
![Django(一):从socket到MVC第15张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第16张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket 2 3 udpSocket =socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 4 5 #接收方一般需要绑定端口 6 #''表示自己电脑的任何一个ip,即无线和有限同时连接或者电脑有不同的网卡(桥接),会有多个ip. 7 #绑定自己的端口 8 bindAddr = ('', 7788) 9 udpSocket.bind(bindAddr) 10 11 recvData = udpSocket.recvfrom(1024) 12 #print(recvData) 13 print(recvData[0].decode('gbk')) 14 15 udpSocket.close() 16 #recvData的格式:(data, ('ip', 端口)).它是一个元组,前面是数据,后面是一个包含ip和端口的元组.
2、tcp的客户端和服务端写法
客户端
![Django(一):从socket到MVC第17张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第18张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket 2 3 tcpClient =socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 5 serverAddr = ('192.168.10.247', 8899) 6 7 #tcp的三次握手,写进了这一句话 8 tcpClient.connect(serverAddr) 9 10 sendData = input('') 11 12 #直接用send就行了,udp是用sendto 13 tcpClient.send(sendData.encode('gbk')) 14 15 recvData = tcpClient.recv(1024) 16 17 print('接收到的数据为:%s' % recvData.decode('gbk')) 18 19 tcpClient.close() 20 21 #为什么用send而不是sendto?因为tcp连接是事先链接好了,后面就直接发就行了。前面的connect已经连接好了,后面直接用send发送即可。 22 #而udp必须用sendto,是发一次数据,连接一次。必须要指定对方的ip和port。 23 #相同的道理,在tcpServer端,要写recv,而不是recvfrom来接收数据
服务端
![Django(一):从socket到MVC第19张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第20张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket 2 tcpServer =socket.socket(socket.AF_INET, socket.SOCK_STREAM) 3 tcpServer.bind(('', 8899)) 4 tcpServer.listen(5) 5 6 #tcp的三次握手,写进了这一句话当中 7 tcpClient, tcpClientInfo =tcpServer.accept() 8 #tcpServer.accept(),不需要写ip,可以接收多个客户端的。但事先要绑定端口和接入的客户端的数量 9 #client 表示接入的新的客户端 10 #clientInfo 表示接入的新的客户端的ip和端口port 11 12 recvData = tcpClient.recv(1024) 13 print('%s: %s' % (str(tcpClientInfo), recvData.decode('gbk'))) 14 15 #tcp的四次握手,写进了这一句话 16 tcpClient.close() 17 tcpServer.close() 18 19 #tcpServer.accept():等待客户端的接入,自带堵塞功能:即必须接入客户端,然后往下执行 20 #tcpClient.recv(1024): 也是堵塞,不输入数据就一直等待,不往下执行. 21 #tcpServer创建了两个套接字,一个是Server,另一个是tcpClient.Server负责监听接入的Client,再为其创建专门的tcpClient进行通信.
3、服务端开启循环和多线程模式
开启循环
![Django(一):从socket到MVC第21张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第22张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket 2 3 Server =socket.socket(socket.AF_INET, socket.SOCK_STREAM) 4 5 Server.bind(('', 9000)) 6 Server.listen(10) 7 8 whileTrue: 9 #如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务 10 serverThisClient, ClientInfo =Server.accept() 11 print('Waiting connect......') 12 13 #如果客户发送的数据是空的,那么断开连接 14 whileTrue: 15 recvData = serverThisClient.recv(1024) 16 if len(recvData) > 1: 17 18 print('recv: %s' % recvData.decode('gbk')) 19 20 sendData = input('send: ') 21 serverThisClient.send(sendData.encode('gbk')) 22 else: 23 print('再见!') 24 break 25 serverThisClient.close()
多线程写法
![Django(一):从socket到MVC第23张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第24张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 from threading importThread 2 importsocket 3 #收数据,然后打印 4 defrecvData(): 5 whileTrue: 6 recvInfo = udpSocket.recvfrom(1024) 7 print('%s:%s' % (str(recvInfo[1]), recvInfo[0].decode('gbk'))) 8 9 #检测键盘,发数据 10 defsendData(): 11 whileTrue: 12 sendInfo = input('') 13 udpSocket.sendto(sendInfo.encode('gbk'), (destIp, destPort)) 14 15 udpSocket =None 16 destIp = '' 17 destPort =0 18 #多线程 19 defmain(): 20 21 globaludpSocket 22 globaldestIp 23 globaldestPort 24 25 destIp = input('对方的ip: ') 26 destPort = int(input('对方的端口:')) 27 28 udpSocket =socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 29 udpSocket.bind(('', 45678)) 30 31 tr = Thread(target=recvData) 32 ts = Thread(target=sendData) 33 34 tr.start() 35 ts.start() 36 37 tr.join() 38 ts.join() 39 if __name__ == '__main__': 40 main()
三、socket 获取 html
目标:获取https://movie.douban.com/top250整个网页。
过程:
1.构造GET请求头,包括: https协议处理,movie.douban.com为host,port默认为443,/top250为请求的url。
2.粘包接收字节流并进行解析,返回状态码、响应头、响应体。
实例代码:
![Django(一):从socket到MVC第25张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第26张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket, ssl 2 3 4 """ 5 https 请求的默认端口是 443,https 的 socket 连接需要 import ssl,并且使用 s = ssl.wrap_socket(socket.socket()) 来初始化 6 7 HTTP 协议的 301 状态会在 HTTP 头的 Location 部分告诉你应该转向的 URL 8 如果遇到 301, 就请求新地址并且返回 9 HTTP/1.1 301 Moved Permanently 10 ... 11 Location: https://movie.douban.com/top250 12 """ 13 14 15 defparse_url(url): 16 protocol = 'http' 17 18 if url[:7] == 'http://': 19 u = url.split("://")[1] 20 elif url[:8] == "https://": 21 protocol = "https" 22 u = url.split("://")[1] 23 else: 24 u =url 25 26 #https://g.cn:1234/hello/world 27 #这里host就是g.cn:1234 28 #这里path就是/hello/world 29 30 #检查host和path 31 i = u.find('/') 32 if i == -1: 33 host =u 34 path = '/' 35 else: 36 host =u[:i] 37 path =u[i:] 38 39 #检查端口 40 port_dict =dict( 41 http=80, 42 https=443, 43 ) 44 #默认端口 45 port =port_dict[protocol] 46 if ":" inhost: 47 h = host.split(":") 48 host = h[0] #到这里获取ip 49 port = int(h[1]) #获取端口 50 returnprotocol, host, port, path 51 52 53 defsocket_by_protocol(protocol): 54 """根据协议返回一个socket实例""" 55 if protocol == 'http': 56 s =socket.socket() 57 else: 58 #https协议要用到ssl 59 s =ssl.wrap_socket(socket.socket()) 60 returns 61 62 defresponse_by_socket(s): 63 """s是一个socket实例,返回这个socket读取的所有数据""" 64 response = b'' 65 buffer_size = 1024 66 whileTrue: 67 r =s.recv(buffer_size) 68 if len(r) ==0: 69 break 70 response +=r 71 returnresponse 72 73 defparsed_response(r): 74 """ 75 把response解析出 状态码 headers body 返回 76 状态码是 int 77 headers是 dict 78 body是 str 79 """ 80 header, body = r.split(' ', 1) #split 1 只分割1次 81 h = header.split(' ') 82 83 #HTTP/1.1 200 OK 84 status_code = h[0].split()[1] #空格切分,取中间的状态码 85 status_code=int(status_code) 86 87 headers ={} 88 for line in h[1: ]: 89 k, v = line.split(": ") 90 headers[k] =v 91 returnstatus_code, headers, body 92 93 defget(url): 94 """用get请求url并返回响应""" 95 protocol, host, port, path =parse_url(url) 96 print(protocol, host, port, path) 97 #根据protocol确定socket方式 98 s =socket_by_protocol(protocol) 99 s.connect((host, port)) 100 101 #构造请求,注意不要用keep-alive,因为服务器不会主动关闭连接 102 request = 'GET {} HTTP/1.1 host: {} connection: close '.format(path, host) 103 104 #发送请求 105 s.send(request.encode(encoding='utf_8', errors='strict')) 106 107 #获取响应 108 response = response_by_socket(s) #接收所有的数据 109 r = response.decode('utf-8') 110 111 status_code, headers, body = parsed_response(r) #解析状态码,请求头和请求体 112 113 if status_code in [301, 302]: #301和302是重定向,要递归进行寻址 114 url = headers["Location"] 115 returnget(url) 116 returnstatus_code, headers, body 117 118 defmain(): 119 url = 'https://movie.douban.com/top250' 120 status_code, headers, body =get(url) 121 print(status_code, headers, body) 122 123 if __name__ == '__main__': 124 main() 125
缺陷:这里没有对html文档进行处理,包括html文档中所需内容(node节点)的解析和保存。可以使用bs4进一步处理。
注意:request请求必须要确保无误,否则会很容易出错。
四、socket写web服务
这里的内容极其重要。一切web框架的本质是socket。Django和Flask的框架都是在下面最简功能上进行拆分解耦建立起来的。
![Django(一):从socket到MVC第27张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第28张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 importsocket, functools 2 from docutils.parsers.rst.directives importencoding 3 4 #包裹print,禁止它的一些额外的功能 5 def log(*args, **kwargs): 6 print("LOG: ", *args, **kwargs) 7 #禁止函数的默认返回, func = lambda x: x, print = func(print)。这个在flask的werkzeug.local中用了几十次。 8 9 10 defroute_index(): 11 """主页的处理函数, 返回响应""" 12 header = 'HTTP/1.x 200 OK Content-Type: text/html ' 13 body = '<h1>Hello World.</h1><img src="http://t.zoukankan.com/dog.gif"/>' 14 r = header + ' ' +body 15 return r.encode(encoding='utf-8') 16 17 defroute_image(): 18 """返回一个图片""" 19 with open('dog.gif', mode='rb') as f: 20 header = b'HTTP/1.x 200 OK Content-Type: image/gif ' 21 img = header +f.read() 22 returnimg 23 24 defpage(html): 25 with open(html, encoding="utf-8") as f: 26 returnf.read() 27 28 29 defroute_msg(): 30 """返回一个html文件""" 31 header = 'HTTP/1.x 200 OK Content-Type: text/html ' 32 body = page("html_basic.html") 33 r = header + ' ' +body 34 return r.encode(encoding='utf-8') 35 36 37 def error(code=404): 38 e ={ 39 404: b'HTTP/1.x 404 NOT FOUND <h1>Page Not Found</h1>', 40 } 41 return e.get(code, b'') 42 43 44 defresponse_for_path(path): 45 """根据path调用相应的处理函数,没有处理的path会返回404""" 46 r ={ 47 '/': route_index, 48 '/dog.gif': route_image, 49 '/msg': route_msg, 50 } 51 response = r.get(path, error) #注意,这里用dict的get方法设置了不存在时的默认值 52 returnresponse() 53 54 def run(host="", port=3000): 55 """启动服务器""" 56 with socket.socket() as s: 57 #使用with可以保证程序终端的时候正确关闭socket,释放占用的端口 58 s.bind((host, port)) 59 60 whileTrue: 61 s.listen(5) 62 63 connection, address =s.accept() 64 request = connection.recv(1024) 65 request = request.decode('utf-8') 66 log('ip and request, {} {}'.format(address, request)) 67 68 try: 69 path = request.split()[1] 70 response = response_for_path(path) #用response_for_path函数来根据不同的path,生成不同的响应内容 71 connection.sendall(response) 72 73 exceptException as e: 74 log("error ", e) 75 76 connection.close() 77 78 79 defmain(): 80 config =dict( 81 host='', 82 port=4000, 83 ) 84 run(**config) 85 86 if __name__ == '__main__': 87 main()
用到的图片直接放在当前.py同一目录下即可。html也是同级目录,内容如下:
![Django(一):从socket到MVC第29张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第30张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 <!DOCTYPE html> 2 <!-- 注释是这样的, 不会被显示出来 --> 3 <!-- 4 html 格式是浏览器使用的标准网页格式 5 简而言之就是 标签套标签 6 --> 7 <!-- html 中是所有的内容 --> 8 <html> 9 <!-- head 中是放一些控制信息, 不会被显示 --> 10 <head> 11 <!-- meta charset 指定了页面编码, 否则中文会乱码 --> 12 <meta charset="utf-8"> 13 <!-- title 是浏览器显示的页面标题 --> 14 <title>例子 1</title> 15 </head> 16 <!-- body 中是浏览器要显示的内容 --> 17 <body> 18 <!-- html 中的空格是会被转义的, 所以显示的和写的是不一样的 --> 19 <!-- 代码写了很多空格, 显示的时候就只有一个 --> 20 很 好普通版 21 <h1>很好 h1 版</h1> 22 <h2>很好 h2 版</h2> 23 <h3>很好 h3 版</h3> 24 <!-- form 是用来给服务器传递数据的 tag --> 25 <!-- action 属性是 path --> 26 <!-- method 属性是 HTTP方法 一般是 get 或者 post --> 27 <!-- get post 的区别上课会讲 --> 28 <form action="/" method="get"> 29 <!-- textarea 是一个文本域 --> 30 <!-- name rows cols 都是属性 --> 31 <textarea name="message" rows="8" cols="40"></textarea> 32 <!-- button type=submit 才可以提交表单 --> 33 <button type="submit">GET 提交</button> 34 </form> 35 <form action="/" method="post"> 36 <textarea name="message" rows="8" cols="40"></textarea> 37 <button type="submit">POST 提交</button> 38 </form> 39 </body> 40 </html>
运行上述py程序,访问localhost:4000/msg,在get和post输入框里分别输入内容并点击发送。可以看到,get请求的参数包含在请求头里,post请求的参数包含在请求体里。这需要分别处理并获取参数。
五、从socket到web框架
有了上述内容,整个url请求的处理流程如下图所示。
创建python package。创建以下几个py文件。(https://github.com/ZJingyu/socket_learning)
- web
- static: 静态文件
- templates: html文件
- server: 主要负责监听socket连接和接收请求,并对请求进行解析,传递给不同的处理函数。
- routes: url对应的处理函数。
- models: 数据交互的处理。
当然,对于models和routes都是可以再拆分。如:
- web
- static
- templates
- db
- views.py: 将routes改名为views.py,删掉route_dict。
- urls.py: 把route_dict放进来,从views.py中导入所有视图函数。
- models:新建名为models的python package,创建Message.py和User.py,将原models.py中的三个类分开,Model放进__init__.py里。
__init__.py: Model类
- Message.py: Message类
- User.py: User类
![Django(一):从socket到MVC第32张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第33张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #__init__.py 2 3 importjson 4 5 defsave(data, path): 6 """把一个dict或者list写入文件""" 7 8 #indent是缩进, ensure_ascii=False用于保存中文 9 s = json.dumps(data, indent=2, ensure_ascii=False) 10 with open(path, 'w+', encoding='utf-8') as f: 11 print('save', path, s, data) 12 f.write(s) 13 14 15 defload(path): 16 """从一个文件中载入数据并转化为dict或者list""" 17 with open(path, 'r', encoding='utf-8') as f: 18 s =f.read() 19 print('load', s) 20 returnjson.loads(s) 21 22 23 #Model是用于存储数据的基类 24 classModel(object): 25 @classmethod 26 defdb_path(cls): 27 classname = cls.__name__ 28 path = '{}.txt'.format(classname) 29 returnpath 30 31 @classmethod 32 defnew(cls, form): 33 m = cls(form) #初始化实例 m = Model(form) 34 returnm 35 36 @classmethod 37 defall(cls): 38 path =cls.db_path() 39 models =load(path) 40 ms = [cls.new(m) for m inmodels] 41 returnms 42 43 defsave(self): 44 models =self.all() 45 print('models', models) 46 models.append(self) 47 l = [m.__dict__ for m inmodels] 48 path =self.db_path() 49 save(l, path) 50 51 def __repr__(self): 52 classname = self.__class__.__name__ 53 properties = ['{}: ({})'.format(k, v) for k, v in self.__dict__.items()] 54 s = ''.join(properties) 55 return '< {} {} > '.format(classname, s)
![Django(一):从socket到MVC第34张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第35张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #Message.py 2 from . importModel 3 4 #定义一个class用于保存message 5 classMessage(Model): 6 def __init__(self, form): 7 self.author = form.get('author', "") 8 self.message = form.get('message', "")
![Django(一):从socket到MVC第36张](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
![Django(一):从socket到MVC第37张](https://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif)
1 #User.py 2 from . importModel 3 4 #以下两个类用于实际的数据处理 5 classUser(Model): 6 def __init__(self, form): 7 self.username = form.get('username', "") 8 self.password = form.get('password', "") 9 10 defvalidate_login(self): 11 return self.username == "gua" and self.password == "123" 12 13 defvalidate_register(self): 14 return len(self.username) > 2 and len(self.password) > 2
当然,,server也可以再拆分成server和manage的。将serve.py中的if main 放到manage.py去执行。
上面这些虽然简陋,但是实现了socket搭建web服务器的基本逻辑和功能,有助于梳理web服务的流程。原理在手,一切的web框架不外乎如是。