Python实现web服务器入门学习之 多进程、多线程实现并发HTTP服务器

  Python实现web服务器入门学习手动实现HTTP服务器中,已经学习了如何通过Python实现一个简单的HTTP服务器,但是问题在于所实现的服务器仅仅是单进程且单线程的,即服务器一次仅可以为一个客户端服务,服务完成之后才可以服务下一个浏览器发过来的请求。

  在前面学习Python多任务编程时,已经分别学习了通过线程、进程完成多任务的方式,

  因此,这里考虑使用线程以及进程实现支持并发的HTTP服务器。

  一、多进程实现并发HTTP服务器

  import socket

  import re

  import multiprocessing

  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)

  # 定义计数器,记录浏览器发起的请求次数

  request_counter = 0

  while True:

  # 4.等待新客户端连接

  new_client_socket, client_addr = tcp_server_socket.accept()

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

  process = multiprocessing.Process(target=serve_client, args=(new_client_socket,))

  process.start()

  new_client_socket.close()

  # 关闭监听套接字

  tcp_server_socket.close()

  if __name__ == "__main__":

  main()

  实际上,上述代码和Python实现web服务器入门学习笔记(2)——手动实现HTTP服务器中区别不大,只是导入了多进程模块multiprocessing,然后在主程序的死循环中为每个连接成功的客户端创建并启动了一个新的进程。

  只是需要特别说明的是,在启动新创建的进程后,需要立马关闭服务器接受连接后产生的new_client_socket对象,否则即使浏览器获取了请求的所有数据也不会断开和服务器的连接,从而造成对服务器有限IO资源的无效占用。

  产生上述问题的原因在于:虽然在子进程中调用了new_client_socket对象的close()方法,但实际上并未成功将对应的IO资源关闭。

  更具体地,如Python多任务学习笔记(7)——进程中所述,由主进程产生的子进程通常会拷贝主进程的各类资源,如此处主进程中有new_client_socket对象,在子进程中也会将其拷贝一份,于是两个new_client_socket对象同时指向服务器上的一个IO资源(实际上相当于指向该IO资源又多了一个硬链接,关于硬链接的概念请见Linux入门学习笔记(2)——文件操作命令的2.16.1节),该资源用以向浏览器发送应答数据,当仅调用子进程中的close()方法时,仅相当于删除了该IO资源文件的一个硬链接,只有在主进程中也调用new_client_socket的close()方法后,才能真正释放该IO资源。

  二、多线程实现并发HTTP服务器

  通常,因为产生一个进程需要的资源开销较大(一般为数个MB的级别),所以,在诸如电商等要求高并发的场景下,采用多进程来实现是不现实的,而相较而言,创新新线程的资源开销较小。因此,下面代码简单示意了如何通过多线程来实现并发HTTP服务器。

  import socket

  import re

  import threading

  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:郑州人流多少钱 http://www.hnmt120.com/

  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.为连接上的客户端服务

  thread = threading.Thread(target=serve_client, args=(new_client_socket,))

  thread.start()

  # 关闭监听套接字

  tcp_server_socket.close()

  if __name__ == "__main__":

  main()

  实际上,上述通过多线程实现并发任务的代码和多进程代码近乎一致,仅在于使用的模块换成了threading,创建进程变成了创建线程。另外,在该场景下,并不需要在主进程中调用new_client_socket的close()方法,这也印证了Python多任务学习进程中提及的:一个进程中的主线程所创建的子线程间一般共享变量等资源。

posted @ 2020-05-07 16:37  网管布吉岛  阅读(513)  评论(0编辑  收藏  举报