一、web框架
web框架,即framework,特指为解决一个开放性问题而设计的具有一定约束性的支撑结构,使用框架可以快速开发特定的系统。他山之石,可以攻玉。python的所有web框架,都是对socket进行封装的。
web应用本质上是一个socket服务端,用户的浏览器是一个socket客户端。socket处在应用层与传输层之间,是操作系统中I/O系统的延伸部分(接口),负责系统进程和应用之间的通信。【python网络编程基础】
上面这个解释看起来有点费劲。重新解释一遍:
socket是在应用层和传输层之间的一个抽象层,扮演“信使”角色。它把tcp/ip层复杂的操作抽象为几个简单的接口以供应用层调用,从而实现在网络中通信。
django是python web开发的主流框架,另外还有flask和tensorflow。django框架必须掌握,要学精通。
web应用的流程:
//浏览器发送一个HTTP请求; //服务器收到请求,根据请求信息,进行函数处理,生成一个HTML文档; //服务器把HTML文档作为HTTP响应的Body发送给浏览器; //浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示;
回顾一下socket的udp连接和tcp连接,注:查看当前进程和杀死进程的window命令:
netstat -ano|findstr 45678
taskkill -PID 45678的进程 -F
udp客户端和服务端
import socket # upd链接 # SOCK_DGRAM:数据报套接字,主要用于UDP协议 udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 关闭防火墙 # 同一网段(局域网)下,主机的ip地址和端口号. sendAddr = ('192.168.10.247', 8080) # 绑定端口:写的是自己的ip和固定的端口,一般是写在sever端. udpSocket.bind(('', 9900)) # sendData = bytes(input('请输入要发送的数据:'), 'gbk') # gbk, utf8, str sendData = input('请输入要发送的数据:').encode('gbk') # python3是unicode编码,也就是以unicode格式写成的字节. # encode,重写编码:就是把unicode环境下的数据,重新编码成指定格式的字节,比如gbk, utf8等.然后接收方以同样的解码格式解码. # 用网络串口助手作为udp的服务端.网络串口助手是字节和十六进制的字节,没有unicode编码,有gbk编码和utf8编码.所以要解码成gbk # 反过来,接收数据的时候,也要知道对方发送的数据要怎么解码. # 使用udp发送数据,每一次发送都需要写上接收方的ip地址和端口号 udpSocket.sendto(sendData, sendAddr) # udpSocket.sendto(b'hahahaha', ('192.168.10.247', 8080)) udpSocket.close()
import socket udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 接收方一般需要绑定端口 # ''表示自己电脑的任何一个ip,即无线和有限同时连接或者电脑有不同的网卡(桥接),会有多个ip. # 绑定自己的端口 bindAddr = ('', 7788) udpSocket.bind(bindAddr) recvData = udpSocket.recvfrom(1024) # print(recvData) print(recvData[0].decode('gbk')) udpSocket.close() # recvData的格式:(data, ('ip', 端口)).它是一个元组,前面是数据,后面是一个包含ip和端口的元组.
tcp客户端和服务端
import socket tcpClient = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverAddr = ('192.168.10.247', 8899) # tcp的三次握手,写进了这一句话 tcpClient.connect(serverAddr) sendData = input('') # 直接用send就行了,udp是用sendto tcpClient.send(sendData.encode('gbk')) recvData = tcpClient.recv(1024) print('接收到的数据为:%s' % recvData.decode('gbk')) tcpClient.close() # 为什么用send而不是sendto?因为tcp连接是事先链接好了,后面就直接发就行了。前面的connect已经连接好了,后面直接用send发送即可。 # 而udp必须用sendto,是发一次数据,连接一次。必须要指定对方的ip和port。 # 相同的道理,在tcpServer端,要写recv,而不是recvfrom来接收数据
import socket tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) tcpServer.bind(('', 8899)) tcpServer.listen(5) # tcp的三次握手,写进了这一句话当中 tcpClient, tcpClientInfo = tcpServer.accept() # tcpServer.accept(),不需要写ip,可以接收多个客户端的。但事先要绑定端口和接入的客户端的数量 # client 表示接入的新的客户端 # clientInfo 表示接入的新的客户端的ip和端口port recvData = tcpClient.recv(1024) print('%s: %s' % (str(tcpClientInfo), recvData.decode('gbk'))) # tcp的四次握手,写进了这一句话 tcpClient.close() tcpServer.close() # tcpServer.accept():等待客户端的接入,自带堵塞功能:即必须接入客户端,然后往下执行 # tcpClient.recv(1024): 也是堵塞,不输入数据就一直等待,不往下执行. # tcpServer创建了两个套接字,一个是Server,另一个是tcpClient.Server负责监听接入的Client,再为其创建专门的tcpClient进行通信.
两个小实例:tcpServer开启循环模式,以及udp的多线程聊天室
import socket Server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) Server.bind(('', 9000)) Server.listen(10) while True: # 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务 serverThisClient, ClientInfo = Server.accept() print('Waiting connect......') # 如果客户发送的数据是空的,那么断开连接 while True: recvData = serverThisClient.recv(1024) if len(recvData) > 1: print('recv: %s' % recvData.decode('gbk')) sendData = input('send: ') serverThisClient.send(sendData.encode('gbk')) else: print('再见!') break serverThisClient.close()
from threading import Thread import socket # 收数据,然后打印 def recvData(): while True: recvInfo = udpSocket.recvfrom(1024) print('%s:%s' % (str(recvInfo[1]), recvInfo[0].decode('gbk'))) # 检测键盘,发数据 def sendData(): while True: sendInfo = input('') udpSocket.sendto(sendInfo.encode('gbk'), (destIp, destPort)) udpSocket = None destIp = '' destPort = 0 # 多线程 def main(): global udpSocket global destIp global destPort destIp = input('对方的ip: ') destPort = int(input('对方的端口:')) udpSocket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udpSocket.bind(('', 45678)) tr = Thread(target=recvData) ts = Thread(target=sendData) tr.start() ts.start() tr.join() ts.join() if __name__ == '__main__': main()
根据以上socket,可以写一个简单的web应用
import socket def handle_request(client): buf = client.recv(1024) # 请求头 print(buf.decode('utf8')) client.send("HTTP/1.1 200 OK ".encode("utf8")) # 响应头 client.send("<h1 style='color:red'>Hello, yuan</h1>".encode("utf8")) # body数据 # 把上面的html写到当前文件夹下的html里,把上行代码换成下面代码,就是一个web基本的流程. # with open('hello.html', 'rb') as f: # data = f.read() # client.send(data) def main(): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.bind(('localhost',8001)) sock.listen(5) while True: connection, address = sock.accept() handle_request(connection) connection.close() if __name__ == '__main__': main() # 在浏览器中输入localhost:8001,显示红色字体的hello,yuan. # 在后台Terminal中,会输出buf的内容: ''' GET / HTTP/1.1 Accept: text/html, application/xhtml+xml, image/jxr, */* Accept-Language: zh-CN User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36 Edge/15.15063 Accept-Encoding: gzip, deflate Host: 127.0.0.1:8001 Connection: Keep-Alive Cookie: csrftoken=pW12OxqURLh8VgHwwR1TzlR65ubxDzZgBv9SJtXZZeKuqd38TWGyntT84gX29rtr '''
二、WSGI
接收HTTP请求,解析HTTP请求,发送HTTP响应(如上面显示的)涉及到TCP连接、HTTP原始请求和响应格式。它涉及到底层较为复杂的构造:
根据网络通信的OSI模型,应用层封装一次数据,包含http参数和数据(数据格式是HTML,CSS,协议是TLS、HTTP等),然后发给传输层;
传输层接收封装后的数据,再封装一次,加上tcp头,变成tcp头、http data, 然后发给网络层;
网络层接收封装后的数据,再封装一次,加上ip头,变成ip头、tcp头、http data,然后发给数据数据链路层;
数据链路层接收封装后的数据,再封装一次,加上以太网头,变成ethernet头、ip头、tcp头、 http data,通过物理层对客户端浏览器进行响应。
这样,web服务端算是完成了一次响应。【详见OSI模型】
python内置了一个WSGI服务器(Web Server Gateway Interface),这个模块就是wigiref。它封装了对tcp、http请求和响应的操作。它负责处理socket的接口(封装了socket对象和准备过程(bind,listen等)),把请求信息(请求头和请求体)封装成键值对的对象,并可以方便地设置响应头和返回请求体。下面通过一个wsgi内置的写一个 web应用来加深理解wsg。i
step1:WSGI的environ和return
from wsgiref.simple_server import make_server def application(environ, start_response): for key, value in environ.items(): print(key, value) # 响应头,告诉浏览器响应的内容是什么格式 start_response('200 OK', [('Content-Type', 'text/html')]) return [b'<h1>Hello, web!</h1>'] # 响应体 # make_server是一个http对象 httpd = make_server('', 8080, application) print('Serving HTTP on port 8000 ...') # 开始监听HTTP请求: httpd.serve_forever() ''' application()就是符合wSGI标准的一个HTTP处理函数,它接收两个参数: environ:一个包含所有HTTP请求信息的dict对象; start_response:一个发送HTTP响应的函数; 在applicaiton()函数中调用start_response('200 OK', [('Content-Type', 'text/html')]),就发送了HTTP响应的Header. start_response()函数接收两个参数: 一个是HTTP响应码 一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示. 通常会把Content-Type头发送给浏览器,其他很多常用的HTTP Header也应该发送。 然后,函数的返回值b'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器. 有了WSGI,我们关心的就是如何从environ中拿到解析好的请求信息,从而写自己的处理逻辑了。 '''
ALLUSERSPROFILE C:ProgramData APPDATA C:UsersadminAppDataRoaming COMMONPROGRAMFILES C:Program FilesCommon Files COMMONPROGRAMFILES(X86) C:Program Files (x86)Common Files COMMONPROGRAMW6432 C:Program FilesCommon Files COMPUTERNAME DESKTOP-ABISCDM COMSPEC C:WINDOWSsystem32cmd.exe FPS_BROWSER_APP_PROFILE_STRING Internet Explorer FPS_BROWSER_USER_PROFILE_STRING Default HOMEDRIVE C: HOMEPATH Usersadmin LOCALAPPDATA C:UsersadminAppDataLocal LOGONSERVER \DESKTOP-ABISCDM MOZ_PLUGIN_PATH C:其它软件福昕阅读器Foxit Readerplugins NUMBER_OF_PROCESSORS 4 ONEDRIVE C:UsersadminOneDrive OS Windows_NT PATH C:Program Files (x86)InteliCLS Client;C:Program FilesInteliCLS Client;C:WINDOWSsystem32;C:WINDOWS;C:WINDOWSSystem32Wbem;C:WINDOWSSystem32WindowsPowerShellv1.0;C:Program Files (x86)IntelIntel(R) Management Engine ComponentsDAL;C:Program FilesIntelIntel(R) Management Engine ComponentsDAL;C:Program Files (x86)IntelIntel(R) Management Engine ComponentsIPT;C:Program FilesIntelIntel(R) Management Engine ComponentsIPT;C:Program Files (x86)Windows Kits8.1Windows Performance Toolkit;C:Program FilesMySQLMySQL Utilities 1.6;C:Program FilesIntelWiFiin;C:Program FilesCommon FilesIntelWirelessCommon;C:pythonspython36Scripts;C:pythonspython36;C:UsersadminAppDataLocalMicrosoftWindowsApps; PATHEXT .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC PROCESSOR_ARCHITECTURE AMD64 PROCESSOR_IDENTIFIER Intel64 Family 6 Model 142 Stepping 9, GenuineIntel PROCESSOR_LEVEL 6 PROCESSOR_REVISION 8e09 PROGRAMDATA C:ProgramData PROGRAMFILES C:Program Files PROGRAMFILES(X86) C:Program Files (x86) PROGRAMW6432 C:Program Files PSMODULEPATH C:Program FilesWindowsPowerShellModules;C:WINDOWSsystem32WindowsPowerShellv1.0Modules PT5HOME C:Cisco Packet Tracer 6.0 PT6HOME C:Cisco Packet Tracer 6.0 PUBLIC C:UsersPublic PYCHARM_HOSTED 1 PYTHONIOENCODING UTF-8 PYTHONPATH C:UsersadminDesktoppythonNote网络编程基础 PYTHONUNBUFFERED 1 SESSIONNAME Console SYSTEMDRIVE C: SYSTEMROOT C:WINDOWS TEMP C:UsersadminAppDataLocalTemp TMP C:UsersadminAppDataLocalTemp USERDOMAIN DESKTOP-ABISCDM USERDOMAIN_ROAMINGPROFILE DESKTOP-ABISCDM USERNAME admin USERPROFILE C:Usersadmin VS140COMNTOOLS C:Program Files (x86)Microsoft Visual Studio 14.0Common7Tools WINDIR C:WINDOWS SERVER_NAME DESKTOP-ABISCDM GATEWAY_INTERFACE CGI/1.1 SERVER_PORT 8080 REMOTE_HOST CONTENT_LENGTH SCRIPT_NAME SERVER_PROTOCOL HTTP/1.1 SERVER_SOFTWARE WSGIServer/0.2 REQUEST_METHOD GET PATH_INFO /alex QUERY_STRING REMOTE_ADDR 127.0.0.1 CONTENT_TYPE text/plain HTTP_HOST 127.0.0.1:8080 HTTP_USER_AGENT Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0 HTTP_ACCEPT text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 HTTP_ACCEPT_LANGUAGE zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 HTTP_ACCEPT_ENCODING gzip, deflate HTTP_COOKIE csrftoken=b2CBBrcSjnRT91jAB1KJzUWphnWB28UiLrHQuECAsv6vWPy4vGWIsHXkRHWh0gTy; sessionid=y4l1ybo6t01nhpqsjkh6fyqm44blinca HTTP_CONNECTION keep-alive HTTP_UPGRADE_INSECURE_REQUESTS 1 HTTP_CACHE_CONTROL max-age=0 wsgi.input <_io.BufferedReader name=748> wsgi.errors <_io.TextIOWrapper name='<stderr>' mode='w' encoding='UTF-8'> wsgi.version (1, 0) wsgi.run_once False wsgi.url_scheme http wsgi.multithread True wsgi.multiprocess False wsgi.file_wrapper <class 'wsgiref.util.FileWrapper'>
step2:根据浏览器输入的url,进行逻辑处理:
from wsgiref.simple_server import make_server def application(environ, start_response): # 响应头,告诉浏览器响应的内容是什么格式 start_response('200 OK', [('Content-Type', 'text/html')]) # message = start_response('200 OK', [('Content-Type', 'text/html')]) # print(message) # 打印所有的请求信息 for key, value in environ.items(): print(key, value) # 从environ中拿到url,键名是'PATH_INFO',然后做一个处理: path = environ['PATH_INFO'] f1 = open('index1.html', 'rb') data1 = f1.read() #当前文件夹下创建index1.html和index2.html,内容随便写. f2 = open('index2.html', 'rb') data2 = f2.read() if path == '/yuan': return [data1] elif path == '/alex': return [data2] else: return [b'<h1>Hello, web!</h1>'] # 响应体 # make_server是一个http对象 httpd = make_server('', 8080, application) print('Serving HTTP on port 8000 ...') # 开始监听HTTP请求: httpd.serve_forever()
step3:把上述逻辑处理进行解耦:
from wsgiref.simple_server import make_server def f1(): f1 = open('index1.html', 'rb') return [f1.read()] def f2(): f2 = open('index2.html', 'rb') return [f2.read()] def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) path = environ['PATH_INFO'] if path == '/yuan': return f1() elif path == '/alex': return f2() else: return [b'<h1>Hello, web!</h1>'] httpd = make_server('', 8080, application) print('Serving HTTP on port 8000 ...') httpd.serve_forever()
step4:接近MVC的升级式写法:
import time from wsgiref.simple_server import make_server def f1(request): print(request['QUERY_STRING']) f1 = open('index1.html', 'rb') return [f1.read()] def f2(request): f2 = open('index2.html', 'rb') return [f2.read()] def f3(request): f3 = open('index3.html', 'rb') localtime = time.strftime('%Y-%m-%d %X',time.localtime()) html = f3.read() print(html) html = str(html, 'utf8').replace('!time!', str(localtime)) return [html.encode('utf8')] def routes(): urlpatterns = [ ('/yuan', f1), ('/alex', f2), ('/cur_time', f3), ] return urlpatterns def application(environ, start_response): path = environ['PATH_INFO'] start_response('200 OK', [('Contentt-Type', 'text/html')]) urlpatterns = routes() func = None for item in urlpatterns: if item[0] == path: func = item[1] break if func: return func(environ) else: return ['<h2>404</h2>'.encode('utf8')] httpd = make_server('', 8001, application) print('Serving HTTP on port 8001 ...') httpd.serve_forever() # 在浏览器中输入localhost:8001/yuan,或/alex,或/cur_time,或一个不存在的url地址 ''' html=b'<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h2>!time!</h2> </body> </html>' 要点一: f3中的html,只写了个h2标签,里面写了!time!这个单词。 f3跟f1,f2的不同之处在于,它对html进行了修改操作(访问一次,打印一次当前时间戳)。 假如定义!time!是一个固定的“语言”,像!vector!,!func!等,都作为“自定义”的python处理方式,那么就可以识别该操作符,然后进行处理。 django定义{{ 变量/函数 }} 和{% 变量/函数/简单循环 %},来与html进行交互。 它扩展为django中的render功能,能够支持对html文件传入参数。以及templatetags,用来在后端给html增加一些功能。 要点二: rountes函数:在list列表中存放每个url和相应的处理函数的tuple,绑定url和处理函数。 每当有url访问,就对这个List对象进行遍历,找到处理函数就返回相应的结果,没找到就返回一个404错误。 它相当于django中的url.py文件。 applicattion()函数中写了多行代码来处理url和func的映射关系。 在django中,它被封装了起来, 用户只需要在urlpatterns中添加url和处理函数。然后在views中写处理逻辑就行了。 '''
如果把f1(),f2(),f3()写在一个models.py中,把routes写进一个urls.py中,把application写在controller.py中,把html写在一个view文件夹下。那就构成了web框架中著名的MVC模式。
三、web框架的MVC模式和django的MTV模式
大部分开发语言中都有MVC框架,MVC框架的核心思想是:解耦。上面WSGI的升级版写法已经体现了这个特点。
所谓MVC,就是指model、view、controller。m主要用于对数据库层的封装,v主要向用户展示结果,c是核心,用于处理请求、获取数据、返回结果。
django是一款python的web开发框架,使用了MVT模式,它本质上与MVC模式没什么差别。MVT是model, template, view。m负责业务对象与数据库的对象(ORM), template负责如何把页面展示给用户,view负责业务逻辑,并在适当的时候调用model和template。此外,还有一个url分发器,它的作用是将一个个url页面请求分发给不同的view处理。
重新解释一遍:model负责与数据库交互,view是核心,负责接收请求,获取数据,返回结果,template负责呈现内容到浏览器。
http://www.cnblogs.com/yuanchenqi/articles/6083427.html # 老男孩python全栈工程师培训著名讲师苑昊博客