python 中的 select 和 ssl

发布于 2024-09-08 01:45:49 字数 306 浏览 2 评论 0原文

我有一个使用 select.select() 的服务器应用程序,现在我尝试向其中添加 SSL,但是在侦听“原始”套接字时出现以下错误:

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

所以我想我应该使用 ssl而是由 ssl.wrap_socket 在 select 中返回的流。这样做,它不会返回任何错误,但也不起作用 - 我不太确定问题是什么,我做了很多研究并遇到了类似问题的帖子,但我发现没有解决这个问题还没有。

非常感谢任何帮助。

I've got a server application using select.select(), and now I'm trying to add SSL to it, however I get the following error when listening to the "raw" sockets:

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

so I figured I'd use the ssl streams returned by ssl.wrap_socket in select instead. Doing so, it doesn't return any errors but it doesn't work either - I'm not really sure what the problem is, I've done a lot of research and encountered posts with similar problems, but I've found no solution to this yet.

Really appreciate any help.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

Bonjour°[大白 2024-09-15 01:45:49

将 SSL 套接字与 select() 一起使用并不像乍看起来那么简单。虽然它们工作得很好,因为当你给它一个错误时它不会抛出错误,但如果你只是像普通套接字一样使用它们,你迟早会遇到一些奇怪的情况。

由于 select() 需要文件描述符,因此它将获取原始套接字。但即使原始套接字变得可读,也不意味着您将从 SSL 套接字获取数据。您需要使用非阻塞套接字(无论如何,在使用 select() 时这是一个好主意),并且如果它抛出 SSL_ERROR_WANT_READ (SSL 相当于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)

Using SSL sockets with select() isn't as straightforward as it may seem at first. While they work okay with it in the sense that it doesn't throw an error when you give it one, if you just use them like normal sockets, you're bound to bump into some weirdness sooner or later.

Since select() needs a file descriptor, it's going to get the raw socket. But even if the raw socket becomes readable, that doesn't mean you will get data out of the SSL socket. You'll need to use non-blocking sockets (which is a good idea anyway when using select()) and just ignore it if it throws SSL_ERROR_WANT_READ (the SSL equivalent of EWOULDBLOCK).

Another problem is, if you write 2048 bytes to the connection at the other end, the select() on your end returns. But if you then only read 1024 bytes from the SSL socket, it is possible that the SSL socket internally reads more data, and the next select() won't return even though there would be more data to read, possibly deadlocking the connection. This is because the raw socket, which is what select() is using, doesn't have any data since it's already in the SSL socket's buffers.

The first solution that comes to mind would be to read more data until reading throws SSL_ERROR_WANT_READ, thus emptying the buffer. However, if the other end generates data faster than you can process it, this would end up starving all your other connections until this one finishes generating data.

You can see how much buffered data the SSL socket is holding by calling sslsock.pending(). A better approach, then, would be to first do one read for some amount of data, check the amount of pending data and issue a second read for exactly that amount of data, thus emptying the buffer without causing any more reads.

The man-page for SSL_pending() (the C function behind the scenes) also says this:

SSL_pending() takes into account only bytes from the TLS/SSL record that is currently being processed (if any). If the SSL object's read_ahead flag is set, additional protocol bytes may have been read containing more TLS/SSL records; these are ignored by SSL_pending()

From what I understand, this means that if read_ahead is set, you'd need to repeat the second step until SSL_pending() returns 0. I'm pretty sure python doesn't set read_ahead, but it's better be safe than sorry, so I've included the loop in the example code.

I'm not that familiar with this, but something like this should work:

# 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)
怪异←思 2024-09-15 01:45:49

正如 Marius 指出的那样, select.select() 适用于 SSL 套接字,我仍然不知道是什么导致了我的无声错误,但当我认为它是 SSL + select() 时,我就急了。这样这个问题就得到了解答。

As Marius pointed out, select.select() works with SSL sockets, I still do not know what caused my silent error but I jumped my gun when thinking it was SSL + select(). Thus this question is answered.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文