目录
1.http协议(了解)
HTTP(Hyper Text Transfer Protocol)<超文本传输协议>。
1.1. HTTP请求
1). 浏览器首先向服务器发送HTTP请求,请求包括:
方法:GET还是POST,GET仅请求资源,POST会附带用户数据;
路径:/full/url/path;
域名:由Host头指定:Host: www.sina.com
以及其他相关的Header;
如果是POST,那么请求还包括一个Body,包含用户数据
2). 服务器向浏览器返回HTTP响应,响应包括:
响应代码:200表示成功,3xx表示重定向,4xx表示客户端发送的请求有错误,5xx表示服务器端处理时发生了错误;
响应类型:由Content-Type指定;
以及其他相关的Header;
通常服务器的HTTP响应会携带内容,也就是有一个Body,包含响应的内容,网页的HTML源码就在Body中。
3). 如果浏览器还需要继续向服务器请求其他资源,比如图片,就再次发出HTTP请求,重复步骤1、2。
Web采用的HTTP协议采用了非常简单的请求-响应模式,从而大大简化了开发。当我们编写一个页面时,我们只需要在HTTP请求中把HTML发送出去,不需要考虑如何附带图片、视频等,浏览器如果需要请求图片和视频,它会发送另一个HTTP请求,因此,一个HTTP请求只处理一个资源(此时就可以理解为TCP协议中的短连接,每个链接只获取一个资源,如需要多个就需要建立多个链接)
1.2. HTTP格式
每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。
HTTP协议是一种文本协议,所以,它的格式也非常简单。
1). HTTP GET请求的格式:
GET /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3
2). HTTP POST请求的格式:
POST /path HTTP/1.1
Header1: Value1
Header2: Value2
Header3: Value3body data goes here...
注:当遇到连续两个\r\n时(一个空行),Header部分结束,后面的数据全部是Body。
3). HTTP响应的格式:
200 OK
Header1: Value1
Header2: Value2
Header3: Value3body data goes here...
注:HTTP响应若包含body,也通过\r\n\r\n分隔。Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。
2. Web静态服务器
2.1 py实现简单的http服务器
import socket
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024)
print(request)
# 2. 返回http格式的数据,给浏览器
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
response += "hello world"
new_socket.send(response.encode("utf-8")) # python3发送的必须是字节类型
# 关闭套接字
new_socket.close()
def main():
"""用来完成整体的控制"""
# 1. 创建套接字
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = tcp_server_socket.accept()
# 5. 为这个客户端服务
service_client(new_socket)
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
运行后,在浏览器输入 http://127.0.0.1:7890/ ,服务器即会返回响应,浏览器显示 hello world。
注:其过程最好了解TCP的三次握手,4次挥手(详细参考计网知识)。
简单参考以下:
一般client先调close。
2.2. py实现返回浏览器需要的页面http服务器
在2.1 中,http服务器返回的是固定的字符串,本节实现返回浏览器需要的页面的http服务器。
实例:先实现返回index.html
运行前先在html文件夹准备一个index.html文件,用于返回给客户端。
import socket
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024)
print(">>>"*50)
print(request)
# 2. 返回http格式的数据,给浏览器
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
f = open("./html/index.html", "rb")
html_content = f.read()
f.close()
# 因为是以二进制打开的文件,不能直接response+=html_content
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = tcp_server_socket.accept()
# 5. 为这个客户端服务
service_client(new_socket)
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
运行后,在浏览器输入 http://127.0.0.1:7890/ ,服务器即会返回响应,浏览器显示 index.html对应的前端内容。
实例:改进,实现返回浏览器需要的页面。这时要先对client发过来的请求进行解码。解码后提取出用户请求的页面文件名。
import socket
import re
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8") # decode进行解码
# print(">>>"*50)
# print(request) # 会打印解码后的请求
request_lines = request.splitlines() # 对请求按行(请求的每行内容都有一个含义)分割,返回一个列表
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1 (这是请求的第一行)
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0]) # 依据请求第一行解析文件名([^/]+匹配多个非/,[^ ]*匹配0或多个非空格)
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") # 上面匹配不到时,没有file_name会异常
except:
response = "HTTP/1.1 404 NOT FOUND\r\n"
response += "\r\n"
response += "------file not found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = tcp_server_socket.accept()
# 5. 为这个客户端服务
service_client(new_socket)
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
运行后,在浏览器输入 http://127.0.0.1:7890/ 指定的文件名(如:http://127.0.0.1:7890/index.html),服务器即会返回响应,文件存在浏览器显示对应的内容。
3. Web静态服务器(多任务)
3.1. 多进程实现http服务器
accept依旧用一个进程来实现,而服务的过程用一个新的进程来实现。
(注:运行前先准备好资源)
import socket
import re
import multiprocessing
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
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 found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = tcp_server_socket.accept()
# 5. 为这个客户端服务(服务过程用新的进程实现)
p = multiprocessing.Process(target=service_client, args=(new_socket,)) # 进程执行service_client,参数new_socket
p.start()
new_socket.close() # 通俗理解:由于创建了新的进程(复制了父进程的socket)
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
3.2. 多线程实现http服务器
代码上与多进程的主要区别,把multiprocessing改成threading。注意子线程不会复制主线程的socket,所以只用一次new_socket.close()即可。
import socket
import re
import threading
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
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 found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = tcp_server_socket.accept()
# 5. 为这个客户端服务
p = threading.Thread(target=service_client, args=(new_socket,))
p.start()
# new_socket.close() # 线程不会复制主线程的socket,所以只用一次new_socket.close()即可。
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
更新:用 面向对象实现多进程web服务器
import socket
import re
import multiprocessing
class WSGIServer(object):
def __init__(self):
# 1. 创建套接字
self.tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 2. 绑定
self.tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
self.tcp_server_socket.listen(128)
def service_client(self, new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
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 found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
new_socket.close()
def run_forever(self):
"""用来完成整体的控制"""
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = self.tcp_server_socket.accept()
# 5. 为这个客户端服务
p = multiprocessing.Process(target=self.service_client, args=(new_socket,))
p.start()
new_socket.close()
# 关闭监听套接字
self.tcp_server_socket.close()
def main():
"""控制整体,创建一个web 服务器对象,然后调用这个对象的run_forever方法运行"""
wsgi_server = WSGIServer()
wsgi_server.run_forever()
if __name__ == "__main__":
main()
3.3. gevent实现http服务器
疑问:为何本例子gevent.spawn创建对象后,不需要调用 对象.join() 或 gevent.joinall() 使线程执行???
import socket
import re
import gevent
from gevent import monkey
monkey.patch_all()
def service_client(new_socket):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
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 found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
# 2.1 准备发送给浏览器的数据---header
response = "HTTP/1.1 200 OK\r\n"
response += "\r\n"
# 2.2 准备发送给浏览器的数据---boy
# response += "hahahhah"
# 将response header发送给浏览器
new_socket.send(response.encode("utf-8"))
# 将response body发送给浏览器
new_socket.send(html_content)
# 关闭套接
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
while True:
# 4. 等待新客户端的链接
new_socket, client_addr = tcp_server_socket.accept()
# 5. 为这个客户端服务
gevent.spawn(service_client, new_socket) # 协程在service_client执行,参数new_socket
# new_socket.close()
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
3.4. 非堵塞模式
在python中,套节字可以被设置为阻塞模式或者非阻塞模式。在非阻塞模式下,调用API 后,例如send() 或recv()方法,如果遇到问题就会抛出异常。在阻塞模式下,通俗说就是每次轮询监听一下,遇到错误并不会阻止操作。
对象.setblocking(False) ,设置套接字为非堵塞的方式。
3.4.1 单任务非堵塞
while内的关键代码,等待客户端到来的socket和列表内等待数据的socket互不影响。
注:该种方式实际应用少,但过程要理解,此种方式是并发而不是并行。
import socket
import time
tcp_server_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建套接字
tcp_server_tcp.bind(("", 7899)) # 绑定
tcp_server_tcp.listen(128)
tcp_server_tcp.setblocking(False) # 设置套接字为非堵塞的方式
client_socket_list = list() # 列表,有新的客户端链接时,将其客户端添加到列表中
while True:
# time.sleep(0.5) # 延时方便看效果
try: # 收不到值时产生异常
new_socket, new_addr = tcp_server_tcp.accept()
except Exception as ret:
print("---没有新的客户端到来---")
else:
print("---只要没有产生异常,那么也就意味着 来了一个新的客户端----") # 此时还不能调用recv,因为调用后又会堵塞
new_socket.setblocking(False) # 设置套接字为非堵塞的方式
client_socket_list.append(new_socket) # 有新的客户端连接,将其socket添加到列表
for client_socket in client_socket_list:
try:
recv_data = client_socket.recv(1024) # 检测列表的socket是否有数据
except Exception as ret:
print(ret)
print("----这个客户端没有发送过来数据----") # client没发送数据,产生异常
else:
print("-----没有异常-----")
print(recv_data)
if recv_data:
# 对方发送过来数据
print("----客户端发送过来了数据-----")
else:
# 对方调用close 导致了有数据,但recv返回空,recv_data为空
client_socket.close()
client_socket_list.remove(client_socket)
print("---客户端已关闭----")
3.4.2 单任务非堵塞(长链接)
通俗来说,短连接每获取一个数据建立一次链接(三次握手连接四次挥手断开)。操作步骤:建立连接——数据传输——关闭连接...建立连接——数据传输——关闭连接;
长链接在一次链接中,通过同一个套接字获取好几个需要的数据,这样可以节省服务器资源。操作步骤:建立连接——数据传输...(保持连接)...数据传输——关闭连接。
注:http1.1版本采用的是长链接。之前的code虽然用了1.1版本的http协议,但是每次连接后手动调用了 socket对象.close() ,导致还是短链接。
单任务非堵塞(长链接)code:在header中添加 Content-Length:,用来记下内容的长度,告诉浏览器包有多长,浏览器依此判断是否传完。这样就不用像短链接一样通过断开连接来判断传完了。
import socket
import re
def service_client(new_socket, request):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
# request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
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 found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
response_body = html_content
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 # 读到的response_body是二进制,response_header是字符串
new_socket.send(response) # 发送二进制的数据
# # 关闭监听套接字
# 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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 将套接字变为非堵塞
client_socket_list = list()
while True:
# 4. 等待新客户端的链接
try:
new_socket, client_addr = tcp_server_socket.accept()
except Exception as ret:
pass
else: # 没有异常
new_socket.setblocking(False)
client_socket_list.append(new_socket)
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: # 有数据
service_client(client_socket, recv_data)
else: # 空数据,即浏览器断开连接
client_socket.close()
client_socket_list.remove(client_socket)
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
3.4.3 epoll实现http服务器
一般linux服务器都是用epoll实现的(注:epoll是单进程、单线程),可以实现高并发。
epoll详解可参考:【Linux学习】epoll详解 。
select/epoll采用IO多路复用(有时也称event driven IO),当某个socket有数据到达,则通知用户进程。好处为单个process可同时处理多个网络连接的IO。
参考特点如下:通过一种机制使一个进程能同时等待多个文件描述符,而这些文件描述符(套接字描述符)其中的任意一个进入读就绪状态,epoll()函数就可以返回。 所以, IO多路复用,本质上不会有并发的功能,因为任何时候还是只有一个进程或线程进行工作,它之所以能提高效率是因为select\epoll 把进来的socket放到他们的 '监视' 列表里面,当任何socket有可读可写数据立马处理,那如果select\epoll 手里同时检测着很多socket, 一有动静马上返回给进程处理,总比一个一个socket过来,阻塞等待,处理高效率。
code:epoll实现HTTP(理解实现过程):
- 导入:import select
- 创建epoll对象:epl = select.epoll()
- 将监听套接字相应的FD(File Discriptor 文件描述符)注册到epoll中:epl.register(tcp_server_socket.fileno(), select.EPOLLIN) (注:参数二:select.EPOLLIN | select.EPOLLET,前者检测是否有输入,后者为ET模式)
-
fd_event_list = epl.poll() # epl.poll()默认会堵塞,直到 os监测到数据到来 通过事件通知方式 告诉这个程序,此时才会解堵塞。返回event和fd组成的列表。(列表格式:[(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)...])
-
对数据处理......
-
注销:epl.unregister(fd)。
注:上述3中,epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
- LT模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll时,会再次响应应用程序并通知此事件。
- ET模式:当epoll检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll时,不会再次响应应用程序并通知此事件。
EPOLLIN (可读)
EPOLLOUT (可写)
EPOLLET (ET模式)
import socket
import re
import select
def service_client(new_socket, request):
"""为这个客户端返回数据"""
# 1. 接收浏览器发送过来的请求 ,即http请求
# GET / HTTP/1.1
# .....
# request = new_socket.recv(1024).decode("utf-8")
# print(">>>"*50)
# print(request)
request_lines = request.splitlines()
print("")
print(">"*20)
print(request_lines)
# GET /index.html HTTP/1.1
# get post put del
file_name = ""
ret = re.match(r"[^/]+(/[^ ]*)", request_lines[0])
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 found-----"
new_socket.send(response.encode("utf-8"))
else:
html_content = f.read()
f.close()
response_body = html_content
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
new_socket.send(response)
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)
# 2. 绑定
tcp_server_socket.bind(("", 7890))
# 3. 变为监听套接字
tcp_server_socket.listen(128)
tcp_server_socket.setblocking(False) # 将套接字变为非堵塞
# 创建一个epoll对象!!!
epl = select.epoll()
# 将监听套接字对应的fd注册到epoll中
epl.register(tcp_server_socket.fileno(), select.EPOLLIN)
fd_event_dict = dict() # 定义字典用来存放fd对应的套接字
while True:
fd_event_list = epl.poll() # 默认会堵塞,直到 os监测到数据到来 通过事件通知方式 告诉这个程序,此时才会解堵塞。返回event和fd组成的列表
# [(fd, event), (套接字对应的文件描述符, 这个文件描述符到底是什么事件 例如 可以调用recv接收等)]
for fd, event in fd_event_list:
# 等待新客户端的链接
if fd == tcp_server_socket.fileno(): # 接收到新的客户端链接。产生新的套接字。将其注册到epoll中
new_socket, client_addr = tcp_server_socket.accept()
epl.register(new_socket.fileno(), select.EPOLLIN)
fd_event_dict[new_socket.fileno()] = new_socket # 存放fd对应的套接字
elif event == select.EPOLLIN: # 事件类型为EPOLLIN,有数据
# 判断已经链接的客户端是否有数据发送过来
recv_data = fd_event_dict[fd].recv(1024).decode("utf-8") # fd_event_dict[fd]根据套接字取socket
if recv_data:
service_client(fd_event_dict[fd], recv_data)
else: # 收到空数据
fd_event_dict[fd].close()
epl.unregister(fd)
del fd_event_dict[fd] # 删除字典对应的ky
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()