本文共 3272 字,大约阅读时间需要 10 分钟。
如果有多个客户端访问服务器,在一般的服务器模型中它会花很长一段时间来处理某个给定的客户端请求,那么服务器就会崩溃。
通过分支出一个新的进程来处理每个新的客户端连接。因为handleClient函数在一个新的进程中运行,所以dispatcher函数可以立即恢复其主循环,以便发现和服务新调入的请求。
介绍一下os.fork,派生进程本质上是产生派生的进程的副本,所以它们从父进程那里继承了文件和套接字描述符。因此,新的运行的handleClient函数的子进程,可以访问连接到父进程中创建的套接字。事实上,这就是子进程工作的原理,当在连接的套接字上回话时,它使用的套接字,与父进程accept调用返回的套接字相同。如果fork调用返回0,程序就知道自己运行在派生子进程中;否则,原来的父进程会获得新的子进程ID。
//服务器端:打开一个端口上的套接字,监听来自客户端的消息,以及发送一个应答响应;分支出可以进程来处理每个客户端的连接;子进程共享父进程的套接字描述符;分支的可移植性低于线程---尚未在Windows中出现,除非安装了Cygwin或类似的组件。__author__ = 'JianqingJiang'# -*- coding: utf-8 -*-import os,time,sysfrom socket import *myHost = ''myPort = 50007sockobj = socket(AF_INET,SOCK_STREAM) # make a TCP socket objectsockobj.bind((myHost,myPort)) # bind it to server port numbersockobj.listen(5) # allow 5 pending connectsdef now(): return time.ctime(time.time()) # current time on serveractiveChildren = []def reapChildren(): # reap any dead child processes while activeChildren: # else may fill up system table pid,stat = os.waitpid(O,os.WNOHANG) # don't hang if no child exited if not pid: break activeChildren.remove(pid)def handleClient(connection): # child process: reply,exit time.sleep(5) # simulate a blocking activity while True: # read , write a client socket data = connection.recv(1024) # till eof when socket closed if not data : break reply = 'Echo=>%s at %s' % (data,now()) connection.send(reply.encode()) connection.close() os._exit(0) //这里必须要退出。否则每个子进程会与其他子进程竞争新的客户端请求def dispatcher(): # listen until process killed while True: # wait for next connection connection,address = socket.accept() # pass to process for service print('Server connected by',address,end='') print('at',now()) reapChildren() # clean up exited children now childPid = os.fork() # copy this process if childPid == 0: # if in child process: handle handleClient(connection) else: # else:go accept next connection activeChildren.append(childPid) # add to active child pid listdispatcher()
这里的关键原理就是该脚本在服务器机器上运行某个主要的父进程,该进程什么都不做,只是留意(dispatcher中的)连接,为每个处于运行中的客户端连接添加一个子进程,与主要的父进程和其他客户端进程(在handleClient中)并行运行。原则上,该服务器可以在不崩溃的情况下,处理任意数量的客户端。
在某个更加实际的应用程序中,延迟可能是致命的,如果有许多客户端正在同时尝试连接的话。服务器会在我们模拟的有time.sleep的过程中国阻塞住,而且不会返回到主循环accept新的客户端请求。由于进程可以在每次请求时产生分支,所以可以并行地服务客户端。
挂着无用但是占用纪录空间的子进程为僵尸进程。为了在子进程结束后清理,这个服务器会保存一个activeChildren列表,其中包括它派生的所有子进程的进程ID。无论何时收到一个客户端请求,服务器都会运行reapChildren,通过发送标准Python os.waitpid(0, os.WNOHANG)调用来等待任何挂起的无用的将消失的进程。
os.waitpid调用试图等待一个子进程退出,然后返回它的进程ID,最后进入结束状态。当它的第一个参数为0时,它就会等待任何子进程,当其第二个参数WNOHANG时,如果没有子进程退出(也就是,它不会阻塞或中止调用,它就什么都不做。其实际结果就是,这个请求仅要求操作系统获得任何已经退出的子进程ID。如果有的话,返回的进程ID,会从系统的进程表及脚本的activeChildren列表中删除。
当reapChildren命令被重新激活时,每次都会清理无用的即将消失的子僵尸进程条目,服务器通过调用Python的os.waitpid函数来获得新的客户端连接请求
转载地址:http://dknqi.baihongyu.com/