python非阻塞多线程socket_Python实现web服务器之 单进程单线程非阻塞实现并发及其原理...

在Python实现web服务器入门学习多进程、多线程实现并发HTTP服务器中,我们知道可以分别通过多进程、多线程的方式实现并发服务器,那么,是否可以通过单进程单线程的程序实现类似功能呢?

实际上,在Python多任务学习分别通过yield关键字、greenlet以及gevent实现多任务中,我们知道gevent可以通过协程的方式实现多任务,且相较于yield关键字和greenlet而言,gevent屏蔽了很多实现细节,使用起来简单方便。

一、gevent实现并发HTTP服务器

下面代码以gevent实现并发HTTP服务器(即一种单进程、单线程、非阻塞的方式):

from gevent import monkey

import gevent

import socket

import re

monkey.patch_all()

def serve_client(new_client_socket):

"""为这个客户端返回数据"""

# 6.接收浏览器发送过来的http请求

request = new_client_socket.recv(1024).decode("utf-8")

# 7.将请求报文分割成字符串列表

request_lines = request.splitlines()

print(request_lines)

# 8.通过正则表达式提取浏览器请求的文件名

file_name = None

ret = re.match(r"^[^/]+(/[^ ]*)", request_lines[0])

if ret:

file_name = ret.group(1)

print("file_name:", file_name)

if file_name == "/":

file_name = "/index.html"

# 9.返回http格式的应答数据给浏览器

try:

f = open("./Charisma" + file_name, "rb")

except Exception:

response = "HTTP/1.1 404 NOT FOUND\r\n"

response += "\r\n"

response += "-----file not found-----"

new_client_socket.send(response.encode("utf-8"))

else:

# 9.1 读取发送给浏览器的数据-->body

html_content = f.read()

f.close()

# 9.2 准备发送给浏览器的数据-->header

response = "HTTP/1.1 200 OK\r\n"

response += "\r\n"

# 将response header发送给浏览器--先以utf-8格式编码

new_client_socket.send(response.encode("utf-8"))

# 将response body发送给浏览器--直接是以字节形式发送

new_client_socket.send(html_content)

# 10. 关闭此次服务的套接字

new_client_socket.close()

def main():

"""用来完成程序整体控制"""

# 1.创建套接字

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 通过设定套接字选项解决[Errno 98]错误

tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 2.绑定端口

tcp_server_socket.bind(("", 7899))

# 3.变为监听套接字

tcp_server_socket.listen(128)

while True:

# 4.等待新客户端连接

new_client_socket, client_addr = tcp_server_socket.accept()

# 5.为连接上的客户端服务

# 创建一个greenlet并不会导致其立即得到切换执行,

# 还需要在其父greenlet(在哪个程序控制流中创建该greenlet,

# 则这个程序控制流就是父greenlet)中遇到正确的阻塞延时类操作或调用greenlet对象的join()方法

#(此处不需要使用join()函数,因为主程序由于死循环的缘故不会在greenlet执行结束前退出)

greenlet = gevent.spawn(serve_client, new_client_socket)

# 关闭监听套接字

tcp_server_socket.close()

if __name__ == "__main__":

main()

至此,本文和Python实现web服务器入门学习笔记(3)——多进程、多线程实现并发HTTP服务器给出了三种实现并发HTTP服务器的方式,对比之后,可以发现:

对于进程、线程实现方式,服务器都是在客户端数量大于1时开辟新的程序执行控制流(分别叫子进程、子线程),且程序控制流之间一般相对独立,不会因为一个的阻塞而导致其他程序控制流无法执行,以避免在单进程且单线程的程序中,先建立请求的客户端因各种原因引起程序阻塞而导致其他客户端的请求得不到执行。如:上述通过进程和线程实现并发服务器程序中的accept()、recv()方法均为阻塞类操作。

对于协程实现方式,服务器为实现并发,虽然也会开辟新的程序执行控制流(这里叫greenlet,程序执行控制流可以承担很多身份,比如:进程、线程、greenlet,关于greenlet的详细说明,具体请见Python多任务学习笔记(10)——分别通过yield关键字、greenlet以及gevent实现多任务),但是这些程序执行控制流之间通过确定的时序作相互间切换实现并发,切换时机为程序中所有延时阻塞类操作的地方,指定程序控制流切换执行时机的方式有两种:

