正则表达式
通俗的讲就是按照某种规则去匹配符合条件的字符串
正则表达式,又称正规表示式、正规表示法、正规表达式、规则表达式、常规表示法(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列匹配某个句法规则的字符串。在很多文本编辑器里,正则表达式通常被用来检索、替换那些匹配某个模式的文本。
正则表达式在线工具
Python中字符串前面加上 r 表示原生字符串,
与大多数编程语言相同,正则表达式里使用"/“作为转义字符,这就可能造成反斜杠困扰。假如你需要匹配文本中的字符”/",那么使用编程语言表示的正则表达式里将需要4个反斜杠"//":前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个反斜杠。
Python里的原生字符串很好地解决了这个问题,有了原生字符串,再也不用担心漏写了反斜杠,写出来的表达式也更直观。
re模块
在Python中需要通过正则表达式对字符串进行匹配的时候,可以使用re模块
re.match() 能够匹配出以xxx开头的字符串
re.match(正则表达式,需要处理的字符串)
如果上一步匹配到数据的话,可以使用group方法来提取数据
匹配单个字符
字符 功能
-
. 匹配任意1个字符(除了\n)
-
[ ] 匹配[ ]中列举的字符
-
匹配0到9第一种写法
-
匹配0到9第二种写法
-
注意:
-
-
\d 匹配一个数字,即0-9
-
\D 匹配一个非数字,即不是数字
-
\s 匹配一个空白,即 空格,tab键
-
\S 匹配一个非空白
-
\w 只匹一个配单词字符,即a-z、A-Z、0-9、_、语言(中文,日文等)
-
\W 匹配一个非单词字符
匹配多个字符
-
* 匹配前一个字符出现0次或者无限次,即可有可无
-
+ 匹配前一个字符出现1次或者无限次,即至少有1次
-
? 匹配前一个字符出现1次或者0次,即要么有1次,要么没有
-
{m} 匹配前一个字符出现m次
-
{m,n} 匹配前一个字符出现从m到n次
注意:使用{}时,若匹配字符串长度大于{}中判断长度,则只会输出{}长度的结果;若匹配字符串长度小于{}中长度,则结果为None
案例:判断命名规则是否符合(命名中,开头只能以字母、下划线开头)
import re
names = ["name1", "_name", "2_name", "__name__"]
for name in names:
res = re.match(r'[a-zA-Z_]+[\w]*',name)
if res:
print('%s 符合变量命名规则' % name)
else:
print('%s 不符合变量命名规则' % name)
匹配开头结尾
在命名规则中,如果判断name#123,和name!
import re
names = ['name#123','name!']
for name in names:
res = re.match(r'[a-zA-Z_]+[\w]*',name)
if res:
# 只有匹配成功,才可以通过group()获取结果
print('%s 符合命名规则,匹配结果为%s' % (name,res.group()))
else:
print('%s 不符合变量命名规则' % name)
这是由于match默认从开头进行匹配,不能匹配结尾,当进行以上判断时候,只判断到#和!之前的内容
- ^ 匹配字符串开头
- $ 匹配字符串结尾
import re
names = ["name1", "_name", "2_name", "__name__",'name#123','name!']
for name in names:
res = re.match(r'^[a-zA-Z_]+[\w]*$',name)
if res:
# 只有匹配成功,才可以通过group()获取结果
print('%s 符合命名规则,匹配结果为%s' % (name,res.group()))
else:
print('%s 不符合变量命名规则' % name)
案例:匹配邮箱地址
import re
email_addr = input('请输入邮箱地址:')
# r表示关闭python转义
res = re.match(r'^(\w)+(.\w+)*@(\w)+((.\w+)+)$',email_addr)
if res:
res.group()
else:
print('邮箱格式不符')
匹配分组
- | 匹配左右任意一个表达式
- 案例:匹配出163、126、qq邮箱
import re emails = ['test@163.com','test@126.com','test@qq.com','test@gmail.com'] for email in emails: res = re.match(r'^\w+@(163|qq|126).com$',email) if res: print('%s 匹配成功,匹配结果%s' % (email,res.group())) else: print('%s 匹配失败' % email)
- (ab) 将括号中字符作为一个分组
- 案例:匹配电话号码xxx-xxxxxxxx
import re number = '010-12345678' res = re.match(r'([^-]*)-(\d+)',number) print('完整电话号码为:',res.group()) print('区号:',res.group(1)) print('本地电话:',res.group(2))
- \num 引用分组num匹配到的字符串
- 案例:匹配出<h1>hh</h1>
import re html_str = '<h1>hh</h1>' res = re.match(r'<\w*>.*</\w*>',html_str) print(res.group()) print('分组后') res2 = re.match(r'<(\w*)>.*</\1>',html_str) print(res2.group()) print(res2.group(1))
- 多个分组时候,注意对应位置
案例:匹配出<div><h1>这是标题</h1></div>
import re html_str = '<div><h1>这是标题</h1></div>' res = re.match(r'<(\w*)><(\w*)>.*</\2></\1>',html_str) print('匹配结果为:',res.group()) print('分组1为:',res.group(1)) print('分组2为:',res.group(2))
- 如果遇到非正常的html格式字符串,匹配出错
如果在第一对<>中是什么,按理说在后面的那对<>中就应该是什么ret = re.match("<[a-zA-Z]*>\w*</[a-zA-Z]*>", "<html>hh</htmlbalabala>") print(ret.group())
- (?P) 分组起别名
(?P=name) 引用别名为name分组匹配到的字符串- 案例:利用(?P) (?P=name) 匹配出<div><h1>这是标题</h1></div>
import re html_str = '<div><h1>这是标题</h1></div>' res = re.match(r'<(?P<name_1>\w*)><(?P<name_2>\w*)>.*</(?P=name_2)></(?P=name_1)>',html_str) print('匹配结果:',res.group()) print('name_1组:',res.group('name_1')) print('name_2组:',res.group('name_2'))
re 高级用法
-
search() 匹配到符合其要求的地方便匹配到,不需要从头开始匹配,而match()是从头匹配
import re content = '文章点赞数为12345' re.search(r'\d+',content).group()
-
findall() 返回值为列表,不需要再调用group()
import re test = 'a=1,b=2,c=3' re.findall(r'\d+',test)
-
sub 将匹配到的数据进行替换
-
sub(正则,替换后的字符串,正则匹配的字符串)
import re test = 'a=1' re.sub(r'\d+','100',test)
案例:提取如下html页面内容
<div>
<p>岗位职责:</p>
<p>完成推荐算法、数据统计、接口、后台等服务器端相关工作</p>
<p><br></p>
<p>必备要求:</p>
<p>良好的自我驱动力和职业素养,工作积极主动、结果导向</p>
<p> <br></p>
<p>技术要求:</p>
<p>1、一年以上 Python 开发经验,掌握面向对象分析和设计,了解设计模式</p>
<p>2、掌握HTTP协议,熟悉MVC、MVVM等概念以及相关WEB开发框架</p>
<p>3、掌握关系数据库开发设计,掌握 SQL,熟练使用 MySQL/PostgreSQL 中的一种<br></p>
<p>4、掌握NoSQL、MQ,熟练使用对应技术解决方案</p>
<p>5、熟悉 Javascript/CSS/HTML5,JQuery、React、Vue.js</p>
<p> <br></p>
<p>加分项:</p>
<p>大数据,数理统计,机器学习,sklearn,高性能,大并发。</p>
</div>
import re
def main():
content = """<div>
<p>岗位职责:</p>
<p>完成推荐算法、数据统计、接口、后台等服务器端相关工作</p>
<p><br></p>
<p>必备要求:</p>
<p>良好的自我驱动力和职业素养,工作积极主动、结果导向</p>
<p> <br></p>
<p>技术要求:</p>
<p>1、一年以上 Python 开发经验,掌握面向对象分析和设计,了解设计模式</p>
<p>2、掌握HTTP协议,熟悉MVC、MVVM等概念以及相关WEB开发框架</p>
<p>3、掌握关系数据库开发设计,掌握 SQL,熟练使用 MySQL/PostgreSQL 中的一种<br></p>
<p>4、掌握NoSQL、MQ,熟练使用对应技术解决方案</p>
<p>5、熟悉 Javascript/CSS/HTML5,JQuery、React、Vue.js</p>
<p> <br></p>
<p>加分项:</p>
<p>大数据,数理统计,机器学习,sklearn,高性能,大并发。</p>
</div>"""
# 将上述内容中标签和空格( ),换行(\n)替换为空字符串即可,下面为方便观看没有进行换行匹配
res = re.sub(r'<\w*>|</\w*>|[ ]','',content)
# 或者
# res = re.sub(r"<[^>]*>| |\n", "", content)
print('正则后数据为:',res)
if __name__=='__main__':
main()
- split 根据匹配进行切割字符串,并返回一个列表
# info:Lisa 27 import re test = 'info:Lisa 27' res = re.split(r':| ',test) print(res)
python贪婪和非贪婪
Python里数量词默认是贪婪的(在少数语言里也可能是默认非贪婪)
总是尝试匹配尽可能多的字符;
非贪婪则相反,总是尝试匹配尽可能少的字符。
在"*","?","+","{m,n}"后面加上?,使贪婪变成非贪婪。
import re
test = 'abc123'
res1 = re.match(r'abc(\d+)',test).group()
print('贪婪:',res1)
res2 = re.match(r'abc(\d+?)',test).group()
print('非贪婪:',res2)
案例:提取html标签中的url地址
import re
test = '<img data-original="https://rpic.douyucdn.cn/appCovers/2016/11/13/1213973_201611131917_small.jpg" src="https://rpic.douyucdn.cn/appCovers/2016/11/13.jpg" style="display: inline;">'
# findall无group()
res = re.findall(r'https:.*?.jpg',test)
res
HTTP协议简介(超文本传输协议)
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。而浏览器和服务器之间的传输协议是HTTP,所以:
HTML是一种用来定义网页的文本,用于编写网页;
HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
以Chrome为例:Chrome浏览器提供了一套完整地调试工具,非常适合Web开发。
安装好Chrome浏览器后,打开Chrome,在菜单中选择 “视图”,“开发者”,“开发者工具”,就可以显示开发者工具(快捷键F12):
- Elements显示网页的结构
- Network显示浏览器和服务器的通信
点击Network,确保第一个小红灯亮着,Chrome就会记录所有浏览器和服务器之间的通信。
Request Headers的view source,可以看到浏览器发给服务器的请求:
GET表示一个读取请求,将从服务器获得网页数据,/表示URL的路径,URL总是以/开头,/就表示首页,最后的HTTP/1.1指示采用的HTTP协议版本是1.1。GET / HTTP/1.1
1.1和1.0版本区别在于:1.1版本允许多个HTTP请求复用一个TCP连接,以加快传输速度。
表示请求的域名,如果一台服务器有多个网站,服务器就需要通过Host来区分浏览器请求的是哪个网站。Host: www.baidu.com
Response Headers的view source,显示服务器返回的原始响应数据
HTTP响应分为Header和Body两部分(Body是可选项)
200表示一个成功的响应,后面的OK是说明。HTTP/1.1 200 OK
如果返回的不是200,那么往往有其他的功能,例如
失败的响应有404 Not Found:网页不存在
500 Internal Server Error:服务器内部出错
3xx:重定向
Content-Type指示响应的内容,这里是text/html表示HTML网页。Content-Type: text/html;charset=utf-8
注意
浏览器就是依靠Content-Type来判断响应的内容是网页还是图片,是视频还是音乐。浏览器并不靠 URL来判断响应的内容,所以,即使URL是http://www.baidu.com/xxx.jpg,它也不一定就是图片.
浏览器解析过程
当浏览器读取到某网页首页的HTML源码后,它会解析HTML,显示页面,然后,根据HTML里面的各种链接,再发送HTTP请求给该网站服务器,拿到相应的图片、视频、Flash、JavaScript脚本、CSS等各种资源,最终显示出一个完整的页面。所以在Network下面能看到很多额外的HTTP请求。
HTTP请求的流程
- 浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET还是POST,GET仅请求资源,POST会附带用户数据;
路径:/full/url/path;
域名:由Host头指定:Host: www.xxx.com
以及其他相关的Header;
如果是POST,那么请求还包括一个Body,包含用户数据 - 服务器向浏览器返回HTTP响应,响应包括:
响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误;
响应类型:由Content-Type指定;
以及其他相关的Header;
通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。 - 如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复以上2步骤
Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当编写一个页面时,只需要在HTTP请求中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源(此时就可以理解为TCP协议中的短连接,每个链接只获取一个资源,如需要多个就需要建立多个链接)
HTTP协议同时具备极强的扩展性,虽然浏览器请求的是http://www.baidu.com的首页,但是baidu在HTML中可以链入其他服务器的资源,从而将请求压力分散到各个服务器上,并且,一个站点可以链接到其他站点,无数个站点互相链接起来,就形成了World Wide Web,简称WWW。
HTTP格式
HTTP协议是一种文本协议,每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP GET请求的格式
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
每个Header一行一个,换行符是\r\n。
HTTP POST请求的格式
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
当遇到连续两个\r\n时,Header部分结束,后面的数据全部是Body。
HTTP响应的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3
body data goes here...
HTTP响应如果包含body,也是通过\r\n\r\n(连续两个\r\n)来分隔的。
注意:
Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。
当存在Content-Encoding时,Body数据是被压缩的,最常见的压缩方式是gzip,所以,看到Content-Encoding: gzip时,需要将Body数据先解压缩,才能得到真正的数据。压缩的目的在于减少Body的大小,加快网络传输。
Web静态服务器-1-显示固定的页面
import socket
def server_client(socket):
# 服务客户端,返回数据
request = socket.recv(1024).decode('utf-8')
print(request)
# 返回HTTP格式数据给浏览器
# header
response = 'HTTP /1.1 200/r/n'
response += '\r\n'
# body
response += '<h1>welcome</h1>'
socket.send(response.encode('utf-8'))
# 关闭套接字
socket.close()
def main():
# 创建tcp套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 绑定本地
tcp_server_socket.bind('',7788)
# 设置listen,主动变被动
tcp_server_socket.listen(128)
# 开始服务
while True:
# 等待客户端链接
client_socket,client_addr = tcp_server_socket.accept()
# 为这个客户端服务
server_client(client_socket)
# 关闭服务器套接字
tcp_server_socket.close()
if __name__=='__main__':
main()
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
tcp的3次握手和4次挥手(重点)
详细解释参考
tcp3次握手 - 建立链接
- 发送端首先发送一个带有SYN(synchronize)标志地数据包给接收方。
- 接收方接收后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示收到。
- 最后,发送方再回传一个带有ACK标志的数据包,表示’握手‘结束。
tcp的4次挥手 - 断开链接,双方释放资源
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手
tcp长连接和短连接
TCP短连接
连接->传输数据->关闭连接
Web静态服务器-2-显示需要的页面
import socket
def server_client(socket):
# 接收浏览器发送的http请求
request = socket.recv(1024).decode('utf-8')
print(request)
# 构造http格式数据发送给浏览器
response = 'HTTP/1.1 200 OK\r\n'
response += '\r\n'
# 将静态页面内容发送给浏览器
with open(path,'rb') as f:
content = f.read()
socket.send(response.encode('utf-8'))
socket.send(content)
# 关闭套接字
socket.close()
def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUESADDR,1)
tcp_server_socket.bind('',7890)
tcp_server_socket.listen(128)
while True:
client_socket,client_addr = tcp_server_socket.accept()
server_client(client_socket)
tcp_socket_server.close()
if __name__=='__main__':
main()
返回不同页面
import socket
import re
def server_client(socket):
# 接收浏览器发送的http请求
request = socket.recv(1024).decode('utf-8')
request_lines = request.splitlines()
for line in request_lines:
print(line)
# 获取静态html文件名
res = re.match(r'[^/]+(/[^]*)',request_lines[0])
if res:
file_name = res.group(1)
if file_name =='/'
file_name = '/index.html'
# 读取本地静态文件内容
try:
f = open('./html'+file_name,'rb')
except:
response = 'HTTP/1.1 404 NOT FOUND\r\n'
response += '/r/n'
response += '----file not exist----'
socket.send(resopnse.encode('utf-8'))
else:
content = f.read()
f.close()
# 构造http格式数据发送给浏览器
response = 'HTTP/1.1 200 OK\r\n'
response += '\r\n'
socket.send(response.encode('utf-8'))
socket.send(content)
# 关闭套接字
socket.close()
def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUESADDR,1)
tcp_server_socket.bind('',7890)
tcp_server_socket.listen(128)
while True:
client_socket,client_addr = tcp_server_socket.accept()
server_client(client_socket)
tcp_socket_server.close()
if __name__=='__main__':
main()
Web静态服务器-3-多进程
import socket
import re
import multiprocessing
def server_client(socket):
# 接收浏览器发送的http请求
request = socket.recv(1024).decode('utf-8')
request_lines = request.splitlines()
for line in request_lines:
print(line)
# 获取静态html文件名
res = re.match(r'[^/]+(/[^]*)',request_lines[0])
if res:
file_name = res.group(1)
if file_name =='/'
file_name = '/index.html'
# 读取本地静态文件内容
try:
f = open('./html'+file_name,'rb')
except:
response = 'HTTP/1.1 404 NOT FOUND\r\n'
response += '/r/n'
response += '----file not exist----'
socket.send(resopnse.encode('utf-8'))
else:
content = f.read()
f.close()
# 构造http格式数据发送给浏览器
response = 'HTTP/1.1 200 OK\r\n'
response += '\r\n'
socket.send(response.encode('utf-8'))
socket.send(content)
def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUESADDR,1)
tcp_server_socket.bind('',7890)
tcp_server_socket.listen(128)
while True:
client_socket,client_addr = tcp_server_socket.accept()
p = multiprocessing.Process(target = server_client,args=(client_socket,))
p.start()
# 关闭套接字
client_socket.close()
tcp_socket_server.close()
if __name__=='__main__':
main()
面向对象代码参考
#coding=utf-8
import socket
import re
import multiprocessing
class WSGIServer(object):
def __init__(self, server_address):
# 创建一个tcp套接字
self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 允许立即使用上次绑定的port
self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定
self.listen_socket.bind(server_address)
# 变为被动,并制定队列的长度
self.listen_socket.listen(128)
def serve_forever(self):
"循环运行web服务器,等待客户端的链接并为客户端服务"
while True:
# 等待新客户端到来
client_socket, client_address = self.listen_socket.accept()
print(client_address) # for test
new_process = multiprocessing.Process(target=self.handleRequest, args=(client_socket,))
new_process.start()
# 因为子进程已经复制了父进程的套接字等资源,所以父进程调用close不会将他们对应的这个链接关闭的
client_socket.close()
def handleRequest(self, client_socket):
"用一个新的进程,为一个客户端进行服务"
recv_data = client_socket.recv(1024).decode('utf-8')
print(recv_data)
requestHeaderLines = recv_data.splitlines()
for line in requestHeaderLines:
print(line)
request_line = requestHeaderLines[0]
get_file_name = re.match("[^/]+(/[^ ]*)", request_line).group(1)
print("file name is ===>%s" % get_file_name) # for test
if get_file_name == "/":
get_file_name = DOCUMENTS_ROOT + "/index.html"
else:
get_file_name = DOCUMENTS_ROOT + get_file_name
print("file name is ===2>%s" % get_file_name) # for test
try:
f = open(get_file_name, "rb")
except IOError:
response_header = "HTTP/1.1 404 not found\r\n"
response_header += "\r\n"
response_body = "====sorry ,file not found===="
else:
response_header = "HTTP/1.1 200 OK\r\n"
response_header += "\r\n"
response_body = f.read()
f.close()
finally:
client_socket.send(response_header.encode('utf-8'))
client_socket.send(response_body)
client_socket.close()
# 设定服务器的端口
SERVER_ADDR = (HOST, PORT) = "", 8888# 设置服务器服务静态资源时的路径
DOCUMENTS_ROOT = "./html"
def main():
httpd = WSGIServer(SERVER_ADDR)
print("web Server: Serving HTTP on port %d ...\n" % PORT)
httpd.serve_forever()
if __name__ == "__main__":
main()
Web静态服务器-5-非堵塞模式
单进程非堵塞 模型
import socket
import time
def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUESADDR,1)
tcp_server_socket.bind('',7890)
tcp_server_socket.listen(128)
# 设置监听套接字为非堵塞状态
tcp_server_setblocking(False)
client_list = list()
while True:
time.sleep(0.5)
# 设置为非堵塞后,如果accept时,恰巧没有客户端connect,那么accept会产生一个异常,所以需要try来进行处理
try:
client_socket,client_addr = tcp_server_socket.accept()
except Exception as result:
print('---没有客户端到来----')
else:
print('----没有异常,说明有客户端接入----')
# 设置客户端套接字为非堵塞
client_socket.setblocking(False)
# 将到来客户端存入列表
client_list.append(client_socket)
# 在列表中遍历
for client in client_list:
try:
recv_data = client.recv(1024)
except Exception as result:
print('---当前客户端没有发送来数据----')
else:
# 当有数据到来需要判断套接字使用完否,使用完需要关闭
# 接收到数据有2中,1为正常数据,2为调用close()关闭套接字返回的数据
if recv_data:
print('---客户端发送来了数据---',recv_data)
else:
client_list.remove(client)
client.close()
print('---客户端关闭---')
tcp_server_socket.close()
if __name__=='__main__':
main()
TCP长连接
建立连接——数据传输…(保持连接)…数据传输——关闭连接
TCP长/短连接的优点和缺点
- 长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。
对于频繁请求资源的客户来说,较适用长连接。 - client与server之间的连接如果一直不关闭的话,随着客户端连接越来越多,server负担会很大,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个客户端连累后端服务。
- 短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。
- 但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。
TCP长/短连接的应用场景
- 长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。每个TCP连接都需要三次握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,所以每个操作完后都不断开,再次处理时直接发送数据包就OK了,不用建立TCP连接。
例如:数据库的连接用长连接,如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。 - 而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。所以并发量大,但每个用户无需频繁操作情况下需用短连好。
Web静态服务器-6-非堵塞模式单进程单线程非堵塞长连接
import socket
import re
import time
def server_client(socket,request):
# 接收浏览器发送的http请求
request_lines = request.splitlines()
for line in request_lines:
print(line)
# 获取静态html文件名
res = re.match(r'[^/]+(/[^]*)',request_lines[0])
if res:
file_name = res.group(1)
if file_name =='/'
file_name = '/index.html'
# 读取本地静态文件内容
try:
f = open('./html'+file_name,'rb')
except:
response = 'HTTP/1.1 404 NOT FOUND\r\n'
response += '/r/n'
response += '----file not exist----'
socket.send(resopnse.encode('utf-8'))
else:
response_body = f.read()
f.close()
# 构造http格式数据发送给浏览器
response_header = 'HTTP/1.1 200 OK\r\n'
response_header += 'Content-Length:%d\r\n' % len(response_body)
response_header += '\r\n'
response = response_header.encode('utf-8')+response_body
socket.send(response)
def main():
# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUESADDR,1)
tcp_server_socket.bind('',7890)
tcp_server_socket.listen(128)
# 设置监听套接字为非堵塞状态
tcp_server_setblocking(False)
client_list = list()
while True:
time.sleep(0.5)
# 设置为非堵塞后,如果accept时,恰巧没有客户端connect,那么accept会产生一个异常,所以需要try来进行处理
try:
client_socket,client_addr = tcp_server_socket.accept()
except Exception as result:
print('---没有客户端到来----')
else:
print('----没有异常,说明有客户端接入----')
# 设置客户端套接字为非堵塞
client_socket.setblocking(False)
# 将到来客户端存入列表
client_list.append(client_socket)
# 在列表中遍历
for client in client_list:
try:
recv_data = client.recv(1024)
except Exception as result:
print('---当前客户端没有发送来数据----')
else:
# 当有数据到来需要判断套接字使用完否,使用完需要关闭
# 接收到数据有2中,1为正常数据,2为调用close()关闭套接字返回的数据
if recv_data:
print('---客户端发送来了数据---',recv_data)
server_client(client,recv_data)
else:
client_list.remove(client)
client.close()
print('---客户端关闭---')
tcp_server_socket.close()
if __name__=='__main__':
main()
Web静态服务器-6-epoll(事件通知)
IO 多路复用
就是select,poll,epoll,有些地方也称这种IO方式为event driven IO。
select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。
它的基本原理就是select,poll,epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。
epoll简单模型
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():
new_socket, new_addr = s.accept()
print('有新的客户端到来%s' % str(new_addr))
# 将 conn 和 addr 信息分别保存起来
connections[new_socket.fileno()] = new_socket
addresses[new_socket.fileno()] = new_addr
# 向 epoll 中注册 新socket 的 可读 事件
epoll.register(new_socket.fileno(), select.EPOLLIN|select.EPOLLET)
# 如果是客户端发送数据
elif events == select.EPOLLIN:
# 从激活 fd 上接收
recvData = connections[fd].recv(1024).decode("utf-8")
if recvData:
print('recv:%s' % recvData)
else:
# 从 epoll 中移除该 连接 fd
epoll.unregister(fd)
# server 侧主动关闭该 连接 fd
connections[fd].close()
print("%s---offline---" % str(addresses[fd]))
del connections[fd]
del addresses[fd]
说明
- EPOLLIN (可读)
- EPOLLOUT (可写)
- EPOLLET (ET模式)
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式
LT模式与ET模式的区别如下:
-
LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
-
ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
web静态服务器-epool(事件通知)
import socket
import re
import time
import select
def service_client(new_socket, request):
"""为这个客户端返回数据"""
# 1.接收浏览器发送来的请求,即http请求
# request = new_socket.recv(1024).decode("utf-8")
# 切割字符串 splitlines()
request_lines = request.splitlines()
print(request_lines)
print("*"*30)
# 利用正则表达式获取文件名
# 一般格式为 POST或GET或PUT等 /index.html HTTP/1.1
# 所需提取内容为index.html等文件名
# 正则提取以/为分隔,第一个/前至少有一个任意字符,到下一个/前,*(以防出现http://127.0.0.1:7890/的情况)
ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0])
file_name = ""
if ret:
# 文件名为分组1内的内容
file_name = ret.group(1)
if file_name == "/":
file_name = "/index.html"
print(">>>>"+file_name+">>>>>")
try:
f = open("./html" + file_name,"rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "\r\n"
response += "-----FILE NOT FOUND-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.返回http数据格式给浏览器
# 2.1准备发送给浏览器的数据....header
response_body = html_content
response_header = "HTTP/1.1 200 OK\r\n" # 浏览器中可以解析的换行格式为\r\n,一般代码中换行为\n
response_header += "Content-Length:%d \r\n" % len(response_body)
response_header = "\r\n"
# header 为str,body为html_content为b
response = response_header.encode("utf-8") + response_body
# 2.2准备发送给浏览器的数据....body
# response += "<h1>hahahaha</h1>" # h1-h6 格式,h1字体最大,h6最小
# 将response 发送给浏览器
new_socket.send(response)
# 3.关闭套接字
# new_socket.close()
def main():
"""用来完成整体的控制"""
# 1.创建套接字
tcp_server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
tcp_server_socket.setblocking(False) # 将套接字变为非堵塞
# 2.绑定
tcp_server_socket.bind(("",7890))
# 3.listen()
tcp_server_socket.listen(128)
# 创建epoll对象
epl = select.epoll()
# 将监听套接字的fd(文件描述符)注册到epoll中
# epl.register(监听套接字fd,输入(select.EPOLLIN)/输出(select.EPOLLET)
epl.register(tcp_server_socket.fileno(),select.EPOLLIN)
# 定义一个字典用于存放套接字的fd和套接字
fd_event_dict = dict()
while True:
# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待,默认堵塞,返回一个fd和事件的列表
# fd_event_list():[(fd,event),],即[(套接字对应的文件描述符,该fd对应事件如可以调用recv()等)]
fd_event_list = epl.poll() # 直到os检测到数据到来,通过事件通知方式告诉这个程序,才会解开堵塞
for fd,event in fd_event_list:
# 4.等待新客户端链接
if fd == tcp_server_socket.fileno():
new_socket,client_addr = tcp_server_socket.accept()
epl.register(new_socket.fileno(),select.EPOLLIN)
# 将accept()获得的套接字的fd作为key,套接字作为value存放入字典中
fd_event_dict[new_socket.fileno()] = new_socket
# 判断新客户端是否有数据发来
elif event == select.EPOLLIN:
recv_data = fd_event_dict[fd].recv(1024).decode("utf-8")
if recv_data:
# 有数据发送时候,处理数据,fd_event_dict[fd]为字典中,key=fd的新客户端套接字
print("----客户端发送来了数据%s----" % recv_data)
service_client(fd_event_dict[fd], recv_data)
else:
# 没有数据发送时候
# 1.关闭套接字
fd_event_dict[fd].close()
# 2.取消该套接字的注册
epl.unregister(fd)
# 3.删除字典中该套接字及fd,字典中删除采用del
del fd_event_dict[fd]
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
I/O 多路复用的特点:
- 通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 ‘监视’ 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。
- 当然也可以多线程/多进程方式,一个连接过来开一个进程/线程处理,这样消耗的内存和进程切换页会耗掉更多的系统资源。 所以我们可以结合IO多路复用和多进程/多线程 来高性能并发,IO复用负责提高接受socket的通知效率,收到请求后,交给进程池/线程池来处理逻辑。
Web静态服务器-7-gevent版
from gevent import monkey
import gevent
import socket
import sys
import re
monkey.patch_all()
class WSGIServer(object):
"""定义一个WSGI服务器的类"""
def __init__(self, port, documents_root):
# 1. 创建套接字
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定本地信息
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind(("", port))
# 3. 变为监听套接字
self.server_socket.listen(128)
self.documents_root = documents_root
def run_forever(self):
"""运行服务器"""
# 等待对方链接
while True:
new_socket, new_addr = self.server_socket.accept()
gevent.spawn(self.deal_with_request, new_socket) # 创建一个协程准备运行它
def deal_with_request(self, client_socket):
"""为这个浏览器服务器"""
while True:
# 接收数据
request = client_socket.recv(1024).decode('utf-8')
# print(gevent.getcurrent())
# print(request)
# 当浏览器接收完数据后,会自动调用close进行关闭,因此当其关闭时,web也要关闭这个套接字
if not request:
new_socket.close()
break
request_lines = request.splitlines()
for i, line in enumerate(request_lines):
print(i, line)
# 提取请求的文件(index.html)
# GET /a/b/c/d/e/index.html HTTP/1.1
ret = re.match(r"([^/]*)([^ ]+)", request_lines[0])
if ret:
print("正则提取数据:", ret.group(1))
print("正则提取数据:", ret.group(2))
file_name = ret.group(2)
if file_name == "/":
file_name = "/index.html"
file_path_name = self.documents_root + file_name
try:
f = open(file_path_name, "rb")
except:
# 如果不能打开这个文件,那么意味着没有这个资源,没有资源 那么也得需要告诉浏览器 一些数据才行
# 404
response_body = "没有你需要的文件......".encode("utf-8")
response_headers = "HTTP/1.1 404 not found\r\n"
response_headers += "Content-Type:text/html;charset=utf-8\r\n"
response_headers += "Content-Length:%d\r\n" % len(response_body)
response_headers += "\r\n"
send_data = response_headers.encode("utf-8") + response_body
client_socket.send(send_data)
else:
content = f.read()
f.close()
# 响应的body信息
response_body = content
# 响应头信息
response_headers = "HTTP/1.1 200 OK\r\n"
response_headers += "Content-Type:text/html;charset=utf-8\r\n"
response_headers += "Content-Length:%d\r\n" % len(response_body)
response_headers += "\r\n"
send_data = response_headers.encode("utf-8") + response_body
client_socket.send(send_data)
# 设置服务器服务静态资源时的路径
DOCUMENTS_ROOT = "./html"
def main():
"""控制web服务器整体"""
# python3 xxxx.py 7890
if len(sys.argv) == 2:
port = sys.argv[1]
if port.isdigit():
port = int(port)
else:
print("运行方式如: python3 xxx.py 7890")
return
print("http服务器使用的port:%s" % port)
http_server = WSGIServer(port, DOCUMENTS_ROOT")
http_server.run_forever()
if __name__ == "__main__":
main()
网络通信过程
tcp-ip
计算机都遵守的网络通信协议叫做TCP/IP协议
- 早期的计算机网络,都是由各厂商自己规定一套协议,IBM、Apple和Microsoft都有各自的网络协议,互不兼容
- 为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议族(Internet Protocol Suite)就是通用协议标准。
- 因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议(族)
常用的网络协议如下图所示
网际层也称为:网络层
网络接口层也称为:链路层
另外一套标准
wireshark抓包工具使用