在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,避免列表过长产生无效遍历,导致程序性能下降。

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

  1. python中单线程非阻塞并发

    我们在使用多线程并发处理IO操作时候,每个线程当发送一个请求之后,就得阻塞等待接受返回的数据,数据较少的时候,这样并发操作还行,但是当数据非常庞大的时候,等待时间就会非常漫长,那么我们可以使用单线程非 ...

  2. python网站开发实例 flask_python-flask框架web服务接口开发实例

    一.flask flask是一个python编写的轻量级框架,可以使用它实现一个网站或者web服务.本文就用flask来开发一个接口. 二:安装框架 flask需要先安装再引用.pip install ...

  3. python bottle框架搭建_python开发web服务 bottle框架

    开发功能不是特别复杂的web服务,可以考虑使用bottle框架.原因:一.Python开发效率高呀!不信你比比同样的功能Python几行可以搞定?换java试试?换C++试试?作为这几种语言都使用过的 ...

  4. 两大主流Web服务器之分析与对比

    转自:http://info.edu.hc360.com/html/001/023/003/29949.htm    长期以来,Apache和Microsoft的IIS一直统治着Web服务器市场最大的 ...

  5. Web服务器之Http压缩(GZip)

     作者:张子秋 出处:http://www.cnblogs.com/zhangziqiu/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接, ...

  6. Web服务器之Nginx介绍

    一.Nginx简介 Nginx (engine x) 是一个高性能的Web和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 代理服器.Nginx处理高并发能力是十分强大的,能经受高负载的考 ...

  7. python硬件编程_树莓派c语言 设置并使用树莓派进行Python和C语言编程 - 硬件设备 - 服务器之家...

    树莓派c语言 设置并使用树莓派进行Python和C语言编程 发布时间:2017-03-01 来源:服务器之家 设置并使用树莓派进行Python和C语言编程 (下) Python部落组织翻译, 禁止转载 ...

  8. web服务器之iis,apache,tomcat三者之间的比较

    IIS-Apache-Tomcat的区别  IIS与Tomcat的区别 IIS是微软公司的Web服务器.主要支持ASP语言环境.  Tomcat是Java Servlet 2.2和JavaServer ...

  9. 常用的系统架构 web服务器之iis,apache,tomcat三者之间的比较

    常用的系统架构是: Linux + Apache + PHP + MySQL Linux + Apache + Java (WebSphere) + Oracle Windows Server 200 ...

最新文章

  1. 《马哥出品高薪linux运维教程》wingkeung学习笔记-linux基础入门课程5
  2. CVPR2021(Oral) 商汤、港中文实现单目人脸重建新突破: 基于生成网络的渲染器!几何形状更精准!渲染效果更真实!...
  3. Emoji表情编解码库XXL-EMOJI
  4. 网易云信11月大事记
  5. Oracle学习(1)——BLOCK
  6. CANN5.0黑科技解密 | 别眨眼,缩小隧道,让你的AI模型“身轻如燕”
  7. 微信公布7月朋友圈十大谣言 包括“奥运冠军杨倩被奖励1600万”等
  8. pybamm库学习-tutorial
  9. VisualBox配置共享文件夹功能
  10. 支付宝——(JAVA)支付测试开发
  11. 鼠标移到元素上 使hover事件不生效
  12. 借助Sigar API获取网络信息
  13. 拓端tecdat|R语言 PCA(主成分分析),CA(对应分析)夫妻职业差异和马赛克图可视化
  14. CodeGym—Java自学神器
  15. 使用Cisco Packet Tracer进行网络模拟
  16. 1周前,一个对外挂一无所知的人,在的成长过程(经典推荐)
  17. 网易公开课——可汗学院公开课:现代密码学(1)
  18. linux定时任务生效_Linux 定时任务不生效的问题
  19. html外链自动加nofollow,WordPress文章/页面外链自动添加nofollow属性的方法
  20. Python使用Win32和天行机器人API实现微信自动聊天机器人(自动敷衍机器人)

热门文章

  1. java正则过滤js_JS/Java正则表达式验证
  2. 华为手机可以安装python吗_何安装python2.6
  3. java 幽灵引用_Java 幽灵引用的作用
  4. 【转】PF_INET 和 AF_INET 的区别
  5. linux – syslog,rsyslog和syslog-ng之间有什么区别?
  6. vscode怎么自动将px转换成vw_基于react/vue移动端适配之px自动转rem、vw
  7. centos部署python flask_用Dockerfile部署你的Flask Web应用
  8. 【HDU - 1542】Atlantis (线段树,扫描线)
  9. 【HDU - 1757】A Simple Math Problem (矩阵快速幂)
  10. 【CodeForces - 608D】Zuma(区间dp)