程序规模很小时,对于所有延时阻塞类操作,如:time.sleep(),socket.accept(),socket.recv(),使用gevent模块中的同名操作做替换,如:gevent.sleep(),gevent.accept(),gevent.recv();

程序规模很大,且多处使用了涉及延时阻塞类操作时,不用挨个做模块替换,通过gevent.monkey模块中的patch_all()函数改变所有的阻塞类操作的行为,使得每当程序遇到阻塞类操作则切换至其他greenlet。

对于协程实现方式,在主程序执行控制流中,通过gevent.spawn()这一类方法创建一个greenlet之后,主程序执行控制流自动成为该greenlet的父greenlet,如果程序中仅存在这两个greenlet,则程序也会在遇到正确的阻塞延时类操作时,在二者之间切换执行,请比较下面两段代码:

1. 程序未正确指定阻塞延时类操作:

import gevent

import time

def foo():

print('Explicit context switch to foo!')

gevent.sleep(0.0)

print('Explicit context switch to foo again!')

def main():

greenlet = gevent.spawn(foo)

print('Explicit execution in main!')

time.sleep(0.0)

print('Explicit context switch to main again!')

time.sleep(0.0)

print("The end of main!")

# 确保主程序(即主greenlet)等待子greenlet执行完毕之后才退出

greenlet.join()

if __name__ == '__main__':

main()

上述代码的运行结果为:

Explicit execution in main!

Explicit context switch to main again!

The end of main!

Explicit context switch to foo!

Explicit context switch to foo again!

2. 程序正确指定了阻塞延时类操作:

import gevent

def foo():

print('Explicit context switch to foo!')

gevent.sleep(0.0)

print('Explicit context switch to foo again!')

def main():

greenlet = gevent.spawn(foo)

print('Explicit execution in main!')

gevent.sleep(0.0)

print('Explicit context switch to main again!')

gevent.sleep(0.0)

# greenlet.join()

print("The end of main!")

if __name__ == '__main__':

main()

上述代码运行结果为:

Explicit execution in main!

Explicit context switch to foo!

Explicit context switch to main again!

Explicit context switch to foo again!

The end of main!

对比上述两段代码,我们知道:

创建一个greenlet并不会导致其立即得到切换执行,还需要在其父greenlet(在哪个程序控制流中创建该greenlet,则这个程序控制流就是父greenlet)中遇到正确的阻塞延时类操作或调用greenlet对象的join()方法;

即使不调用greenlet对象的join()方法,只要使用正确的阻塞延时类操作,程序依然可以按照期望的顺序执行完毕。

二、单进程单线程非阻塞实现并发原理

实际上,从单进程、单线程、非阻塞这几个关键字就可以发现,要想通过单进程、单线程实现并发,首要是要解决单进程、单线程的程序可能面对的程序阻塞问题,因为这一般会无谓地耗费时间。郑州哪个人流医院好 http://www.csyhjlyy.com/

那么,自然地,我们会想到:是否可以让原本阻塞的操作不阻塞?答案是肯定的:对于socket对象中的accept()、recv()等方法,其原本都是阻塞类操作,可以通过调用socket对象的setblocking()方法设置其为非阻塞模式。

然而,问题在于:在将socket对象设置为非阻塞模式的情况下,在调用其accept()、recv()方法时,如果未能立刻正确返回,则程序会抛出异常。故此时需要进行异常捕捉和处理,保证程序不被中断。

基于上述讨论,下面代码简单演示了单进程单线程非阻塞实现并发的原理:

import socket

import time

def initialize(port):

# 1.创建服务器端TCP协议socket

tcp_server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 2.绑定本地IP和端口

tcp_server_sock.bind(("", port))

# 3.设置套接字为监听状态

tcp_server_sock.listen(128)

# 4.设置套接字为非阻塞状态

tcp_server_sock.setblocking(False)

return tcp_server_sock

def non_blocking_serve(tcp_server_sock):

# 定义一个列表,用于存放已成功连接但是未完成数据发送的客户端

client_sock_list = list()

while True:

try:

new_client_sock, new_client_addr = tcp_server_sock.accept()

except Exception as exception:

print(exception)

else:

print("新客户端", new_client_sock, "已成功连接!")

new_client_sock.setblocking(False)

client_sock_list.append(new_client_sock)

for client_sock in client_sock_list:

try:

recv_data = client_sock.recv(1024)

except Exception as exception:

print(exception)

else:

if recv_data:

# 表明客户端发来了数据

print(recv_data)

else:

# 客户端已调用close()方法,recv()返回为空

client_sock_list.remove(client_sock)

client_sock.close()

print("客户端", client_sock, "已断开连接!")

def main():

tcp_server_sock = initialize(8888)

non_blocking_serve(tcp_server_sock)

if __name__ == '__main__':

main()

对于上述代码,需要说明的几点是:

程序24行定义的列表client_sock_list用于存放已成功连接但是未完成数据发送的客户端对象;

程序36行遍历列表client_sock_list,挨个通过recv()方法通过非阻塞方式接收数据,而recv()方法正确返回有两种情况:

客户端将数据正确发送了过来,此时recv_data变量非空;

客户端完成了此次请求,主动先断开了连接,此时recv_data为空。

程序45行将已经完成请求的客户端移出列表client_sock_list,避免列表过长产生无效遍历,导致程序性能下降。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/446033.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

企业知识库与知识管理:如何统一战略与实践

在知识密集型的现代企业中,知识已经成为了一种宝贵的资产。如何有效地管理和利用这一资产,成为企业持续发展与创新的关键。企业知识库与知识管理作为知识经济的两大支柱,它们的重要性不言而喻。但很多时候,我们发现企业的知识管理…

音视频处理 ffmpeg中级开发 AAC编码

介绍 编码流程类似于视频编码,1,查找编码器;2,设定参数,打开编码器;3,数据编码编码函数 avcodec_encode_audio2 已经被弃用FFmpeg 过时 Api 汇总整理 - 灰色飘零 - 博客园 未成功使用 旧版本i…

虚拟机为Ubuntu分配空间

当虚拟机里面的创建的ubuntu镜像需要更大的空间,将ubuntu关掉之后,对应调整硬盘的空间大小,由先前的20G上调至50G,但是先前的20G内存空间映射的位置是/dev/sda,后面增加的这段内存空间30G映射到/dev/sda1因此&#xff…

为什么人会摆高姿态_Yo , 你为什么喜欢冲浪?

“你为什么喜欢冲浪?” 那天木木突然问我。我愣住了。此时一道碧波恰从防泼堤(jetty)的那头升起,木木转头望去,视线追着那道浪缓缓向西,直至它破碎成白色的浪花。我瞥见他眼神中的光亮,就和小孩…

音视频处理 ffmpeg初级开发 命令行工具-实用命令

参考链接 ffmpeg Documentation作者:smallest_one 链接:FFmpeg命令行工具-实用命令 - 简书 目录 1,help命令使用 1.1 ffmpeg命令的语法结构1.2 获取详细的help信息1.3 打印帮助或者支持能力的信息1.4 全局选项1.5 文件选项1.6 视频/音频/字…

不同的电脑打印预览不同怎么解决_条码打印软件中标签预览正常打印无反应怎么解决...

在使用条码打印软件制作标签时,有客户反馈,标签打印预览正常的,但是打印无反应,咨询是怎么回事?今天针对这个情况,可以参考以下方法进行解决。一、预览正常情况下,打印没反应(1)在条码打印软件中设计好标签之后&#…

python安装scrapy_Python安装Scrapy的种种

这几天没什么事,决定把自己抓代理的小工具用scrapy改写。然而安装的时候却出现以下问题,反复失败:Unable to find vcvarsall.bat经过一番查找,找到了这个文件:\Lib\distutils\_msvccompiler.py它里边长这样&#xff1a…

MP4文件格式的相关内容

参考链接 FFmpeg中mp4的demuxer(mov.c)代码阅读 - 简书mp4文件格式解析 - 简书mp4封装格式各box类型讲解及IBP帧计算_青丶空゛的博客-CSDN博客5分钟入门MP4文件格式 - 程序猿小卡 - 博客园​关于M4A文件的随机访问 - 云社区 - 腾讯云 MP4文件格式相关内容 MP4文件由许多box组…

华三交换机如何进入配置_学校机房项目交换机的如何配置,理解这篇,交换机配置不再难...

弱电项目中,交换机的配置是无法避免的,大部分的项目都有可能会涉及到,尤其是机房等网络项目,本期我们就通过一个实际项目案例来详细了解交换机在项目中的应用配置,如果我们平时对交换机配置不熟,这个案例可…

百度地图迁徙大数据_百度地图大数据:五一高速拥堵不似预期,广深成热门迁出入地...

