Python中的选择与SSL
我有一个服务器应用程序,使用了select.select(),现在我想给它加上SSL(安全套接层),但是在监听“原始”套接字时遇到了以下错误:
ValueError: file descriptor cannot be a negative integer (-1)
所以我想用ssl.wrap_socket返回的ssl流来替代select。这样做的话没有报错,但也没有效果——我不太确定问题出在哪里,我查了很多资料,看到过一些类似的问题,但到现在为止还没有找到解决办法。
非常感谢任何帮助。
2 个回答
使用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)
正如Marius提到的,select.select()可以和SSL套接字一起使用。我还是不太清楚是什么导致了我那个没有提示的错误,但我在想是SSL和select()一起用的时候出了问题,这样看来这个问题已经解决了。