Python socket non-blocking with SSL 的问题

最近要直接用Socket做一个简单的Server,想使用non-blocking的Scoket,但是遇到一些问题,解决了所以在这里总结一下。

简单的Server端代码片段(只有接受数据的):

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import ssl
import select
import socket

DEFAULT_SERVER_HOST = "0.0.0.0"
DEFAULT_SERVER_PORT = 14443

class Server(object):

    def __init__(self, host, port, is_ssl=False, cert_file=None, key_file=None):
        self.host = host
        self.port = port
        self.is_ssl = is_ssl
        self.cert_file = cert_file
        self.key_file = key_file
        self.context = None

        self.__socket = None
        self.running = False
        self.multiplex = None

        self.read_set = set()
        self.write_set = set()
        self.error_set = set()

    def __initialize(self):

        if self.is_ssl and (self.cert_file is None or self.key_file is None):
            raise Exception("If you want to enable ssl, please set cert_file and key_file")

        if self.is_ssl:
            self.context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
            self.context.load_cert_chain(certfile=self.cert_file, keyfile=self.key_file)

        self.__socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.__socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT & socket.SO_REUSEADDR, 1)

        self.__socket.bind((self.host if self.host is not None else DEFAULT_SERVER_HOST,
                            self.port if self.port is not None else DEFAULT_SERVER_PORT))

        self.__socket.setblocking(0)
        self.__socket.listen(5)

    def start(self):
        self.__initialize()

        server_fd = self.__socket.fileno()
        self.read_set.add(server_fd)

        while True:
            read_list, write_list, error_list = select.select(self.read_set, self.write_set, self.error_set, 2)

            if server_fd in read_list:
                conn, addr = self.__socket.accept()
                conn.setblocking(0)
                if self.is_ssl:
                    conn = self.context.wrap_socket(conn, server_side=True, do_handshake_on_connect=False)
                    i = 0

                    while True:
                        i += 1
                        print(i)
                        try:
                            conn.do_handshake()
                            select.select([conn], [], [])
                            break
                        except ssl.SSLError as err:
                            if err.args[0] == ssl.SSL_ERROR_WANT_READ:
                                print("read")
                                select.select([conn], [], [])
                            elif err.args[0] == ssl.SSL_ERROR_WANT_WRITE:
                                print("write")
                                select.select([], [conn], [])
                            else:
                                raise

                # rfile = conn.makefile("rb")
                # a = rfile.read(1024*8)
                a = conn.recv(1024*8)
                print(a)

if __name__ == "__main__":
    cs = Server("0.0.0.0", 14443, True, "snakeoil.crt", "snakeoil.key")
    cs.start()

由于self.__socket.setblocking(0) conn.setblocking(0)都设置为非阻塞,所以conn = self.context.wrap_socket(conn, server_side=True, do_handshake_on_connect=False) 不能设置为连接时自动握手。

在成功握手后,发现一个问题,调用

rfile = conn.makefile("rb")
a = rfile.read(1024*8)

如果读取范围较大,会出现

Traceback (most recent call last):
  File "/home/ming/Application/pycharm-2017.1.4/helpers/pydev/pydevd.py", line 1591, in <module>
    globals = debugger.run(setup['file'], None, None, is_module)
  File "/home/ming/Application/pycharm-2017.1.4/helpers/pydev/pydevd.py", line 1018, in run
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/home/ming/Application/pycharm-2017.1.4/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/home/ming/PycharmProjects/Test/server.py", line 86, in <module>
    cs.start()
  File "/home/ming/PycharmProjects/Test/server.py", line 80, in start
    a = rfile.read(1024*8)
  File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/socket.py", line 576, in readinto
    return self._sock.recv_into(b)
  File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/ssl.py", line 937, in recv_into
    return self.read(nbytes, buffer)
  File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/ssl.py", line 799, in read
    return self._sslobj.read(len, buffer)
  File "/home/ming/.pyenv/versions/3.5.3/lib/python3.5/ssl.py", line 583, in read
    v = self._sslobj.read(len, buffer)
ssl.SSLWantReadError: The operation did not complete (read) (_ssl.c:2090)

这个错误,表示读未完成。 而使用a = conn.recv(1024*8)则不会发生错误。

发现使用makefile()后的读操作,将会多次调用ssl.pySSLSocket.classrecv_into方法,最后到ssl.pySSLObject

    def read(self, len=1024, buffer=None):
        """Read up to 'len' bytes from the SSL object and return them.

        If 'buffer' is provided, read into this buffer and return the number of
        bytes read.
        """
        if buffer is not None:
            v = self._sslobj.read(len, buffer)    //makefile 后执行这句
        else:
            v = self._sslobj.read(len)
        return v

,直至错误出现。

而直接使用socketread方法,则是直接调用ssl.pySSLObject

    def read(self, len=1024, buffer=None):
        """Read up to 'len' bytes from the SSL object and return them.

        If 'buffer' is provided, read into this buffer and return the number of
        bytes read.
        """
        if buffer is not None:
            v = self._sslobj.read(len, buffer)   
        else:
            v = self._sslobj.read(len) // socket 的read执行这句
        return v

更深入的原因还没找出,目前觉得应该是makefile后把socket当成文件读取,会尝试读直至无法继续读取,所以才会导致错误发生。

转载于:https://my.oschina.net/jianming/blog/1600714

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值