五一假期在即,你是否做好了“出行功课”?高速拥堵水平降低、公众出门不出城、公园成踏青赏景热门目的地……在全国疫情防控仍未松懈的时刻,2020年的五一或许注定与往年不同。近日,百度地图发布2020五一假期安全出行大数据&#xf…

音视频的基础知识 视频播放器原理/封装格式/视频音频编码数据/视频像素数据/音频采样数据

参考链接 FFMpeg视频播放器的制作-雷霄骅(去除电流音版本)_哔哩哔哩_bilibili 视频播放器原理 播放视频文件的流程YUV是一张屏幕中像素点的数值封装格式 MP4 RMVB TS FLV AVI将视频和音频码流按照一定的格式存储在一个文件中封装格式分析工具&#xf…

科立捷7代写频软件_天大厦大“两硕士论文雷同”通报,代写买卖论文

澎湃新闻记者 薛莎莎天津大学、厦门大学7月10日晚就“两硕士论文雷同”一事,分别发出调查处理通报。通报称,涉事两名学生存在由他人代写、买卖论文的学术作假的行为,均撤销其所获硕士学位,收回、注销硕士学位证书。澎湃新闻注意到…

FFMpeg命令行基础

参考链接 FFMpeg视频播放器的制作-雷霄骅(去除电流音版本)_哔哩哔哩_bilibili音视频处理 ffmpeg初级开发 命令行工具-实用命令_MY CUP OF TEA的博客-CSDN博客 介绍 FFMpeg是视频播放和转码的内核 使用 win中ffmpeg.exe用于视频转码简单命令&#xff1…

悲观锁和乐观锁_面试必备之乐观锁与悲观锁

何谓悲观锁与乐观锁乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。大家可以点击加群【JAVA架构知识学习讨论群】47398464…

Microsoft Visual Studio2019环境下搭建FFmpeg开发环境

参考链接 《基于 FFmpeg SDL 的视频播放器的制作》课程的视频_雷霄骅的博客-CSDN博客_雷霄骅ffmpeg视频教程小学期课程资料 - 基于FFmpegSDL的视频播放器的制作.zip_免费高速下载|百度网盘-分享无限制辅助参考链接使用VS2019创建项目,添加文件和库地址_MY CUP OF …

vue process.env获取不到_从文档开始,重学vue(下)源码级别

此篇文章主要是从应用及源码层面讲解vue部分常用api,阅读起来可能略有难度,新手可以看《从文档开始,重学vue(上)》示例代码均在vue-cli3中完成Vue.extend()可以使用 extend 创建一个子类,该方法通常用于构建全局组件,如弹框组件等,下面我们就用它来制作个全局alert组件吧首先我…

Microsoft Visual Studio2019环境下搭建SDL开发环境

参考链接 《基于 FFmpeg SDL 的视频播放器的制作》课程的视频_雷霄骅的博客-CSDN博客_雷霄骅ffmpeg视频教程小学期课程资料 - 基于FFmpegSDL的视频播放器的制作.zip_免费高速下载|百度网盘-分享无限制辅助参考链接VS自动链接到Windows上随vcpkg安装的SDL2库 | 码农俱乐部 - G…

不关注公众号可以获取openid吗_微信公众号粉丝迁移

目录 [toc] 微信公众号迁移 正常的公众号迁移直接通过微信操作就可以,如下图。但是因为udb数据里面存的是迁移前公众号的openid以及unionid,需要自行获取新旧openid以及unionid。 旧的用户信息要在迁移之前获取,第三步点击同意之后就公众号的接口就调不通…

建筑专业规范大全 2020版_房屋建筑工程现行规范标准目录汇编(2020版)—建筑电气...

房屋建筑工程现行规范标准目录汇编(2020版)建筑电气规范编号规范名称GB 50034-2013建筑照明设计标准GB 50052-2009供配电系统设计规范GB 50053-201320kV及以下变电所设计规范GB 50057-2010建筑物防雷设计规范GB 50147-2010电气装置安装工程 高压电器施工及验收规范GB 50148-201…

基于Microsoft Visual Studio2019环境编写ffmpeg视频解码代码

旧代码 旧代码使用了很多过时的API,这些API使用后,vs会报编译器警告 (级别 3) C4996的错误即 函数被声明为已否决 报 C4996的错误 // test_ffmpeg.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。 // #define SDL_MAIN_HANDLED …