Python中的选择与SSL

4 投票
2 回答
6361 浏览
提问于 2025-04-16 00:53

我有一个服务器应用程序,使用了select.select(),现在我想给它加上SSL(安全套接层),但是在监听“原始”套接字时遇到了以下错误:

ValueError: file descriptor cannot be a negative integer (-1)

所以我想用ssl.wrap_socket返回的ssl流来替代select。这样做的话没有报错,但也没有效果——我不太确定问题出在哪里,我查了很多资料,看到过一些类似的问题,但到现在为止还没有找到解决办法。

非常感谢任何帮助。

2 个回答

17

使用SSL套接字和select()函数并不像看起来那么简单。虽然它们在某种程度上可以正常工作,也就是说,当你给它一个套接字时不会报错,但如果你像使用普通套接字那样使用它们,迟早会遇到一些奇怪的问题。

因为select()需要一个文件描述符,所以它会获取原始套接字。但是,即使原始套接字变得可读,这并不意味着你能从SSL套接字中读取到数据。你需要使用非阻塞套接字(这在使用select()时是个好主意),如果出现SSL_ERROR_WANT_READ的错误,就可以忽略它(这个错误类似于EWOULDBLOCK)。

另一个问题是,如果你向另一端写入2048字节的数据,select()在你这边会返回。但如果你只从SSL套接字读取1024字节,可能SSL套接字内部会读取更多数据,而下一个select()不会返回,即使还有更多数据可以读取,这可能会导致连接死锁。这是因为select()使用的原始套接字没有数据,因为数据已经在SSL套接字的缓冲区中了。

第一个想到的解决方案是继续读取数据,直到出现SSL_ERROR_WANT_READ的错误,从而清空缓冲区。然而,如果另一端生成数据的速度比你处理的速度快,这样做会导致你所有其他连接都被饿死,直到这个连接生成数据完成。

你可以通过调用sslsock.pending()来查看SSL套接字中有多少缓冲数据。那么,更好的方法是,先读取一定量的数据,检查待处理的数据量,然后再精确读取那部分数据,从而清空缓冲区,而不需要进行更多的读取。

关于SSL_pending()(背后实际使用的C函数)的手册页也提到:

SSL_pending()只考虑当前正在处理的TLS/SSL记录中的字节(如果有的话)。如果SSL对象的read_ahead标志被设置,可能会读取到包含更多TLS/SSL记录的额外协议字节;这些会被SSL_pending()忽略。

根据我的理解,这意味着如果read_ahead被设置,你需要重复第二步,直到SSL_pending()返回0。我不太确定Python是否设置了read_ahead,但为了安全起见,我在示例代码中包含了这个循环。

我对这个不是很熟悉,但类似这样的代码应该可以工作:

# Put the SSL socket to non-blocking mode
sslsock.setblocking(0)

while True:
    r, w, e = select.select([sslsock], [], [])
    if sslsock in r:
        try:
            data = sslsock.recv(1024)
        except ssl.SSLError as e:
            # Ignore the SSL equivalent of EWOULDBLOCK, but re-raise other errors
            if e.errno != ssl.SSL_ERROR_WANT_READ:
                raise
            continue
        # No data means end of file
        if not data:
            break
        # Drain the SSL socket's internal buffer.
        # If you want to remove the loop, make sure you don't call recv()
        # with a 0 length, since that could cause a read to the raw socket.
        data_left = sslsock.pending()
        while data_left:
            data += sslsock.recv(data_left)
            data_left = sslsock.pending()
        # Process the data
        process(data)
-4

正如Marius提到的,select.select()可以和SSL套接字一起使用。我还是不太清楚是什么导致了我那个没有提示的错误,但我在想是SSL和select()一起用的时候出了问题,这样看来这个问题已经解决了。

撰写回答