03 web服务器创建
1_正则表达式
01_匹配单个字符
import re
def main():
# re.match(正则表达式,要处理的字符),有返回值则证明用户输入的数据是符合你规定的规范的
print(re.match(r"hello","hello world"))
print(re.match(r"[Hh]ello", "hello world"))
# 加上一个[]用户输入大小写均可以匹配
print(re.match(r"速度与激情1", "速度与激情1"))
print(re.match(r"速度与激情\d", "速度与激情1"))
# \d等价一个数字,可以匹配一位,可以为0-9中的任意一位
ret = re.match(r"速度与激情\d", "速度与激情1")
print(ret.group())
# 通过调用group,来看用户到底输入了什么样的数据进匹配
# ret = re.match(r"速度与激情[12345678]", "速度与激情1")
ret = re.match(r"速度与激情[1-8]", "速度与激情8")
# ret = re.match(r"速度与激情[1-36-8]", "速度与激情8")
# 直接使用\d有时会使有效范围超出,比如,速度与激情只有1-8,使用[]可以限制数据匹配范围
# 若数据连续:可以写成1-8;若数据不连续:1-36-8
print(ret.group())
ret = re.match(r"速度与激情[1-8abcde]", "速度与激情a")
print(ret.group())
ret = re.match(r"速度与激情[1-8a-z]", "速度与激情h")
print(ret.group())
ret = re.match(r"速度与激情[1-8a-zA-Z]", "速度与激情G")
# 可以匹配大小写字母,不管中括号里边写什么都只会匹配一个
print(ret.group())
ret = re.match(r"速度与激情\w", "速度与激情h")
# \w 等价于a-z;0-9;A-Z;"_",慎用,范围太广了,什么语言都支持
print(ret.group())
ret = re.match(r"速度与激情\w", "速度与激情H")
print(ret.group())
ret = re.match(r"速度与激情\w", "速度与激情8")
print(ret.group())
ret = re.match(r"速度与激情\w", "速度与激情_")
print(ret.group())
ret = re.match(r"速度与激情 \d", "速度与激情 1")
# 想让用户在名称与数字直间加一个空格,即可以在\d前边加一个空格
print(ret.group())
ret = re.match(r"速度与激情\s\d", "速度与激情 1")
print(ret.group())
ret = re.match(r"速度与激情\s\d", "速度与激情\t1")
# 要想在数字前加空格或者tab键等空白字符,可以在\d前加\s
print(ret.group())
# \D;\S;\W匹配范围与小写相反
ret = re.match(r"速度与激情.", "速度与激情1")
print(ret.group())
ret = re.match(r"速度与激情.", "速度与激情a")
print(ret.group())
ret = re.match(r"速度与激情.", "速度与激情A")
print(ret.group())
ret = re.match(r"速度与激情.", "速度与激情_")
print(ret.group())
ret = re.match(r"速度与激情.", "速度与激情哈")
print(ret.group())
ret = re.match(r"速度与激情.", "速度与激情!")
print(ret.group())
ret = re.match(r"速度与激情.", "速度与激情#")
# "."可以匹配任意值,除了"\n"以外,但只能匹配一个
print(ret.group())
if __name__ == '__main__':
main()
02_匹配多个字符
import re
def main():
ret = re.match(r"速度与激情\d{1,3}", "速度与激情1")
# 想要匹配几位,就在相应的符号后边加一个大括号,里边写上相应多少位到多少位
print(ret.group())
ret = re.match(r"速度与激情\d{1,3}", "速度与激情12")
print(ret.group())
ret = re.match(r"速度与激情\d{1,3}", "速度与激情123")
print(ret.group())
ret = re.match(r"\d{11}", "17854250704")
# 若想固定匹配位数,直接在大括号内输入相应位数,比如11位的电话号码
print(ret.group())
ret = re.match(r"021-?\d{8}","021-29660815")
# ?前边的符号可有可无,要么没有,要么有一个
print(ret.group())
ret = re.match(r"021-?\d{8}", "02112345678")
print(ret.group())
ret = re.match(r"\d{3,4}-?\d{8}", "021-12345678")
print(ret.group())
ret = re.match(r"\d{3,4}-?\d{7,8}", "0546-1234567")
print(ret.group())
html = """asdf
asdfasdf
sdfasdfasd
asdfasdf
dfasdf
"""
ret = re.match(r".*", html)
# "*"匹配前一个字符出现0次或者n次,即可有可无,由于前一个字符"."无法输出换行,所以只能输出第一行的值
print(ret.group())
ret = re.match(r".*", html,re.S)
# 要想输出其他的值,加上re.S即可
print(ret.group())
ret = re.match(r".*", "速度与激情12")
print(ret.group())
ret = re.match(r".*", "2")
print(ret.group())
ret = re.match(r".*", "")
print(ret.group())
ret = re.match(r".+", "21353516321531sdfadsfas")
# "+"匹配前一个字符出现1次或者n次,即至少一次
print(ret.group())
ret = re.match(r".+", "")
print(ret.group())
if __name__ == '__main__':
main()
03_匹配出变量名是否有效
import re
def main():
names = ["name1","_name","2_name","_name_","name!","name#23"]
for name in names:
# ret = re.match(r"[a-zA-Z_]+[\w]*",name)
# 关键是这里的正则的写法:以字母和下划线开头,用"+"来说明开头必须有一位,\w来匹配任意一个字符,
# "*"来匹配前一个字符\w的匹配次数,同时不要求全部匹配,只要求匹配符合规范的部分,从出现不符合规范的部分开始往后不再匹配
# ret = re.match(r"[a-zA-Z_]+[a-zA-Z0-9_]*",name)
# match可以判断以谁开头但是无法判断以谁结尾
ret = re.match(r"^[a-zA-Z_]+[a-zA-Z0-9_]*$",name)
# "$"匹配字符串结尾:在要求的规范的末尾写上"$"要求必须匹配到最后一位
# "^"匹配字符串开头:在要求的规范的前边写上"^"要求必须从开头开始匹配,此处之所以可以不写是因为match默认匹配开头
if ret:
print("变量名 %s 符合要求" % ret.group())
else:
print("变量名 %s 不符合要求" % name)
email = input("请输入邮箱地址:")
ret = re.match(r"[a-zA-Z0-9_][4-20]@163.com",email)
print(ret.group())
if __name__ == '__main__':
main()
04_匹配163邮箱地址
import re
def main():
email = input("请输入邮箱地址:")
ret = re.match(r"^[a-zA-Z0-9_]{4,20}@(163|126)\.com$",email)
# 若在正则表达式中需要用到某些普通的字符,比如:".","?"等,仅仅需要在它们前边加一个反斜杠进行转译,否则这些字符就是特殊字符,易引起混
# 乱
# 使用"|"表示或,匹配竖杠左右任意一个表达式,使用小括号括起来起到了限定在括号内的作用
# 小括号的作用二:(将这个作用称为分组)通过正则规范检验通过后,可以取出小括号内部数值,想把谁取出来就把谁括起来,根据从头到尾按照小括号出现的次数排序
# 将序号写在ret.group()内部
if ret:
print("%s 符合要求" % ret.group())
print(ret.group(1))
else:
print("%s 不符合要求" % email)
if __name__ == '__main__':
main()
05_分组的应用-前端网页
import re
def main():
html_str = "<h1>hahahaha</h1>"
ret = re.match(r"<\w*>.*</\w*>",html_str)
# 进行正则规范化翻译的时候,引号内必须出现的普通字符照写,特殊字符作普通字符使用时加"\"进行转译
# 假如要输入"\w",即要输入任意一个,那么有多少任意一个呢?使用"*",表示可以有多个(n个)
print(ret.group())
html_str = "<h1>hahahaha</h2>" # 输入错误,前后尖括号内不配对的时候依旧可以输出,即正则规范不完善,可以使用分组解决
ret = re.match(r"<\w*>.*</\w*>",html_str)
print(ret.group())
html_str = "<h1>hahahaha</h1>"
ret = re.match(r"<(\w*)>.*</\1>",html_str)
# 为了使前后尖括号内的值匹配,使用小括号分组的方法,将第一个尖括号内部的值放入分组中,在第二个尖括号内使用\相应的序号直接取分组中的值
print(ret.group())
html_str = "<body><h1>hahahaha</h1></body>"
ret = re.match(r"<(\w*)><(\w*)>.*</\2></\1>",html_str)
# 对于分组的问题,随着括号越来多,一个一个数十分的麻烦,有可能数错,起个变量名的话就可以解决了,很少用到
print(ret.group())
html_str = "<body><h1>hahahaha</h1></body>"
ret = re.match(r"<(?P<p1>\w*)><(?P<p2>\w*)>.*</(?P=p2)></(?P=p1)>",html_str) # 很少用到!能用\1肯定用\1,一般够用
print(ret.group())
if __name__ == '__main__':
main()
06_re模块的高级用法-search,sub等
import re
def main():
"""search"""
ret = re.search(r"\d+","阅读次数为:9999") # "+"必须有一位数字,从哪里开始无所谓
# 与match的不同在于search不会从头开始匹配,直接按照正则的要求去找
print(ret.group())
"""findall"""
ret = re.findall(r"\d+","阅读次数为:9999,点赞量:1212") # "+"必须有一位数字,从哪里开始无所谓
# 与search的不同在于findall可以匹配所有数值,直接输出一个列表,不用调用group函数
print(ret)
"""sub"""
ret = re.sub(r"\d+","110,112","阅读次数为:9999,点赞量:564")
# sub可以实现替换,也可以被函数调用
# 格式:先是正则规范,中间是要替换的值,最后边是要进行匹配的字符串.
# 过程:先利用正则进行匹配,匹配到相应数据后进行替换
print(ret)
def add(temp):
strNum = temp.group()
num = int(strNum) + 1
return str(num)
ret = re.sub(r"\d+",add,"阅读次数为:9999")
# sub可被函数调用,python独有
# 过程:先利用正则进行匹配,,匹配到相应的数据后,调用中间函数,将匹配到的数据传入函数中,
# 调用group获取返回值,将返回值变为整数类型进行计算,再变为字符串输出
print(ret)
"""split"""
# 根据正则规范进行切割字符串,并返回一个列表
ret = re.split(r":| ","info:xiaoming 33 shanghai")
print(ret)
if __name__ == '__main__':
main()
07_网络数据清洗
import re
def main():
test_str = "<div class=> 职位职责: <br>1、负责公司内部自动化安全平台和部分产品的" \
"开发; <br>2、负责对网络攻击或安全事件进行溯源分析;<br>3、跟踪安全" \
"漏洞和安全事件,负责开发exp和poc脚本;<br>4、负责最新的安全漏洞研究分析," \
"辅助文档人员完成漏洞分析报告;<br>5、负责对产品代码进行安全审计分析;<br>" \
"职位要求: <br>1、熟悉信息安全领域的技术,有相关工作经验; " \
"<br>2、熟练掌握python,了解 分布式开发 框架<br>3、熟练使用各种数据库软" \
"件及linux操作系统," \
"Elasticsearch优先;<br>4、有WAF,扫描器,入侵检测,SOC等安全产品开" \
"发经验优先<br>5、专业基础扎实,熟练掌握常见 web、系统安全漏洞原理和解" \
"决方案<br>6、具有良好的语言表达能力、文档组织能力;</div>"
# ""内部的内容,不能直接换行,格式如上
ret = re.sub(r"<[^>]*>| |\n", "", test_str)
# 正则规范表达式:首先匹配<>,在<>内部开头处开始匹配">",只要不是">",有多少个都可以
# 遇">"即不再进行匹配,匹配成功后将<>及其内部内容进行替换
# []表示从哪里到哪里停,eg:<[^>]:从"<"开始,到">"停(从"<"后边的头开始)
print(ret)
if __name__ == '__main__':
main()
2_http协议
01_http协议的含义
在Web应用中,服务器把网页传给浏览器,实际上就是把网页的HTML代码发送给浏览器,让浏览器显示出来。
而浏览器和服务器之间的传输协议是HTTP,所以:
HTML是一种用来定义网页的文本,会HTML,就可以编写网页;
HTTP是在网络上传输HTML的协议,用于浏览器和服务器的通信。
什么是http协议(超文本传输协议)?
答:是一种用来规定浏览器与服务器之间传输的一种约定的协议,请求的时候浏览器按照....样的格式发送;回复的时候服务器按照....样的格式发送
都是基于TCP发送的字符串
02_http的request和response
浏览器---->服务器发送的请求格式如下:
"""
在请求的内部,下边的代码叫做:request header,按理说也应该有response body,但是一般来讲
如果浏览器请求的方式是GET,一般都不会有body,如果浏览器的请求是一个要提交的数据就会有body
"""
GET / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
# 带body的请求
POST / HTTP/1.1
Host: 127.0.0.1:8080
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: zh-CN,zh;q=0.8
user_name:yangge
password:123456789
服务器--->浏览器回送的数据格式如下:
HTTP/1.1 200 OK
# 浏览器使用的协议是1.1
Bdpagetype: 1
Bdqid: 0xe87cb3f700023783
Bduserid: 0
Cache-Control: private
Connection: Keep-Alive
Content-Encoding: gzip
Content-Type: text/html; charset=utf-8
# 若服务器返回给浏览器的数据里含有中文,返回给浏览器之后会显示乱码.因为你的编码格式是utf-8
# 但是浏览器不见得使用utf-8去解码,可能按照jpk解,如何告诉浏览器按照我要求的格式进行解压呢:
# 将上边这句话加上,浏览器就自动知道要按照要求的utf-8的格式进行解码body
Cxy_all: baidu+55617f8533383cbe48d5d2b7dc84b7f0
Date: Fri, 20 Oct 2017 00:59:55 GMT
Expires: Fri, 20 Oct 2017 00:59:11 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=0; path=/
Set-Cookie: H_PS_PSSID=1463_21080_17001_20929; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Vary: Accept-Encoding
X-Powered-By: HPHP
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
<h1>haha</h1>
协议的作用:规定浏览器和服务器之间传输数据的一种规定格式
应答部分:
在header和body之间有一个空行,空行之后浏览器将视为body
response ----header:HTTP/1.1 200 OK
response ----body:
3_简单的web服务器的实现
01_实现简单的http服务器
import socket
def sevice_client(new_socket):
"""为这个客户端 返回数据"""
# ****.recv() # 为什么要先收?服务器发过来的请求如果连收都不收,又怎么知道回什么
# ****不知道是什么,但知道是accept()返回的套接字,又因为该套接字不是全局变量,故需要传递参数
# 1.接收浏览器发送过来的请求,即http请求
# 具体接收格式如下:
# GET / HTTP/1.1
# ......
recv_request = new_socket.recv(1024)
print(recv_request)
# 2.返回http格式的数据,给浏览器
# 2.1准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n" # 在程序中"\r\n"才表示换行,是windows的独特要求,是为了兼容windows
response += "\r\n"
# 2.2准备发送给浏览器的数据---body
response += "<h1>hahahahaha</h1>" # "+="是为了实现三部分相拼
new_socket.send(response.encode("utf-8")) # 浏览器通常使用字节类型,所以要进行编码
new_socket.close()
# 客户端先调用close,需要等待两分钟.目的是等待服务器回复,若没有回复依旧在两分钟后释放
# (这也说明了为什么服务器为什么要绑定端口而客户端不绑定端口,就是为了,
# 为新的客户端提供新的端口,防止等待堵塞),若服务器先关闭,该服务器端口会被占用,无法运行
def main():
"""用来完成整体的控制"""
"""使用注释来搭建框架"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2.绑定
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动套接字)
tcp_sever_socket.listen(128)
while True:
# 4.等待新客户端的链接
new_socket,client_addr = tcp_sever_socket.accept()
# 5.为这个客户端服务
"""服务的话应该是一个复杂的过程,故可以单独定义一个函数"""
sevice_client(new_socket)
# 调用一个函数的时候要下意识的想一下是否需要传递参数
# 关闭监听套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
02_设定套接字选项
import socket
def sevice_client(new_socket):
"""为这个客户端 返回数据"""
# ****.recv() # 为什么要先收?服务器发过来的请求如果连收都不收,又怎么知道回什么
# ****不知道是什么,但知道是accept()返回的套接字,又因为该套接字不是全局变量,故需要传递参数
# 1.接收浏览器发送过来的请求,即http请求
# 具体接收格式如下:
# GET / HTTP/1.1
# ......
recv_request = new_socket.recv(1024)
# 2.返回http格式的数据,给浏览器
# 2.1准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n" # 在程序中"\r\n"才表示换行,是windows的独特要求,是为了兼容windows
response += "\r\n"
# 2.2准备发送给浏览器的数据---body
response += "<h1>hahahahaha</h1>" # "+="是为了实现三部分相拼
new_socket.send(response.encode("utf-8")) # 浏览器通常使用字节类型,所以要进行编码
new_socket.close()
# 客户端先调用close,需要等待两份钟.(这也说明了为什么服务器为什么要绑定端口而客户端不绑定端口,就是为了,为新的客户端提供新的端口,防止等待堵塞),若服务器先关闭,该服务器端口会被占用,无法运行
def main():
"""用来完成整体的控制"""
"""使用注释来搭建框架"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 设置当服务器先close,即服务器四次挥手后资源能够立即释放,这也就保证了下次运行时可以立即执行
# 2.绑定
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动套接字)
tcp_sever_socket.listen(128)
while True:
# 4.等待新客户端的链接
new_socket,client_addr = tcp_sever_socket.accept()
# 5.为这个客户端服务
"""服务的话应该是一个复杂的过程,故可以单独定义一个函数"""
sevice_client(new_socket)
# 调用一个函数的时候要下意识的想一下是否需要传递参数
# 关闭监听套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
03_返回index.html页面
import socket
def sevice_client(new_socket):
"""为这个客户端 返回数据"""
# ****.recv() # 为什么要先收?服务器发过来的请求如果连收都不收,又怎么知道回什么
# ****不知道是什么,但知道是accept()返回的套接字,又因为该套接字不是全局变量,故需要传递参数
# 1.接收浏览器发送过来的请求,即http请求
# 具体接收格式如下:
# GET / HTTP/1.1
# ......
recv_request = new_socket.recv(1024)
print("*"*50)
print(recv_request)
"""不管用户请求的是什么,准备好header,打开一个页面,读出里边的内容放在html_content,先把header发过去,再把内容html_content发过去"""
# 2.返回http格式的数据,给浏览器
# 2.1准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n" # 在程序中"\r\n"才表示换行,是windows的独特要求,是为了兼容windows
response += "\r\n"
# 2.2准备发送给浏览器的数据---body
# response += "<h1>hahahahaha</h1>" # "+="是为了实现三部分相拼
f = open("./html/test.html","rb") # 此处发送的是固定的内容,但是只要保证用户要什么发送什么就可以
# 路径信息:打开当前路径下的html路径,打开html路径下的index文件.以二进制只读的方式打开,不用进行解码了
html_content = f.read() # 将文件中的内容读出来
f.close()
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8")) # 浏览器通常使用字节类型,所以要进行编码
# 将response body发送给浏览器
new_socket.send(html_content)
# 此处不能使用response += html_content的方式进行链接发送,因为header部分为字符串,body部分为字节类型
new_socket.close()
# 客户端先调用close,需要等待两份钟.(这也说明了为什么服务器为什么要绑定端口而客户端不绑定端口,就是为了,为新的客户端提供新的端口,防止等待堵塞),若服务器先关闭,该服务器端口会被占用,无法运行
def main():
"""将body的内容换成用户需要的页面信息(此处发送一个html文件)"""
"""用来完成整体的控制"""
"""使用注释来搭建框架"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 设置当服务器先close,即服务器四次挥手后资源能够立即释放,这也就保证了下次运行时可以立即执行
# 2.绑定
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动套接字)
tcp_sever_socket.listen(128)
while True:
# 4.等待新客户端的链接
new_socket,client_addr = tcp_sever_socket.accept()
# 5.为这个客户端服务
"""服务的话应该是一个复杂的过程,故可以单独定义一个函数"""
sevice_client(new_socket)
# 调用一个函数的时候要下意识的想一下是否需要传递参数
# 关闭监听套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
04_根据用户的需求返回相应的页面
import socket
import re
"""
写程序时有一点小问题没关系,以后再解,尽快的要先看到整体的效果,看到整体的效果后立马回头处理细节/bug
这样做的好处:
当一个任务比较麻烦比较费劲时,不管怎样,要尽快的看到一个最简单的效果,树立强大的自信心,就会觉得这个事能干
剩下的就是解bug了,然而,若是在一个细节的地方花费了太多的时间精力,就会丢掉最原始的方向,往往就不会做了
"""
def send_data_to_client(new_socket):
"""为这个客户端 返回数据"""
# ****.recv() # 为什么要先收?服务器发过来的请求如果连收都不收,又怎么知道回什么
# ****不知道是什么,但知道是accept()返回的套接字,又因为该套接字不是全局变量,故需要传递参数
# 1.接收浏览器发送过来的请求,即http请求
# 具体接收格式如下:
# GET / HTTP/1.1
# ......
# 1.1对请求进行解码
recv_request = new_socket.recv(1024).decode("utf-8") # 先解码,将发来的要求解成字符串的格式
# print("*"*50)
# print(recv_request)
# 1.2将接收到的请求进行按行切割,返回一个列表(切割使之形成一个列表)
request_lines_list = recv_request.splitlines()
print("")
print(">"*20)
print(request_lines_list)
# 1.3匹配出(找到)请求中要求的目标
# 请求到的格式应为:GET / HTTP/1.1
# 通常的请求方式有:GET,POST,PUT,DEL....故匹配时不能直接写GET
ret = re.match(r"[^/]+(/[^ ]*)",request_lines_list[0])
# "[^/]+":这一部分表示匹配从头到"/"什么都行,"+"表示任何值均可,但必须有一位
# "(/[^ ]*)"这一部分表示要取出括号内部的内容,"*"表示任意值均可,也可以没有
file_name = "" # 若if不成立,则没有file_name这个变量,所以提前定义这个变量为一个空字符串,防止异常
if ret:
file_name = ret.group(1)
# print("*"*50,file_name)
if file_name == "/": # 当请求的只有一个"/"没有输入一个页面时,也可以浏览网页内容
file_name = "/index.html"
"""不管用户请求的是什么,准备好header,打开一个页面,读出里边的内容放在html_content,先把header发过去,再把内容html_content发过去"""
# 2.返回http格式的数据,给浏览器
# response += "<h1>hahahahaha</h1>" # "+="是为了实现三部分相拼
try:
# 打开一个文件是一个很危险的事情,存在才能打开,若不存在,可能直接导致程序崩溃,故要处理异常(try except)
f = open("./html"+file_name,"rb") # 此处发送的是固定的内容,但是只要保证用户要什么发送什么就可以
# 一个路径下的文件名:浏览器请求的文件名称是什么不用关心,把该文件名提出来之后对应到某个路径下的真正的那个文件,打开
# 路径信息:打开当前路径下的html路径,打开html路径下的index文件.以二进制只读的方式打开,不用进行解码了
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "-----file not find-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read() # 将文件中的内容读出来
f.close()
# 2.返回http格式的数据,给浏览器
# 2.1准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n" # 在程序中"\r\n"才表示换行,是windows的独特要求,是为了兼容windows
response += "\r\n"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8")) # 浏览器通常使用字节类型,所以要进行编码
# 2.2准备发送给浏览器的数据---body
# 将response body发送给浏览器
new_socket.send(html_content)
# 此处不能使用response += html_content的方式进行链接发送,因为header部分为字符串,body部分为字节类型
new_socket.close()
# 客户端先调用close,需要等待两份钟.(这也说明了为什么服务器为什么要绑定端口而客户端不绑定端口,就是为了,为新的客户端提供新的端口 ,防止等待堵塞),若服务器先关闭,该服务器端口会被占用,无法运行
def main():
"""将body的内容换成用户需要的页面信息(此处发送一个html文件)"""
"""用来完成整体的控制"""
"""使用注释来搭建框架"""
"""基于TCP"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 设置当服务器先close,即服务器四次挥手后资源能够立即释放,这也就保证了下次运行时可以立即执行
# 2.绑定
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动套接字)
tcp_sever_socket.listen(128)
while True:
# 4.等待新客户端的链接
new_socket,client_addr = tcp_sever_socket.accept()
# 5.为这个客户端服务
"""服务的话应该是一个复杂的过程,故可以单独定义一个函数"""
send_data_to_client(new_socket)
# 调用一个函数的时候要下意识的想一下是否需要传递参数
# 6.关闭监听套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
05_使用多进程完成http服务器
import socket
import re
import multiprocessing
"""使用多进程实现多任务"""
def send_data_to_client(new_client_socket):
"""为这个客户端返回数据"""
# 1.接收浏览器发来的请求,即http请求
# GET / HTTP/1.1
# ......
recv_request_content = new_client_socket.recv(1024).decode("utf-8")
# 1.1将接收到的请求进行切割,使之形成一个列表
request_lines_list = recv_request_content.splitlines()
print("\r\n")
print("*"*50)
print(request_lines_list)
# 1.2匹配出(找出)浏览器请求的相应文件名
# GET /index.html HTTP/1.1
ret = re.match(r"[^/]+([^ ]*)", request_lines_list[0])
file_name = ""
if ret:
file_name = ret.group(1)
print(">"*50,file_name)
if file_name == "/":
file_name = "/index.html"
# 2.回复浏览器的请求:返回http格式的数据,给浏览器
try:
f = open("./html"+file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "----not find file----"
new_client_socket.send(response.encode("utf-8"))
else:
# 2.1准备发送给浏览器的数据----header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2准备发送给浏览器的数据----body
file_content = f.read()
f.close()
# 将reponse header发送给浏览器
new_client_socket.send(response.encode("utf-8"))
# 将reponse body发送给浏览器
new_client_socket.send(file_content)
# 3.关闭套接字
new_client_socket.close()
def main():
"""用来完成整体的控制"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定本地服务器信息
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动)套接字
tcp_sever_socket.listen(128)
while True:
# 4.接收客户端信息:等待新客户端的链接
new_client_socket,client_address = tcp_sever_socket.accept()
# 卡在这里,等待一个用户的链接,为该用户服务,服务结束后,再为下一个用户服务,只能一个一个的执行
# 为了提高运行效率,可以这样做:每接收到一个请求干脆开一个进程,让这个进程里边的线程去为它服务
#(进程是资源分配的单位,真正在做事情的是子进程里边的主线程)
# 只要创建一个进程,让里边自带一个线程即可
# accept()依旧是一个,让它来接收,将来分配任务的时候让其他人去做
# 5.为这个客户端服务:传递数据给客户端
p = multiprocessing.Process(target=send_data_to_client,args=(new_client_socket,))
# 单独调用函数的时候要传递参数,现在是开一个进程,让进程去做这个事情,已经指定里进程去哪里执行,故执行的时候要把参数扔进去
p.start()
new_client_socket.close()
# 为什么在函数中(子进程中)关闭了套接字,在这里(主进程)还要关闭一次才行呢?
"""
用Process创建一个子进程的时候,子进程会复制主进程的资源,能共用就共用,不能共用就复制一份,比如代码就可以共用,叫做写实拷贝
调用Process时,所有的变量(局部,全局)都会复制一份,两者标记了同一个客户端(指向底层的同一个fd(file descriptor)文件故当子
进程调用close()时并不会关闭底层的fd文件,因为主进程中还有指向它的对象,所以要在主进程中再调用一次close(),此时四次挥手才开始
启动
在linux里边,一切设备皆文件(.py文件,压缩包,键盘,鼠标,显示屏,打印机,耳机...),把一切都抽象成文件
同理,标记客户端的套接字说到底,也是一个文件
"""
# 6.关闭套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
06_使用多线程实现http服务器
import socket
import re
import threading
"""使用线进程实现多任务"""
def send_data_to_client(new_client_socket):
"""为这个客户端返回数据"""
# 1.接收浏览器发来的请求,即http请求
# GET / HTTP/1.1
# ......
recv_request_content = new_client_socket.recv(1024).decode("utf-8")
# 1.1将接收到的请求进行切割,使之形成一个列表
request_lines_list = recv_request_content.splitlines()
print("\r\n")
print("*"*50)
print(request_lines_list)
# 1.2匹配出(找出)浏览器请求的相应文件名
# GET /index.html HTTP/1.1
ret = re.match(r"[^/]+([^ ]*)", request_lines_list[0])
file_name = ""
if ret:
file_name = ret.group(1)
print(">"*50,file_name)
if file_name == "/":
file_name = "/index.html"
# 2.回复浏览器的请求:返回http格式的数据,给浏览器
try:
f = open("./html"+file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "----not find file----"
new_client_socket.send(response.encode("utf-8"))
else:
# 2.1准备发送给浏览器的数据----header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2准备发送给浏览器的数据----body
file_content = f.read()
f.close()
# 将reponse header发送给浏览器
new_client_socket.send(response.encode("utf-8"))
# 将reponse body发送给浏览器
new_client_socket.send(file_content)
# 3.关闭套接字
new_client_socket.close()
def main():
"""用来完成整体的控制"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定本地服务器信息
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动)套接字
tcp_sever_socket.listen(128)
while True:
# 4.接收客户端信息:等待新客户端的链接
new_client_socket,client_address = tcp_sever_socket.accept()
# 卡在这里,等待一个用户的链接,为该用户服务,服务结束后,再为下一个用户服务,只能一个一个的执行
# 为了提高运行效率,可以这样做:每接收到一个请求干脆开一个进程,让这个进程里边的线程去为它服务
#(进程是资源分配的单位,真正在做事情的是子进程里边的主线程)
# 只要创建一个进程,让里边自带一个线程即可
# accept()依旧是一个,让它来接收,将来分配任务的时候让其他人去做
# 5.为这个客户端服务:传递数据给客户端
t = threading.Thread(target=send_data_to_client,args=(new_client_socket,))
# 单独调用函数的时候要传递参数,现在是开一个进程,让进程去做这个事情,已经指定里进程去哪里执行,故执行的时候要把参数扔进去
t.start()
# new_client_socket.close()
# 这里不需要再次调用close,因为多进程要复制,多线程是共用,要是在主线程里边关了,子线程就挂了
# 三次握手的开始是connect发起,成功是服务器调用accept
# 6.关闭套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
07_使用协程gevent实现http服务器
import socket
import re
import gevent
from gevent import monkey
"""使用协程gevent实现多任务"""
monkey.patch_all()
def send_data_to_client(new_client_socket):
"""为这个客户端返回数据"""
# 1.接收浏览器发来的请求,即http请求
# GET / HTTP/1.1
# ......
recv_request_content = new_client_socket.recv(1024).decode("utf-8")
# 1.1将接收到的请求进行切割,使之形成一个列表
request_lines_list = recv_request_content.splitlines()
print("\r\n")
print("*"*50)
print(request_lines_list)
# 1.2匹配出(找出)浏览器请求的相应文件名
# GET /index.html HTTP/1.1
ret = re.match(r"[^/]+([^ ]*)", request_lines_list[0])
file_name = ""
if ret:
file_name = ret.group(1)
print(">"*50,file_name)
if file_name == "/":
file_name = "/index.html"
# 2.回复浏览器的请求:返回http格式的数据,给浏览器
try:
f = open("./html"+file_name, "rb")
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "----not find file----"
new_client_socket.send(response.encode("utf-8"))
else:
# 2.1准备发送给浏览器的数据----header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2准备发送给浏览器的数据----body
file_content = f.read()
f.close()
# 将reponse header发送给浏览器
new_client_socket.send(response.encode("utf-8"))
# 将reponse body发送给浏览器
new_client_socket.send(file_content)
# 3.关闭套接字
new_client_socket.close()
def main():
"""用来完成整体的控制"""
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2.绑定本地服务器信息
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动)套接字
tcp_sever_socket.listen(128)
while True:
# 4.接收客户端信息:等待新客户端的链接
new_client_socket,client_address = tcp_sever_socket.accept()
# 卡在这里,等待一个用户的链接,为该用户服务,服务结束后,再为下一个用户服务,只能一个一个的执行
# 为了提高运行效率,可以这样做:每接收到一个请求干脆开一个进程,让这个进程里边的线程去为它服务
#(进程是资源分配的单位,真正在做事情的是子进程里边的主线程)
# 只要创建一个进程,让里边自带一个线程即可
# accept()依旧是一个,让它来接收,将来分配任务的时候让其他人去做
# 5.为这个客户端服务:传递数据给客户端
gevent.spawn(send_data_to_client,new_client_socket)
#现在看不出来进程协程线程三者的区别将来真正的进行压力测试(模拟大量的客户端的请求)的时候就能看出来了
# new_client_socket.close()
# 此处也不用再次调用close(),因为线程是共享的协程肯定也是共享的
# 6.关闭套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
08_单进程 单线程 非阻塞实现监听多个套接字(实现并发)的原理
"""
之前使用的gevent其实他就是用了一个单进程,一个单线程,里边有多个函数相当于一起跑(实则一个一个执行),
最终来实现多任务,但是里边必须要有monkey.patch_all(),否则所有的阻塞的位置,通通都要换成gevent的
东西,十分的麻烦.
剖析一下gevent的一个实现过程:
一个进程里边,一个线程里边,竟然还可以为多个套接字服务,原理是如何实现的呢?
用的是协程的话,那么也就意味着很多函数里边可能都会出现recv(),那么一个recv()只要一调,那么整个线程
就堵塞,当整个线程都堵塞的时候它是怎么实现再切到别的函数中去调用recv()的呢?
之所以用多进程的一个目的是什么?
想同一时刻为多个人一起服务,而只要有一个recv()调用了,而此时数据却偏偏没有到来,这时候整个进程就全部
卡了,别人就服务不了了,才用的多进程(一个进程卡了,不会影响其他的进程)
"""
import socket
# 单进程单线程实现多任务的方案
tcp_server_socket = socket.socket(...) # 假设此处创建了tcp_server_socket
tcp_server_socket.setblocking(False) # 设置套接字为非堵塞的方式
# 接下来再去调用accept()的时候,若是新的客户端还没来,堵塞的时候会等待,由于设置成了不堵塞,
# 马上要返回一个值,要是没有返回一个合适的值,接下来处理的时候,必挂无疑,所以宁愿立马会产生异
# 常,也不返回值,同时为了程序不挂,要加上异常处理
client_socket_list = list() # 假设先有一个列表,先定义一个列表,用来存储产生的所有的客户端
while True:
"""因为要一直服务下去,所以要循环下去"""
try:
new_socket,new_addr = tcp_server_socket.accept() # 等待客户端到来,在没有用户传过来的时候就一直卡在这里
# 为了能够实现为这个客户端在服务的时候不会影响到我不能接收的其他用户,所以说,
# 我得到的这个新的套接字的时候,立马创建一个进程,让这个进程里边去做这个事情,
# 那么这个进程里边到底做什么呢?
# 客户端调用recv() send()进行收发数据
except Exception as ret:
print("----没有新的客户端到来----")
else:
print("----只要没有产生异常,那么也就意味着有一个新的客户端到来----")
new_socket.setblocking(False) # 设置套接字为非堵塞的方式,只要设置成非堵塞就一直是非堵塞
client_socket_list.append(new_socket)
# 将产生的客户端套接字的引用全部存储到列表中
for client_socket in client_socket_list:
# 遍历列表中所有的客户端套接字看是否接收到数据,若没来,再从while True开始执行,
# 若上边没有新的客户端到来,产生异常,对此处for循环无影响,继续将之前的套接字取出来
# 看看有没有数据,有我就收,继续向下操作,没有就异常
# 创建新的套接字继续存储进去(不影响接收新的用户),再执行到这里的时候,在遍历一遍
# 列表,看有无套接字收到了数据
try:
recv_data = client_socket.recv() # 要是不设置成非堵塞,程序执行到这里的时候立马就又堵了
# 设置成非堵塞后,有数据到来是一切正常,没有数据到来的时候就又会产生异常,需要处理异常
# 对方调用close()关闭了,返回值为空
except Exception as ret:
print("----这个客户端没有发送过来数据----")
else:
if recv_data:
# 对方发送过来数据
print("----客户端发送过来了数据----")
else:
# 对方调用close(),不发送数据了,导致recv返回
client_socket_list.remove(client_socket)
client_socket.close() # 对方关闭后我们也要关闭,并将不用的套接字从列表里边去掉
new_socket.recv()
# 进程运行到这,当数据还没有到来的时候,进程进卡在这里了
# 为了能够使得我在接收到请求之后,接下来调用recv()的时候,在等待资源的期间不会导致别人链接不上我
# 要想方设法再从while True开始执行
new_socket.send()
09_单进程 单线程 非阻塞实现并发的原理验证
import socket
import time
def main():
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 假设此处创建了tcp_server_socket
tcp_server_socket.bind(("", 7892))
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 设置套接字为非堵塞的方式
client_socket_list = list()
while True:
"""因为要一直服务下去,所以要循环下去"""
time.sleep(1)
try:
new_socket,new_addr = tcp_server_socket.accept() # 等待客户端到来,在没有用户传过来的时候就一直卡在这里
except Exception as ret:
print("----没有新的客户端到来----")
else:
print("----只要没有产生异常,那么也就意味着有一个新的客户端到来----")
new_socket.setblocking(False) # 设置套接字为非堵塞的方式,只要设置成非堵塞就一直是非堵塞
client_socket_list.append(new_socket)
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024) # 要是不设置成非堵塞,程序执行到这里的时候立马就又堵了
# 出现bug的时候要学会锁定范围,缩到几行,然后一行一行的检验,排除bug
except Exception as ret:
print("----这个客户端没有发送过来数据----")
else:
if recv_data:
# 对方发送过来数据
print(recv_data.decode("utf-8"))
else:
# 对方调用close(),不发送数据了,导致recv返回
client_socket_list.remove(client_socket)
client_socket.close() # 对方关闭后我们也要关闭,并将不用的套接字从列表里边去掉
print("----客户端已经关闭----")
if __name__ == '__main__':
main()
10_单进程 单线程 非阻塞 长链接
import socket
import re
def sevice_client(new_socket,recv_request): # 函数都写好了,函数里用的什么变量,形参就用什么,省事
"""为这个客户端 返回数据"""
# 1.接收浏览器发送过来的请求,即http请求
# 具体接收格式如下:
# GET / HTTP/1.1
# ......
# 1.1对请求进行解码
# recv_request = new_socket.recv(1024).decode("utf-8") # 先解码,将发来的要求解成字符串的格式
# 1.2将接收到的请求进行按行切割,返回一个列表(切割使之形成一个列表)
request_lines = recv_request.splitlines()
print("")
print(">"*20)
print(request_lines)
# 1.3匹配出(找到)请求中要求的目标
# 请求到的格式应为:GET / HTTP/1.1
# 通常的请求方式有:GET,POST,PUT,DEL....故匹配时不能直接写GET
ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
# print("*"*50,file_name)
if file_name == "/": # 当请求的只有一个"/"没有输入一个页面时,也可以浏览网页内容
file_name = "/index.html"
# 2.返回http格式的数据,给浏览器
try:
f = open("./html"+file_name,"rb") # 此处发送的是固定的内容,但是只要保证用户要什么发送什么就可以
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "-----file not find-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read() # 将文件中的内容读出来
f.close()
response_body = html_content # 就等于从文件中读取的数据
# 将body放在前边是因为len()中要使用body,若放在后边,使用的时候还没定义会出错
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
# header是字符串,body是二进制,故header要编译成二进制
new_socket.send(response)
new_socket.send(html_content)
# new_socket.close()
# 这里不强制断开的话,浏览器什么时候数据传输结束,会一直接收数据
def main():
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 设置当服务器先close,即服务器四次挥手后资源能够立即释放,这也就保证了下次运行时可以立即执行
# 2.绑定
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动套接字)
tcp_sever_socket.listen(128)
tcp_sever_socket.setblocking(False) # 将套接字变为非堵塞
client_socket_list = list()
while True:
try:
# 4.等待新客户端的链接
new_socket,client_addr = tcp_sever_socket.accept()
except Exception as ret:
pass
else:
new_socket.setblocking(False)
client_socket_list.append(new_socket)
# 由于else属于处理是否有新客户端出现的异常的,故接下来的代码不能接在后边写
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024).decode("utf-8")
except Exception as ret:
pass
else:
if recv_data:
sevice_client(client_socket,recv_data)
else:
client_socket_list.remove(client_socket)
client_socket.close()
# 5.为这个客户端服务
sevice_client(client_socket)
# 调用一个函数的时候要下意识的想一下是否需要传递参数
# 6.关闭监听套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()
11_epoll实现http服务器
import socket
import re
import select
"""
epoll的核心点:原来是应用程序去检测,哪一个套接字可以收,每次去检测的时候都需要告诉操作系统,所以说有一个拷贝的过程,列表越大,拷贝的次数越多,效率相当慢,为了提高效率,于是建立了一个内存空间,将要检测的套接字注册到里边去(即将套接字的文件标识符放在里边),通过epoll有个特点:把应用程序每次都要去遍历列表的过程交给操作系统去做,操作系统的级别高,故其效率也很高(时间通知(麻烦)的效率比轮询(简单)快的多)
做服务器的执行效率(由慢到快):多进程-多线程-协程-单进程 单线程 非阻塞-epoll
1.首先要建立一个特殊的共享内存:导入select模块,调用select模块中的 epoll() 创建一个对象,这个对象就指定了是那个内存(对应着(指向)
那个共享内存)
2.将监听套接字对应的文件描述符(fd)放入共享内存中
"""
def sevice_client(new_socket,recv_request): # 函数都写好了,函数里用的什么变量,形参就用什么,省事
"""为这个客户端 返回数据"""
# 1.接收浏览器发送过来的请求,即http请求
# 具体接收格式如下:
# GET / HTTP/1.1
# ......
# 1.1对请求进行解码
# recv_request = new_socket.recv(1024).decode("utf-8") # 先解码,将发来的要求解成字符串的格式
# 1.2将接收到的请求进行按行切割,返回一个列表(切割使之形成一个列表)
request_lines = recv_request.splitlines()
print("")
print(">"*20)
print(request_lines)
# 1.3匹配出(找到)请求中要求的目标
# 请求到的格式应为:GET / HTTP/1.1
# 通常的请求方式有:GET,POST,PUT,DEL....故匹配时不能直接写GET
ret = re.match(r"[^/]+(/[^ ]*)",request_lines[0])
file_name = ""
if ret:
file_name = ret.group(1)
# print("*"*50,file_name)
if file_name == "/": # 当请求的只有一个"/"没有输入一个页面时,也可以浏览网页内容
file_name = "/index.html"
# 2.返回http格式的数据,给浏览器
try:
f = open("./html"+file_name,"rb") # 此处发送的是固定的内容,但是只要保证用户要什么发送什么就可以
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "-----file not find-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read() # 将文件中的内容读出来
f.close()
response_body = html_content # 就等于从文件中读取的数据
# 将body放在前边是因为len()中要使用body,若放在后边,使用的时候还没定义会出错
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
# header是字符串,body是二进制,故header要编译成二进制
new_socket.send(response)
new_socket.send(html_content)
# new_socket.close()
# 这里不强制断开的话,浏览器什么时候数据传输结束,会一直接收数据
def main():
# 1.创建套接字
tcp_sever_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_sever_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 设置当服务器先close,即服务器四次挥手后资源能够立即释放,这也就保证了下次运行时可以立即执行
# 2.绑定
tcp_sever_socket.bind(("",7890))
# 3.变为监听(被动套接字)
tcp_sever_socket.listen(128)
tcp_sever_socket.setblocking(False) # 将套接字变为非堵塞
# 创建一个epoll对象,系统要为对象分配内存,作为共享内存
epl = select.epoll()
# 定义一个变量epl来指向那个共享内存
# 将监听套接字对应的fd注册到epoll中(这个元组就标记这个套接字)
epl.register(tcp_sever_socket.fileno(),select.EPOLLIN)
# fileno()给tcp_sever_socket定义文件描述符,使tcp_sever_socket的fd等于一个数字
fd_event_dict = dict() # 定义了一个字典
while True:
fd_event_list = epl.poll() # 默认会堵塞,直到os(操作系统)检测到数据到来.通过事件通杂方式,告诉这个程序,此时才会解堵塞
# 返回值是一个列表,由fd和event组成[(fd,event)] 列表里边套着元组,因为要存入多个对象,每个包含两部分 两部分:标记谁,标记可以收还是可以发
# [套接字对应的文件描述符,这个文件描述符到底是一个什么事件(例如:可以调用recv()接收等)]
for fd,event in fd_event_list: # 取出来的是一个元组,直接拆包成两个变量
# 等待新客户端的链接
if fd == tcp_sever_socket.fileno(): # 只有这个监听套接字可以调用accept(),其他的调用recv()
new_socket,client_addr = tcp_sever_socket.accept()
# 接收成功之后,就产生了一个新的套接字,要想对这个套接字进行检测,就必须将其注册到共享内存中
epl.register(new_socket.fileno(),select.EPOLLIN) # select.EPOLLIN:检测是否有数据到来(回到epl.poll()继续堵塞等待)
# 若浏览器链接以后均没有反应,又有新的客户端到来,列表中的tcp_sever_socket可以接收,运行到accept再次产生一个新的套接字
# 若浏览器产生反应,程序解堵塞,调用recv()
fd_event_dict[new_socket.fileno()] = new_socket # 向字典中添加一组key:value key = new_socket.fileno() value = new_socket
elif event == select.EPOLLIN:
# 判断已经链接的客户端是否有数据发送过来,
# 接下来要使用套接字调用recv()但是此时在for循环的过程中没有套接字,返回的只有fd/event
# 从文件描述符获取对象(套接字)的方法:使用字典
recv_data = fd_event_dict[fd].recv(1024).decode("utf-8") # 根据key(new_socket.fileno()所标记的数字)值,取出相应的套接字
if recv_data:
# 5.为这个客户端服务
sevice_client(fd_event_dict[fd],recv_data)
else:
fd_event_dict[fd].close()
epl.unregister(fd) # 删除共享内存中的对应数据,注册用:register,注销用:unregister
del fd_event_dict[fd] # 将字典中的相应数据移除
# 6.关闭监听套接字
tcp_sever_socket.close()
if __name__ == '__main__':
main()