Python 开发web服务器,socket非堵塞模式

需求描述

在开发web服务器接受http请求的时候,socket在recv等待接受数据的时候,服务端是堵塞的。 用于等待http发送过来的数据。 那么这个等待,其实也是会占用服务端的资源的。 为了节省这个资源,可以采用非堵塞的方式来进行socket等待监听,就是每次轮询监听一下,并不会堵塞等待。

修改为非堵塞方式

首先修改socket为非堵塞模式,并且创建一个专门用来存放client_socket的list

   # 设置非堵塞模式
   server_socket.setblocking(False)
   # 创建用来存放子进程的list
   client_socket_list = list() # 因为是非堵塞的方式,所以需要存放接受到要处理的client_socket

设置tcp的主socket等待接受http请求到来,因为没有堵塞,所以循环的时候必定会报错,所以需要进行异常抛出处理。

运行效果如下:

那么再来将client_socket的list循环处理

运行如下:

那么这个是什么意思呢?

这是因为在循环非堵塞的过程中,client_socket执行之后就会关闭,但是由于是提前存入list组中,然后再次循环的时候,就会重复执行同一个client_socket,导致client_socket关闭错误(因为第一次已经关闭了,再次recv的时候当然报错)。

那么只要处理一下这个关闭即可。

还有另外一个问题,那就是在什么时候才能进行client_socket的关闭呢?

来思考一下,是不是当client_socket在堵塞recv的时候,只要来了数据,就不会抛出异常,只要不抛出异常,那这时候肯定就是要关闭client_socket、同时将该client_socket剔除list中,避免重复循环。

处理循环调用client_socket

运行效果如下:

那么到了这里已经完成了非堵塞的web服务了。但是这里面还有一个比较不好的性能问题,下次再讨论吧。

可以提前思考一下,如果当这个存放client_socket的list越来越大,然后轮询的时候就会很久,那么就是导致访问一个页面很慢,这个问题该怎么处理呢?

本次完整代码如下

#coding=utf-8
from socket import *
import re
import threading
import time

def handle_client(client_socket):
    """为一个客户端服务"""
    # 接收对方发送的数据
    recv_data = client_socket.recv(1024).decode("utf-8") #  1024表示本次接收的最大字节数
    # 打印从客户端发送过来的数据内容
    #print("client_recv:",recv_data)
    request_header_lines = recv_data.splitlines()
    for line in request_header_lines:
        print(line)
     
    # 返回浏览器数据
    # 设置内容body
    # 使用正则匹配出文件路径
    print("------>",request_header_lines[0])
    print("file_path---->","./html/" + re.match(r"[^/]+/([^\s]*)",request_header_lines[0]).group(1))
    ret = re.match(r"[^/]+/([^\s]*)",request_header_lines[0])
    if ret:
       file_path = "./html/" + ret.group(1)
       if file_path == "./html/":
          file_path = "./html/index.html"
       print("file_path *******",file_path)

    try:
       # 设置返回的头信息 header
       response_headers = "HTTP/1.1 200 OK\r\n" # 200 表示找到这个资源
       response_headers += "\r\n" # 空一行与body隔开
       # 读取html文件内容
       file_name = file_path # 设置读取的文件路径
       f = open(file_name,"rb") # 以二进制读取文件内容
       response_body = f.read()
       f.close()   
       # 返回数据给浏览器
       client_socket.send(response_headers.encode("utf-8"))   #转码utf-8并send数据到浏览器
       client_socket.send(response_body)   #转码utf-8并send数据到浏览器
    except:
       # 如果没有找到文件,那么就打印404 not found
       # 设置返回的头信息 header
       response_headers = "HTTP/1.1 404 not found\r\n" # 200 表示找到这个资源
       response_headers += "\r\n" # 空一行与body隔开
       response_body = "<h1>sorry,file not found</h1>"
       response = response_headers + response_body
       client_socket.send(response.encode("utf-8"))

#    client_socket.close()

