1. Python socket 模块使用

Python Socket 编程详细介绍 这篇文章讲解的非常好.

先看下面一段代码:

import socket

HOST, PORT = '', 8888

listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listen_socket.bind((HOST, PORT))
listen_socket.listen(1)
print('Serving HTTP on port %s ...' % PORT)
while True:
    client_connection, client_address = listen_socket.accept()
    request = client_connection.recv(1024)
    print(type(request))
    print(request)

    http_response = b"""\
HTTP/1.1 200 OK

Hello, World!
"""
    client_connection.sendall(http_response)
    client_connection.close()

浏览器访问 localhost:8888 会展示出 ‘Hello, World!’.

一般 TCP 服务器设计的步骤如下:

  • 创建 socket, 绑定 ip 和 port socket() bind()
  • 设置监听链接 listen()
  • 循环, 监听客户端请求 while True: listen_socket.accept()
  • 接收请求 recv()
  • 返回请求 sendall()
  • 关闭连接 close()

客户端链接步骤:

  • 创建 socket
  • connect()
  • sendall()
  • recv()
  • close()

上面例子代码中, 浏览器访问地址, 就是模拟了客户端的操作, 注意到 http_response 返回内容:

HTTP/1.1 200 OK

Hello, World!

内容包括3个部分:

  1. HTTP version
  2. HTTP status code
  3. HTTP response body

2. 使用 socket 实现一个 WebServer

在重复造轮子前, 先回顾一下 server 怎么使用的:

server = XXServer(ip, port, callbac k)
server.start()

也就是说, XXServer 需要将上面的代码包装一层. 实现代码如下:

class SixServer(object):
    def __init__(self, host='127.0.0.1', port=8888, callback=None):
        self.host = host
        self.port = port
        self.callback = callback

    def _init_socket(self):
        import socket
        self.listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.listen_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.listen_socket.bind(('', self.port))
        self.listen_socket.listen(5)

    def start(self):
        self._init_socket()
        print('Serving HTTP on port %s:%s ...' % (self.host, self.port))
        while True:
            self.client_connection, client_address = self.listen_socket.accept()
            try:
                self._handle_request()
                self._handle_response()
            except Exception:
                pass
            finally:
                self.client_connection.close()

    def _handle_request(self):
        raw_request = self.client_connection.recv(1024)
        self._parse_request(raw_request)
        print(self.request)

    def _parse_request(self, raw_request):
        self.request = {}
        # 默认类型是 byte, 需要转码
        raw_request = raw_request.decode('utf-8')
        request_line = raw_request.splitlines()[0]
        # Break down the request line into components
        (self.request['request_method'],  # GET
         self.request['path'],  # /hello
         self.request['request_version']  # HTTP/1.1
         ) = request_line.split()

    def _handle_response(self):
        status = self.request['request_version'] + ' 200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.raw_response = self.callback(self.request)
        # sep 需要输入两个 \n, 一个是不生效的, 怀疑是 encode 的原因.
        sep = '\r\n\n'
        http_response = ''
        # 添加 http 协议 http 状态码
        http_response += status + sep
        # 丰富一下 headers
        for header in response_headers:
            http_response += '{0}:{1}'.format(*header) + sep
        # 添加 回调函数处理后的内容
        for res in self.raw_response:
            http_response = http_response + '{0}: {1}'.format(res, self.raw_response[res]) + sep
       
        self.response = http_response
        print(self.response)
        # http_response = """HTTP/1.1 200 OK\r\n
        # <b>nmhai</b>"""
        self.client_connection.sendall(http_response.encode())

2.1 代码说明

函数 _parse_request 用来处理入参, 比如访问 http://localhost:8888/cdasfd?key=whoami, self.request 解析如下:

{'path': '/cdasfd?key=whoami', 'request_version': 'HTTP/1.1', 'request_method': 'GET'}

函数 _handle_response 用来包装 response, 一个标准的 response 上面已经提到过, HTTP 版本号 + HTTP 状态码, 后面跟的分隔符, 要使用 ‘\r\n\n’, 只使用 ‘\r\n’ 浏览器识别不出来.

# sep 需要输入两个 \n, 一个是不生效的, 怀疑是 encode 的原因.
sep = '\r\n\n'

2.2 代码调用

首先写个回调函数, 这个函数啥也不做, 直接返回

def callback(text):
    return text

调用结果截图如下:

def callback(text):
    return text

six_server = SixServer(callback=callback)
six_server.start()

这样, 一个简单的 WebServer 就写完了, 实际使用中, callback() 函数主要是拼装 html 代码, 类似上面注释掉的两行, 浏览器可以直接将标签渲染出来.

    http_response = """HTTP/1.1 200 OK\r\n
    <b>nmhai</b>"""

3. 参考文档

END



blog comments powered by Disqus

Published

20 November 2017

Tags