版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
前言
需要知道的以下几点:
- 重中之重:该项目注释相当详细,小白完全可以看懂。
- 项目需求:浏览器作为客户端发送请求,服务器接收并处理返回对应的数据–也就是网页(要熟悉正则表达。式)。
- 部分重复代码并未定义函数,直接复制粘贴。
- epoll中重要的是“事件通知”和“内存映射”以及“fd(file descriptor)”的灵活使用。
- 此文中还有长连接的运用及介绍。
代码部分
import socket, re, gevent
import select
from gevent import monkey
# monkey.patch_all()
def service_client(client_socket,client_request):
# client_request = client_socket.recv(1024).decode("utf-8")
# print(">>"*50) # 体现浏览器多少个请求
# print(client_request) # GET / HTTP/1.1
client_request_lines = client_request.splitlines() # splitlines() 切割一个字符串(\n\r),返回一个列表,缺省为False
print(">>" * 20)
print(client_request_lines)
# GET /index.html HTTP/1.1
ret = re.match(r"[^/]+(/[^ ]*)", client_request_lines[0])
file_name = "" # 解决引用未定义的变量
if ret:
file_name = ret.group(1)
# print("*"*50, file_name)
if file_name == "/":
file_name = "/index.html"
# else:
# file_name = "/index.html"
try:
f = open("./html" + file_name, "rb")
html_content = f.read()
f.close()
response_body = html_content
response_header = "HTTP/1.1 200 OK\r\n" # win中只识别 \r\n的换行
# 长连接的关键
response_header += "Content-Length:%d\r\n" % len(response_body)
response_header += "\r\n"
# 字符串编码为二进制相加
response = response_header.encode("gbk")+response_body
client_socket.send(response)
except Exception as a:
f = open("./html/" + "404.html", "rb")
html_content = f.read()
f.close()
response_body = html_content
response_header = "HTTP/1.1 200 OK\r\n" # win中只识别 \r\n的换行
# 长连接的关键
response_header += "Content-Length:%d\r\n" % len(response_body)
response_header += "\r\n"
# 字符串编码为二进制相加
response = response_header.encode("gbk") + response_body
client_socket.send(response)
# todo 经过测试发现 如果想要利用长连接的话,那么不能直接返回字符串,而需要返回一个网页。(另外,可能因为我的网页问题,我的浏览器在访问我的服务器时,有时会出现类似堵塞的状况)
# todo 得到解决了,那是因为我没有设置监听套接字为非堵塞(见main())
# print(a)
# # response = "HTTP/1.1 404 NOT FOUND\r\n"
# response = "HTTP/1.1 200 OK\r\n" # 兼容 谷歌外的浏览器
# response += "\r\n"
# # response += "Content-Length:%d\r\n" % len("<h1>---没有你要的地址---</h1>")
# response += "<h1>---没有你要的地址---</h1>"
# client_socket.send(response.encode("gbk"))
#
# client_socket.close() # 调用了close() 还是短连接
pass
def main():
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置服务器先close 即服务器端四次挥手之后资源能够立即释放,这样就保证了下次运行服务器程序的时候,可以立即启动。
tcp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
tcp_socket.bind(("192.168.233.1", 7890))
tcp_socket.listen(128)
tcp_socket.setblocking(False)
# 创建一个epoll对象
epl = select.epoll()
# 将监听套接字对应的fd注册到epoll中 .fileno(): 获取套接字对应的fd,.EPOLLIN:检测套接字是否有输入
epl.register(tcp_socket.fileno(),select.EPOLLIN)
fd_event_dict = dict() # 定义一个字典,通过fd存储 “客户端套接字”
while True:
fd_event_list = epl.poll() # 默认堵塞,直到os检测到数据到来,以 事件通知 的方式告诉这个程序。--返回值是一个列表。
# 列表里是:[(fd,event),(套接字对应的文件描述符,文件描述符究竟是什么事件 例如 可以recv接收等)]
for fd, event in fd_event_list: # 列表中有几个,便循环几次(虽说很简单的基础知识,但是对于理解这一部分是很重要的)。
# 返回的fd和监听套接字的fd一样,便可有一个新的套接字。
if fd == tcp_socket.fileno(): # 这是监听套接字的 if。
client_socket, client_addr = tcp_socket.accept()
epl.register(client_socket.fileno(), select.EPOLLIN) # 到这就至少有两个套接字了,循环之后因为有了监听套接字的缘故,便可有多个“新的套接字”了。
fd_event_dict[client_socket.fileno()] = client_socket
# 判断链接的客户端是否有数据过来
elif event == select.EPOLLIN: # 剩下的套接字的 elif
recv_data = fd_event_dict[fd].recv(1024).decode("gbk")
if recv_data:
service_client(fd_event_dict[fd], recv_data)
else:
# 关闭套接字
fd_event_dict[fd].close()
# 注销fd
epl.unregister(fd)
# 删除字典中的内容
del fd_event_dict[fd]
tcp_socket.close()
pass
if __name__ == '__main__':
main()
# todo 仅参考。
# 长连接的意义:减少使用服务器的资源。一次性要所有的数据:(比如一次性将网页中的图片全部要来(可能比起短连接的要慢一点【短链接中服务器解析浏览器的数据请求中包含多个图片文件,会建立多个短链接发送数据】--但是现在网速都相对较快了,所以在资源合理化利用的角度上来说是要使用长连接的))