def main():
   # 创建套接字
   server_socket = socket(AF_INET, SOCK_STREAM)
   # 设置当服务器先close 即服务器端4次挥手之后资源能够立即释放,这样就保证了,下次运行程序时 可以立即绑定7788端口
   server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
   # 设置服务端提供服务的端口号
   server_socket.bind(('', 7788))
   # 使用socket创建的套接字默认的属性是主动的,使用listen将其改为被动,用来监听连接
   server_socket.listen(128) #最多可以监听128个连接

   # 设置非堵塞模式
   server_socket.setblocking(False)
   # 创建用来存放子进程的list
   client_socket_list = list() # 因为是非堵塞的方式,所以需要存放接受到要处理的client_socket

   # 开启while循环处理访问过来的请求 
   while True:
      #time.sleep(0.5)    
      # 如果有新的客户端来链接服务端,那么就产生一个新的套接字专门为这个客户端服务
      # client_socket用来为这个客户端服务
      # server_socket就可以省下来专门等待其他新的客户端连接while True:
      try:
         client_socket, clientAddr = server_socket.accept()
      except Exception as e:
         print("----1 暂无http请求到来------",e) # 因为是非堵塞,所以有可能会出现socket异常的情况
      else:
         print("----2 有http请求到来了!!------")
         client_socket.setblocking(False) # 设置client_socket为非堵塞模式
         client_socket_list.append(client_socket) # 将子进程加入list中

      # 循环处理client_socket_list
      for client_socket in client_socket_list:
          try:
              handle_client(client_socket) 
          except Exception as e:
              print("----3 client_socket并无收到http请求到来",e)
          else:
              print("----4 client_socket收到http请求到来,并进行数据处理")
              client_socket.close()
              client_socket_list.remove(client_socket)

if __name__ == "__main__":
   main()

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏GitHubDaily

GitHub 项目推荐:前端开发资料、Go 项目源码解读

有位开发者在 GitHub 上整理了一批前端开发相关的优质网站、博客、教程、书籍等内容。

14210
来自专栏java思维导图

分布式Session解决方案

Session 是客户端与服务器通讯会话跟踪技术,服务器与客户端保持整个通讯的会话基本信息。

13510
来自专栏网站建设、网站制作专栏

常用的免费cms智能建站系统推荐

CMS是"Content Management System"的缩写,意为"网站管理系统",也叫智能建站系统或自助建站系统,注意这里要和在线建站区分,cms...

94620
来自专栏Java识堂

Maven jar包冲突如何解决?

假设我们现在有一个多模块项目,依赖关系如图,我们在st-web模块中引入st-dal依赖时,st-common-lib这个依赖也会被我们引入,这个就是依赖传递,...

92210
来自专栏Java3y

[网站优化实战]公共CDN库/Nginx启用Gzip/全站CDN加速

我自己搭建的网站https://price.monitor4all.cn/网页打开的速度一直比较慢,经查证是我的网站有很多静态js大文件,通过浏览器读取这些js...

37050
来自专栏热爱IT

Yii2.0.12升级到PHP7.2可用版本 转

Yii2.0.12版本使用\yii\base\Object类,Object作为类名PHP7.2报错。

19610
来自专栏APP专栏

密码太多记不住怎么办

我这个人吧记性不好,却总是喜欢设置各种不熟悉的密码,包括银行的、软件的、账号的,时间一长就全忘了。虽然可以绑定手机找回,但是每次都要重设密码很烦。我就是把密码记...

19730
来自专栏码洞

客户端统一开发框架 Flutter 完全指南

Flutter 作为一个跨平台的框架,其开发技术栈融合了 Native 和前端的技术,不仅涉及到了 Native(Android、iOS )的开发知识,又吸取了...

21330
来自专栏沈唁志

WordPress插件开发教程一:创建、停用、删除插件

在wp-content/plugins创建一个文件夹,命名最好加前缀,WordPress官方现在应该收录了有五万多的插件,所以要起一个特殊的名称,防止插件和别人...

14320
来自专栏区块链大本营

干了一年半, 我还是离开了区块链, 这5点是我学到的

Ryan Lechner是ConsenSys的区块链工程师,过去一年半的时间里,他都在帮助ConsenSys做区块链开发。因为ConsenSys是以太坊中重点项...

11360

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励