Python socket non-blocking with SSL 的問題

最近要直接用Socket做一個簡單的Server,想使用non-blocking的Scoket,但是遇到一些問題,解決了所以在這里總結一下。

簡單的Server端代碼片段(只有接受數(shù)據(jù)的):

#!/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) 不能設置為連接時自動握手。

在成功握手后,發(fā)現(xiàn)一個問題,調用

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

如果讀取范圍較大,會出現(xiàn)

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)則不會發(fā)生錯誤。

發(fā)現(xiàn)使用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 后執(zhí)行這句
        else:
            v = self._sslobj.read(len)
        return v

,直至錯誤出現(xiàn)。

而直接使用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執(zhí)行這句
        return v

更深入的原因還沒找出,目前覺得應該是makefile后把socket當成文件讀取,會嘗試讀直至無法繼續(xù)讀取,所以才會導致錯誤發(fā)生。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • iPhone的標準推薦是CFNetwork 庫編程,其封裝好的開源庫是 cocoa AsyncSocket庫,用它...
    Ethan_Struggle閱讀 2,277評論 2 12
  • 最近在學習Python看了一篇文章寫得不錯,是在腳本之家里的,原文如下,很有幫助: 一、網絡知識的一些介紹 soc...
    qtruip閱讀 2,747評論 0 6
  • 代碼不好排版,可以到微信訂閱號(xuanhun521)查看原文。 Python黑帽編程2.8套接字編程 套接字編程...
    玄魂閱讀 666評論 1 2
  • 大綱 一.Socket簡介 二.BSD Socket編程準備 1.地址 2.端口 3.網絡字節(jié)序 4.半相關與全相...
    VD2012閱讀 2,436評論 0 5
  • 今天我們畫第四個公主,難度在慢慢增加,你發(fā)現(xiàn)了嗎? 自動鉛筆打底稿。應該已經很熟練了。大家有沒有發(fā)現(xiàn),為了降低難度...
    Ann苳杭杭閱讀 6,140評論 31 174