Python socket非阻塞recv()异常和sendall()异常

huangapple go评论65阅读模式
英文:

Python socket non-blocking recv() exception(s) and sendall() exception

问题

  1. recv()在没有可读取数据时返回时,会引发哪些异常?

  2. sendall()无法完成(处理整个待发送数据)时,会引发哪些异常?

这是我的代码目前的样子:

try:
    socket1.setblocking(False)
    socket2 setblocking(False)

    while True:
        try:
            sock1_data = socket1.recv(1024)
            if sock1_data:
                socket2.sendall(sock1_data)
        except socket.error as e:
            if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
                raise e

        try:
            sock2_data = socket2.recv(1024)
            if sock2_data:
                socket1.sendall(sock2_data)
        except socket.error as e:
            if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
                raise e
except:
    pass
finally:
    if socket2:
        socket2.close()
    if socket1:
        socket1.close()

我的主要关注点是:
sendall()失败时,我会得到什么socket.error.errno

如果从失败的sendall()中获得的socket.error.errnoEAGAINEWOULDBLOCK,那将会很困扰!另外,当处理非阻塞的recv()时,我是否需要检查EAGAINEWOULDBLOCK两者?

如有需要,我可以提供进一步的信息或解释。

英文:

I'm writing a simple TCP relay server, which is gonna be deployed both on a Windows and a Linux machine (same code base).
Naturally there're gonna be two sockets to work with.
I would like to know which exceptions exactly do get raised for the following cases:

  1. recv() returns when no data is available to read.
  2. sendall() cannot complete (dispose of the whole to-send data)
  • Do I have to check for both errnos (socket.EWOULDBLOCK and socket.EAGAIN) when expecting to return from a non-blocking sockets recv()?
  • Which errno (.args[0]) do I get when sendall() fails?

Here's my code so far:

try:
	socket1.setblocking(False)
	socket2.setblocking(False)

	while True:
		try:
			sock1_data = socket1.recv(1024)
			if sock1_data:
				socket2.sendall(sock1_data)
		except socket.error as e:
			if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
				raise e

		try:
			sock2_data = socket2.recv(1024)
			if sock2_data:
				socket1.sendall(sock2_data)
		except socket.error as e:
			if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
				raise e
except:
	pass
finally:
	if socket2:
		socket2.close()
	if socket1:
		socket1.close()

My main concern is:
What socket.error.errno do I get when sendall() fails?

Lest the socket.error.errno I get from a failing sendall() is EAGAIN or EWOULDBLOCK, in which case it'd be troubling!!

Also, do I have to check for both EAGAIN and EWOULDBLOCK when handling a non-blocking recv()?

答案1

得分: 1

以下是您要翻译的内容:

深入探讨

在CPython中,socket.sendall是由socketmodule.c中的sock_sendall实现的,它通过sock_call_ex间接调用sock_send_implsocket.send也是如此)。最终,这将导致调用系统调用send(...),并相应地设置errno。 sock_call_ex将处理一些潜在错误-它可能会在EINTR上自动重试,并且对于EWOULDBLOCKEAGAIN,如果存在积极的超时,它将重试。

然而,除此之外,socket.sendall可以产生与send相同的大多数错误。有一些与参数错误相关的错误,假设python的socket模块实现没有错误,那应该是不可能的,但其余的错误都是有效的,包括EAGAINEWOULDBLOCK。您可以查阅send可能产生的错误列表的man页面,或查看此在线列表

至于socket.recv,实现在sock_recv_impl中,它直接调用recv,导致类似的情况。再次查看man页面,或在线

简而言之,对于recv(),您确实需要处理EAGAINEWOULDBLOCK,以应对无可用数据的情况,除非您了解您的平台,可以确定它会引发哪个错误。对于sendall(),您需要处理的错误将取决于您想要支持/从中恢复的错误。作为一个极端的例子-如果您的内存用尽,它会尝试满足发送请求,但这是一个极不可能的情况,那时您可能已经有更大的问题。

XY问题

幸运的是,在您的情况下,有一个简单的解决方案,因为您实际上只关心区分非阻塞接收的预期错误和接收或发送期间的实际错误。只需将socket.sendall移到try体外,使用else结构:

while True:
    try:
        sock1_data = socket1.recv(1024)
    except socket.error as e:
        if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
            raise e
    else:  # 仅在没有异常时运行
        if sock1_data:
            socket2.sendall(sock1_data)

    try:
        sock2_data = socket2.recv(1024)
    except socket.error as e:
        if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
            raise e
    else:  # 仅在没有异常时运行
        if sock2_data:
            socket1.sendall(sock2_data)
英文:

A Deep Dive

In CPython, socket.sendall is implemented by sock_sendall in socketmodule.c, which calls sock_send_impl indirectly via sock_call_ex (as does socket.send too). Ultimately, this results in calling the system call send(...), which will set errno accordingly. sock_call_ex will handle some of the potential errors- it may automatically retry on EINTR, and for EWOULDBLOCK and EAGAIN it will retry if there's a positive timeout.

Apart from that, however, socket.sendall can by and large produce most of the same errors that send can. There are a few, related to malformed arguments that should be impossible, assuming no bugs in python's socket module implementation, but the rest are valid, including EAGAIN and EWOULDBLOCK. You can consult the man pages for a list of errors that send can produce, or see this online list.

As for socket.recv, the implementation is at sock_recv_impl, which calls recv directly, resulting in a similar situation. Again, see the man pages, or online.

In short, for recv(), you indeed need to handle both EAGAIN and EWOULDBLOCK for no data available, unless you know your platform so you can determine which it will raise. For sendall(), the errors you need to handle will depend on the errors you want to support / recover from. As an extreme example- you could get ENOMEM if you ran out of memory as it tries to fulfill the send request, however that's an extremely unlikely scenario, and you would already have bigger problems at that point.

XY Problem

Luckily, in your case there's an easy solution, since you're really just concerned with distinguishing the expected errors on nonblocking recv from actual errors during either recv or send. Just move the socket.sendall out of the try body, using the else construct:

while True:
    try:
        sock1_data = socket1.recv(1024)
    except socket.error as e:
        if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
            raise e
    else:  # only runs if there was no exception
        if sock1_data:
            socket2.sendall(sock1_data)

    try:
        sock2_data = socket2.recv(1024)
    except socket.error as e:
        if e.args[0] != socket.EAGAIN and e.args[0] != socket.EWOULDBLOCK:
            raise e
    else:  # only runs if there was no exception
        if sock2_data:
            socket1.sendall(sock2_data)

</details>



huangapple
  • 本文由 发表于 2023年2月16日 06:37:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/75466064.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定