概述:
web服务器实际上就是一个运行在物理机上的网络服务器,它等待客户端给他发送请求,成功接收后将客户端请求的资源响应给它。客户端与服务端的通信通过http协议实现。客户端可以是浏览器或者可以发送请求的一段程序。

一、一个简单的web服务器
我们可以通过socket()函数来创建一个网络连接,或者说打开一个网络文件,socket的返回值就是一个文件描述符。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如
read 用来读取数据
write 用来写数据
只要你创建了连接,剩下的就是文件操作了

import socket
host, port = '', 8080
# SOCK_STREAM 流格式套接字,也叫面向连接的套接字(使用TCP协议)
# AF_INET使用ipv4地址
#一般情况下有了 af 和 type 两个参数就可以创建套接字了,操作系统会自动推演出协议类型
listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, proto=0)
listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listen_socket.bind((host, port))
listen_socket.listen(5)
print(f'Service HTTP on Port {port}')

while True:

    client_connection, client_address = listen_socket.accept()
    request = client_connection.recv(1024)
    print(request)
    http_response = """\HTTP/1.1 200 OK
    Hello world!
    """
    client_connection.sendall(http_response)
    client_connection.close()

接下来,我们考虑如何将我们的web应用放在我们写的web服务器上运行,那么我们如和做到不同的应用在不修改架构和代码的前提下可以让应用正常的运行在web服务器上,答案就是python web server gateway interface(简称WSGI,读作wizgy)
WSGI允许开发者选择将web应用框架和web服务器分开,如此你可以将web框架和web服务器混合匹配使用,选择一个适合你需要的配对。比如,你可以在Gunicorn 或者 Nginx/uWSGI 或者 Waitress上运行 Django, Flask, 或 Pyramid。真正的混合匹配,得益于WSGI同时支持服务器和架构。
下面我们去实现一个简单的WSGI服务器:

import socket
import sys
import io


class WSGIServer(object):
    address_family = socket.AF_INET
    socket_type = socket.SOCK_STREAM
    request_queue_size = 1

    def __init__(self, server_address):
        self.listen_socket = socket.socket(self.address_family, self.socket_type)
        self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.listen_socket.listen(self.request_queue_size)
        host, port = self.listen_socket.getsockname()[:2]
        self.server_name = socket.getfqdn(host)
        self.server_port = port

        self.headers_set = []

    def set_app(self, application):
        self.application = application

    def server_forever(self):
        listen_socket = self.listen_socket
        while True:
            self.client_connection, self.client_address = listen_socket.accept()
            self.handle_one_request()

    def handle_one_request(self):
        self.request_data = self.client_connection.recv(1024).decode('utf-8')

        self.parse_request(self.request_data)
        env = self.get_environ()
        result = self.application(env, self.start_response)
        self.finish_response(result)

    def parse_reqeust(self, text):
        request_line = text.splitlines()
        request_line = request_line.rstrip('\r\n')
        (self.request_method,
         self.path,
         self.request_version) = request_line.split()

    def get_environ(self):
        env = dict()
        env['wsgi.version'] = (1, 0)
        env['wsgi.url_scheme'] = 'http'
        env['wsgi.input'] = io.StringIO(self.request_data)
        env['wsgi.errors'] = sys.stderr
        env['wsgi.multithread'] = False
        env['wsgi.multiprocess'] = False
        env['wsgi.run_once'] = False
        # Required CGI variables
        env['REQUEST_METHOD'] = self.request_method  # GET
        env['PATH_INFO'] = self.path  # /hello
        env['SERVER_NAME'] = self.server_name  # localhost
        env['SERVER_PORT'] = str(self.server_port)  # 8888
        return env

    def start_response(self, status, response_headers, exc_info=None):

        server_headers = [
            ('Date', 'Tue, 26 Mar 2019 00:22:48 GMT'),
            ('Server', 'WSGIServer 0.2'),
        ]
        self.headers_set = [status, response_headers + server_headers]

    def finish_response(self, result):
        try:
            status, response_headers = self.headers_set
            response = f'HTTP/1.1 {status}\r\n'
            for header in response_headers:
                response += "{}:{}\r\n".format(*header)
            response += '\r\n'
            for data in result:
                response += data
            print('====')
            self.client_connection.sendall(response)

        except Exception as e:
            print(e)
        finally:
            self.client_connection.close()


