我有一个使用select.select()的服务器应用程序,现在我正在尝试向其添加SSL,但是在收听"原始"套接字时出现以下错误:
ValueError: file descriptor cannot be a negative integer (-1)
Run Code Online (Sandbox Code Playgroud)
所以我想我会在select中使用ssl.wrap_socket返回的ssl流.这样做,它不会返回任何错误但它也不起作用 - 我不确定问题是什么,我做了很多研究并遇到了类似问题的帖子,但我发现没有对此的解决方案.
真的很感激任何帮助.
Ale*_*amo 14
使用SSL套接字select()并不像最初看起来那么简单.虽然它们可以正常工作,因为当你给它一个时它不会抛出错误,如果你只是像普通套接字一样使用它们,你迟早会遇到一些奇怪的问题.
由于select()需要文件描述符,它将获得原始套接字.但即使原始套接字变得可读,这并不意味着您将从SSL套接字中获取数据.你需要使用非阻塞套接字(这在使用时无论如何都是个好主意select()),如果它抛出则忽略它SSL_ERROR_WANT_READ(SSL相当于EWOULDBLOCK).
另一个问题是,如果你在另一端写入2048字节到连接,select()你的结尾就会返回.但是,如果您只读取SSL套接字中的1024个字节,则SSL套接字可能会在内部读取更多数据,select()即使有更多数据要读取,下一个也不会返回,可能会使连接死锁.这是因为select()正在使用的原始套接字没有任何数据,因为它已经在SSL套接字的缓冲区中.
想到的第一个解决方案是读取更多数据直到读取抛出SSL_ERROR_WANT_READ,从而清空缓冲区.但是,如果另一端生成数据的速度超过了处理速度,那么最终会使所有其他连接都处于饥饿状态,直到完成数据生成.
您可以通过调用查看SSL套接字持有的缓冲数据量sslsock.pending().那么,更好的方法是首先对一些数据量进行一次读取,检查待处理数据的数量,并为确切的数据量发出第二次读取,从而清空缓冲区而不会导致更多的读取.
SSL_pending()(幕后的C函数)的man-page 也说:
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)
Run Code Online (Sandbox Code Playgroud)