SERVER_ADDRESS = (host, port) = '', 5000

def make_server(server_address, application):
    server = WSGIServer(server_address)
    server.set_app(application)
    return server
if __name__ == '__main__':
    if len(sys.argv) < 2:
        sys.exit('Provide a WSGI application object as module:callable')
    app_path = sys.argv[1]
    module, application = app_path.split(":")
    module = __import__(module)
    application = getattr(module, application)
    httpd = make_server(SERVER_ADDRESS, application)
    print('----')
    httpd.server_forever()

接下来让我们实现一个简单的并发服务器
前面我们写的是迭代服务器,一次只能处理一个请求

import os
import time
import socket

SERVER_ADDRESS = (HOST, PORT) = '', 5000
REQUEST_QUEUE_SIZE = 5


def handle_request(client_conn):
    request = client_conn.revc(1024)
    print(f"Child pid:{os.getpid()},Parent pid:{os.getppid()}")
    print(request.decode())
    http_response = b"""
    HTTP/1.1 200 OK
    Hello world!
    """
    client_conn.sendall(http_response)
    time.sleep(60)


def server_forever():
    # 创建套接字
    listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 绑定地址
    listen_sock.bind(SERVER_ADDRESS)
    # 设置监听数
    listen_sock.listen(REQUEST_QUEUE_SIZE)
    print(f"Parent pid:{os.getpid()} ")

# 一直接收
while True:
    client_conn, client_addr = listen_sock.accept()
    # 创建进程
    pid = os.fork()
    if pid == 0:  # 子进程
        # 关闭子进程复制父进程的套接字
        listen_sock.close()
        # 处理请求
        handle_request(client_conn)
        # 处理完,连接关闭
        client_conn.close()
        # 子进程退出
        os._exit(0)
    else:
        # 父进程
        # 关闭连接
        client_conn.close()

上面就是并发服务器的源码,现在服务器父进程唯一的角色就是接受一个新的客户端连接,fork一个新的子进程来处理客户端请求,然后重复接受另一个客户端连接,就没有别的事做啦。服务器父进程不处理客户端请求——它的小弟(子进程)干这事。

上面的服务器会有可能产生僵尸进程,我们改一下

import os
import time
import errno
import signal
import socket

SERVER_ADDRESS = (HOST, PORT) = '', 5000
REQUEST_QUEUE_SIZE = 5


def wait_child(signum, frame):
while True:
    try:
        # 采用os.waitpid 系统调用
        pid, status = os.waitpid(
            -1,  # 等待所有子进程都被处理掉
            os.WNOHANG  # 不阻塞并返回EWOULDBLOCK error
        )

    except OSError as e:
        return
    if pid == 0:
        return


def handle_request(client_conn):
request = client_conn.revc(1024)
print(f"Child pid:{os.getpid()},Parent pid:{os.getppid()}")
print(request.decode())
http_response = b"""
HTTP/1.1 200 OK
Hello world!
"""
client_conn.sendall(http_response)
time.sleep(60)


def server_forever():
# 创建套接字
listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址
listen_sock.bind(SERVER_ADDRESS)
# 设置监听数
listen_sock.listen(REQUEST_QUEUE_SIZE)
print(f"Parent pid:{os.getpid()} ")
# 注册等待函数,父进程退出时,会触发wait_child的函数
# 等子进程执行完
signal.signal(signal.SIGCHLD, wait_child)
# 一直接收
while True:
    try:
        client_conn, client_addr = listen_sock.accept()
    except IOError as e:
        code, msg = e.args
        if code == errno.EINTR:
            continue
        else:
            raise
    # 创建进程
    pid = os.fork()
    if pid == 0:  # 子进程
        # 关闭子进程复制父进程的套接字
        listen_sock.close()
        # 处理请求
        handle_request(client_conn)
        # 处理完,连接关闭
        client_conn.close()
        # 子进程退出
        os._exit(0)
    else:
        # 父进程
        # 关闭连接
        client_conn.close()

参考:https://ruslanspivak.com/lsbaws-part1/
https://ruslanspivak.com/lsba...
https://ruslanspivak.com/lsba...


某个夜晚
11 声望1 粉丝

心之所向,素履以往