From e8447768ef18c0ce959ca3b2abe8f4a334b41056 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 May 2025 15:02:02 +1000 Subject: [PATCH 1/6] extmod/modlwip: Add optional flags argument for recv and recvfrom. Implements MSG_PEEK and MSG_DONTWAIT. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/modlwip.c | 140 +++++++++++++++++++++-------------------------- 1 file changed, 62 insertions(+), 78 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 961803f5e84dd..65a3412ecb801 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -70,6 +70,10 @@ #define TCP_NODELAY TF_NODELAY +// Socket flags +#define MSG_PEEK 0x01 +#define MSG_DONTWAIT 0x02 + // For compatibilily with older lwIP versions. #ifndef ip_set_option #define ip_set_option(pcb, opt) ((pcb)->so_options |= (opt)) @@ -673,13 +677,13 @@ static mp_uint_t lwip_raw_udp_send(lwip_socket_obj_t *socket, const byte *buf, m } // Helper function for recv/recvfrom to handle raw/UDP packets -static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, ip_addr_t *ip, mp_uint_t *port, int *_errno) { +static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, mp_int_t flags, ip_addr_t *ip, mp_uint_t *port, int *_errno) { lwip_incoming_packet_t *slot = &socket->incoming.udp_raw.array[socket->incoming.udp_raw.iget]; if (slot->pbuf == NULL) { - if (socket->timeout == 0) { - // Non-blocking socket. + // Non-blocking socket or flag + if (socket->timeout == 0 || (flags & MSG_DONTWAIT)) { *_errno = MP_EAGAIN; return -1; } @@ -705,9 +709,11 @@ static mp_uint_t lwip_raw_udp_receive(lwip_socket_obj_t *socket, byte *buf, mp_u MICROPY_PY_LWIP_ENTER u16_t result = pbuf_copy_partial(p, buf, ((p->tot_len > len) ? len : p->tot_len), 0); - pbuf_free(p); - slot->pbuf = NULL; - socket->incoming.udp_raw.iget = (socket->incoming.udp_raw.iget + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; + if ((flags & MSG_PEEK) == 0) { + pbuf_free(p); + slot->pbuf = NULL; + socket->incoming.udp_raw.iget = (socket->incoming.udp_raw.iget + 1) % LWIP_INCOMING_PACKET_QUEUE_LEN; + } MICROPY_PY_LWIP_EXIT @@ -815,14 +821,14 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui } // Helper function for recv/recvfrom to handle TCP packets -static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, int *_errno) { +static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_t len, mp_int_t flags, int *_errno) { // Check for any pending errors STREAM_ERROR_CHECK(socket); if (socket->incoming.tcp.pbuf == NULL) { - // Non-blocking socket - if (socket->timeout == 0) { + // Non-blocking socket or flag + if (socket->timeout == 0 || (flags & MSG_DONTWAIT)) { if (socket->state == STATE_PEER_CLOSED) { return 0; } @@ -867,19 +873,21 @@ static mp_uint_t lwip_tcp_receive(lwip_socket_obj_t *socket, byte *buf, mp_uint_ memcpy(buf, (byte *)p->payload + socket->recv_offset, len); - remaining -= len; - if (remaining == 0) { - socket->incoming.tcp.pbuf = p->next; - // If we don't ref here, free() will free the entire chain, - // if we ref, it does what we need: frees 1st buf, and decrements - // next buf's refcount back to 1. - pbuf_ref(p->next); - pbuf_free(p); - socket->recv_offset = 0; - } else { - socket->recv_offset += len; + if ((flags & MSG_PEEK) == 0) { + remaining -= len; + if (remaining == 0) { + socket->incoming.tcp.pbuf = p->next; + // If we don't ref here, free() will free the entire chain, + // if we ref, it does what we need: frees 1st buf, and decrements + // next buf's refcount back to 1. + pbuf_ref(p->next); + pbuf_free(p); + socket->recv_offset = 0; + } else { + socket->recv_offset += len; + } + tcp_recved(socket->pcb.tcp, len); } - tcp_recved(socket->pcb.tcp, len); MICROPY_PY_LWIP_EXIT @@ -1271,40 +1279,58 @@ static mp_obj_t lwip_socket_send(mp_obj_t self_in, mp_obj_t buf_in) { } static MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_send_obj, lwip_socket_send); -static mp_obj_t lwip_socket_recv(mp_obj_t self_in, mp_obj_t len_in) { - lwip_socket_obj_t *socket = MP_OBJ_TO_PTR(self_in); +// Common implementation for recv & recvfrom +static mp_obj_t lwip_socket_recv_common(size_t n_args, const mp_obj_t *args, ip_addr_t *ip, mp_uint_t *port) { + lwip_socket_obj_t *socket = MP_OBJ_TO_PTR(args[0]); + mp_int_t len = mp_obj_get_int(args[1]); + mp_int_t flags = n_args > 2 ? mp_obj_get_int(args[2]) : 0; int _errno; + vstr_t vstr; + mp_uint_t ret = 0; lwip_socket_check_connected(socket); - mp_int_t len = mp_obj_get_int(len_in); - vstr_t vstr; vstr_init_len(&vstr, len); - mp_uint_t ret = 0; switch (socket->type) { - case MOD_NETWORK_SOCK_STREAM: { - ret = lwip_tcp_receive(socket, (byte *)vstr.buf, len, &_errno); + case MOD_NETWORK_SOCK_STREAM: + if (ip != NULL) { + *ip = socket->tcp_peer_addr; + *port = (mp_uint_t)socket->tcp_peer_port; + } + ret = lwip_tcp_receive(socket, (byte *)vstr.buf, len, flags, &_errno); break; - } case MOD_NETWORK_SOCK_DGRAM: #if MICROPY_PY_LWIP_SOCK_RAW case MOD_NETWORK_SOCK_RAW: #endif - ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, NULL, NULL, &_errno); + ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, flags, ip, port, &_errno); break; } if (ret == -1) { mp_raise_OSError(_errno); } - if (ret == 0) { return mp_const_empty_bytes; } vstr.len = ret; return mp_obj_new_bytes_from_vstr(&vstr); } -static MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_recv_obj, lwip_socket_recv); + +static mp_obj_t lwip_socket_recv(size_t n_args, const mp_obj_t *args) { + return lwip_socket_recv_common(n_args, args, NULL, NULL); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(lwip_socket_recv_obj, 2, 3, lwip_socket_recv); + +static mp_obj_t lwip_socket_recvfrom(size_t n_args, const mp_obj_t *args) { + ip_addr_t ip; + mp_uint_t port; + mp_obj_t tuple[2]; + tuple[0] = lwip_socket_recv_common(n_args, args, &ip, &port); + tuple[1] = lwip_format_inet_addr(&ip, port); + return mp_obj_new_tuple(2, tuple); +} +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(lwip_socket_recvfrom_obj, 2, 3, lwip_socket_recvfrom); static mp_obj_t lwip_socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t addr_in) { lwip_socket_obj_t *socket = MP_OBJ_TO_PTR(self_in); @@ -1339,50 +1365,6 @@ static mp_obj_t lwip_socket_sendto(mp_obj_t self_in, mp_obj_t data_in, mp_obj_t } static MP_DEFINE_CONST_FUN_OBJ_3(lwip_socket_sendto_obj, lwip_socket_sendto); -static mp_obj_t lwip_socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { - lwip_socket_obj_t *socket = MP_OBJ_TO_PTR(self_in); - int _errno; - - lwip_socket_check_connected(socket); - - mp_int_t len = mp_obj_get_int(len_in); - vstr_t vstr; - vstr_init_len(&vstr, len); - ip_addr_t ip; - mp_uint_t port; - - mp_uint_t ret = 0; - switch (socket->type) { - case MOD_NETWORK_SOCK_STREAM: { - ip = socket->tcp_peer_addr; - port = (mp_uint_t)socket->tcp_peer_port; - ret = lwip_tcp_receive(socket, (byte *)vstr.buf, len, &_errno); - break; - } - case MOD_NETWORK_SOCK_DGRAM: - #if MICROPY_PY_LWIP_SOCK_RAW - case MOD_NETWORK_SOCK_RAW: - #endif - ret = lwip_raw_udp_receive(socket, (byte *)vstr.buf, len, &ip, &port, &_errno); - break; - } - if (ret == -1) { - mp_raise_OSError(_errno); - } - - mp_obj_t tuple[2]; - if (ret == 0) { - tuple[0] = mp_const_empty_bytes; - } else { - vstr.len = ret; - tuple[0] = mp_obj_new_bytes_from_vstr(&vstr); - } - tuple[1] = lwip_format_inet_addr(&ip, port); - - return mp_obj_new_tuple(2, tuple); -} -static MP_DEFINE_CONST_FUN_OBJ_2(lwip_socket_recvfrom_obj, lwip_socket_recvfrom); - static mp_obj_t lwip_socket_sendall(mp_obj_t self_in, mp_obj_t buf_in) { lwip_socket_obj_t *socket = MP_OBJ_TO_PTR(self_in); lwip_socket_check_connected(socket); @@ -1542,12 +1524,12 @@ static mp_uint_t lwip_socket_read(mp_obj_t self_in, void *buf, mp_uint_t size, i switch (socket->type) { case MOD_NETWORK_SOCK_STREAM: - return lwip_tcp_receive(socket, buf, size, errcode); + return lwip_tcp_receive(socket, buf, size, 0, errcode); case MOD_NETWORK_SOCK_DGRAM: #if MICROPY_PY_LWIP_SOCK_RAW case MOD_NETWORK_SOCK_RAW: #endif - return lwip_raw_udp_receive(socket, buf, size, NULL, NULL, errcode); + return lwip_raw_udp_receive(socket, buf, size, 0, NULL, NULL, errcode); } // Unreachable return MP_STREAM_ERROR; @@ -1919,6 +1901,8 @@ static const mp_rom_map_elem_t mp_module_lwip_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_IPPROTO_TCP), MP_ROM_INT(IP_PROTO_TCP) }, { MP_ROM_QSTR(MP_QSTR_TCP_NODELAY), MP_ROM_INT(TCP_NODELAY) }, + { MP_ROM_QSTR(MP_QSTR_MSG_PEEK), MP_ROM_INT(MSG_PEEK) }, + { MP_ROM_QSTR(MP_QSTR_MSG_DONTWAIT), MP_ROM_INT(MSG_DONTWAIT) }, }; static MP_DEFINE_CONST_DICT(mp_module_lwip_globals, mp_module_lwip_globals_table); From 4781cde4f78ca386c99bb63cb1ad238f8aff7731 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 May 2025 15:04:37 +1000 Subject: [PATCH 2/6] esp32/modsocket: Add optional flags argument for recv and recvfrom. Implements MSG_PEEK and MSG_DONTWAIT (both passed through to LWIP sockets API). This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/modsocket.c | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 7ea5e855d325e..2050d1d04d4d6 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -611,7 +611,7 @@ static MP_DEFINE_CONST_FUN_OBJ_2(socket_setblocking_obj, socket_setblocking); // XXX this can end up waiting a very long time if the content is dribbled in one character // at a time, as the timeout resets each time a recvfrom succeeds ... this is probably not // good behaviour. -static mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, +static mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, mp_int_t flags, struct sockaddr *from, socklen_t *from_len, int *errcode) { socket_obj_t *sock = MP_OBJ_TO_PTR(self_in); @@ -645,7 +645,7 @@ static mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, if (release_gil) { MP_THREAD_GIL_EXIT(); } - int r = lwip_recvfrom(sock->fd, buf, size, 0, from, from_len); + int r = lwip_recvfrom(sock->fd, buf, size, flags, from, from_len); if (release_gil) { MP_THREAD_GIL_ENTER(); } @@ -655,7 +655,7 @@ static mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, if (r >= 0) { return r; } - if (errno != EWOULDBLOCK) { + if (errno != EWOULDBLOCK || (flags & MSG_DONTWAIT)) { *errcode = errno; return MP_STREAM_ERROR; } @@ -666,14 +666,17 @@ static mp_uint_t _socket_read_data(mp_obj_t self_in, void *buf, size_t size, return MP_STREAM_ERROR; } -mp_obj_t _socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in, +mp_obj_t _socket_recvfrom(size_t n_args, const mp_obj_t *args, struct sockaddr *from, socklen_t *from_len) { - size_t len = mp_obj_get_int(len_in); + mp_obj_t self_in = args[0]; + size_t len = mp_obj_get_int(args[1]); + int flags = n_args > 2 ? mp_obj_get_int(args[2]) : 0; + vstr_t vstr; vstr_init_len(&vstr, len); int errcode; - mp_uint_t ret = _socket_read_data(self_in, vstr.buf, len, from, from_len, &errcode); + mp_uint_t ret = _socket_read_data(self_in, vstr.buf, len, flags, from, from_len, &errcode); if (ret == MP_STREAM_ERROR) { mp_raise_OSError(errcode); } @@ -682,17 +685,17 @@ mp_obj_t _socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in, return mp_obj_new_bytes_from_vstr(&vstr); } -static mp_obj_t socket_recv(mp_obj_t self_in, mp_obj_t len_in) { - return _socket_recvfrom(self_in, len_in, NULL, NULL); +static mp_obj_t socket_recv(size_t n_args, const mp_obj_t *args) { + return _socket_recvfrom(n_args, args, NULL, NULL); } -static MP_DEFINE_CONST_FUN_OBJ_2(socket_recv_obj, socket_recv); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recv_obj, 2, 3, socket_recv); -static mp_obj_t socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { +static mp_obj_t socket_recvfrom(size_t n_args, const mp_obj_t *args) { struct sockaddr from; socklen_t fromlen = sizeof(from); mp_obj_t tuple[2]; - tuple[0] = _socket_recvfrom(self_in, len_in, &from, &fromlen); + tuple[0] = _socket_recvfrom(n_args, args, &from, &fromlen); uint8_t *ip = (uint8_t *)&((struct sockaddr_in *)&from)->sin_addr; mp_uint_t port = lwip_ntohs(((struct sockaddr_in *)&from)->sin_port); @@ -700,7 +703,7 @@ static mp_obj_t socket_recvfrom(mp_obj_t self_in, mp_obj_t len_in) { return mp_obj_new_tuple(2, tuple); } -static MP_DEFINE_CONST_FUN_OBJ_2(socket_recvfrom_obj, socket_recvfrom); +static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_recvfrom_obj, 2, 3, socket_recvfrom); int _socket_send(socket_obj_t *sock, const char *data, size_t datalen) { int sentlen = 0; @@ -789,7 +792,7 @@ static mp_obj_t socket_makefile(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(socket_makefile_obj, 1, 3, socket_makefile); static mp_uint_t socket_stream_read(mp_obj_t self_in, void *buf, mp_uint_t size, int *errcode) { - return _socket_read_data(self_in, buf, size, NULL, NULL, errcode); + return _socket_read_data(self_in, buf, size, 0, NULL, NULL, errcode); } static mp_uint_t socket_stream_write(mp_obj_t self_in, const void *buf, mp_uint_t size, int *errcode) { @@ -1010,6 +1013,8 @@ static const mp_rom_map_elem_t mp_module_socket_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SO_BINDTODEVICE), MP_ROM_INT(SO_BINDTODEVICE) }, { MP_ROM_QSTR(MP_QSTR_IP_ADD_MEMBERSHIP), MP_ROM_INT(IP_ADD_MEMBERSHIP) }, { MP_ROM_QSTR(MP_QSTR_TCP_NODELAY), MP_ROM_INT(TCP_NODELAY) }, + { MP_ROM_QSTR(MP_QSTR_MSG_PEEK), MP_ROM_INT(MSG_PEEK) }, + { MP_ROM_QSTR(MP_QSTR_MSG_DONTWAIT), MP_ROM_INT(MSG_DONTWAIT) }, }; static MP_DEFINE_CONST_DICT(mp_module_socket_globals, mp_module_socket_globals_table); From 0b224048ef1cf448cfae18cde91ed42c6b7838f0 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 May 2025 15:04:48 +1000 Subject: [PATCH 3/6] unix/modsocket: Expose MSG_PEEK flag for recv & recvfrom. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/unix/modsocket.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/unix/modsocket.c b/ports/unix/modsocket.c index 6d6059ae44c54..2aaa21183a9d4 100644 --- a/ports/unix/modsocket.c +++ b/ports/unix/modsocket.c @@ -701,6 +701,7 @@ static const mp_rom_map_elem_t mp_module_socket_globals_table[] = { C(MSG_DONTROUTE), C(MSG_DONTWAIT), + C(MSG_PEEK), C(SOL_SOCKET), C(SO_BROADCAST), From 73d4ddce6cc797e2dabe37aadef5795c4be11665 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 May 2025 15:05:12 +1000 Subject: [PATCH 4/6] tests/multi_net: Add test coverage for socket recv flag MSG_PEEK. Adds TCP and UDP multi_net test cases. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/multi_net/tcp_recv_peek.py | 46 ++++++++++++++++++++++++++++++++ tests/multi_net/udp_recv_peek.py | 36 +++++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 tests/multi_net/tcp_recv_peek.py create mode 100644 tests/multi_net/udp_recv_peek.py diff --git a/tests/multi_net/tcp_recv_peek.py b/tests/multi_net/tcp_recv_peek.py new file mode 100644 index 0000000000000..ff540dd3c3c41 --- /dev/null +++ b/tests/multi_net/tcp_recv_peek.py @@ -0,0 +1,46 @@ +# Test TCP recv with MSG_PEEK +# +# Note that bare metal LWIP only returns at most one packet's worth of TCP data +# in any recv() call - including when peeking - so can't be too clever with +# different recv() combinations +import socket +import random + + +# Server +def instance0(): + PORT = random.randrange(10000, 50000) + multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + s.listen() + multitest.next() + s2, _ = s.accept() + print(s2.recv(8, socket.MSG_PEEK)) + print(s2.recv(8)) + s2.send(b"1234567890") + multitest.broadcast("0-sent") + multitest.wait("1-sent") + print(s2.recv(5, socket.MSG_PEEK)) + print(s2.recv(5, socket.MSG_PEEK)) + multitest.broadcast("0-recved") + multitest.wait("1-recved") # sync here necessary as MP sends RST if closing TCP early + s2.close() + s.close() + + +# Client +def instance1(): + multitest.next() + s = socket.socket() + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + s.send(b"abcdefgh") + multitest.broadcast("1-sent") + multitest.wait("0-sent") + s.send(b"klmnopqr") + print(s.recv(5, socket.MSG_PEEK)) + print(s.recv(10)) + multitest.broadcast("1-recved") + multitest.wait("0-recved") + s.close() diff --git a/tests/multi_net/udp_recv_peek.py b/tests/multi_net/udp_recv_peek.py new file mode 100644 index 0000000000000..47897ce553954 --- /dev/null +++ b/tests/multi_net/udp_recv_peek.py @@ -0,0 +1,36 @@ +# Test UDP recv and recvfrom with MSG_PEEK +import random +import socket +import time + + +# Server +def instance0(): + PORT = random.randrange(10000, 50000) + multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + multitest.next() + peek_bytes, peek_addr = s.recvfrom(8, socket.MSG_PEEK) + print(peek_bytes) + real_bytes, real_addr = s.recvfrom(8) + print(real_bytes) + print(peek_addr == real_addr) # source addr should be the same for each + res = s.sendto(b"1234567890", peek_addr) + print(res) + print(s.recv(5, socket.MSG_PEEK)) + print(s.recv(5, socket.MSG_PEEK)) + s.close() + + +# Client +def instance1(): + multitest.next() + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + s.send(b"abcdefgh") + s.send(b"klmnopqr") + print(s.recv(5, socket.MSG_PEEK)) + print(s.recv(10)) + s.close() From d23fb869349a1525069380e5cce30ffb6bce8a8c Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 May 2025 15:51:43 +1000 Subject: [PATCH 5/6] tests/multi_net: Add test coverage for socket recv flag MSG_DONTWAIT. Adding multi_net case for UDP only, as TCP timing is hard to test reliably. Signed-off-by: Angus Gratton --- tests/multi_net/udp_recv_dontwait.py | 59 ++++++++++++++++++++++++ tests/multi_net/udp_recv_dontwait.py.exp | 7 +++ 2 files changed, 66 insertions(+) create mode 100644 tests/multi_net/udp_recv_dontwait.py create mode 100644 tests/multi_net/udp_recv_dontwait.py.exp diff --git a/tests/multi_net/udp_recv_dontwait.py b/tests/multi_net/udp_recv_dontwait.py new file mode 100644 index 0000000000000..640f3f060ea74 --- /dev/null +++ b/tests/multi_net/udp_recv_dontwait.py @@ -0,0 +1,59 @@ +# Test UDP recv and recvfrom with MSG_DONTWAIT +import random +import socket + +try: + import errno, time +except ImportError: + print("SKIP") + raise SystemExit + + +# Server +def instance0(): + PORT = random.randrange(10000, 50000) + multitest.globals(IP=multitest.get_network_ip(), PORT=PORT) + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(socket.getaddrinfo("0.0.0.0", PORT)[0][-1]) + multitest.next() + begin = time.ticks_ms() + + # do some recvs before instance1 starts, when we know no packet is waiting + try: + print(s.recvfrom(8, socket.MSG_DONTWAIT)) + except OSError as e: + print(e.errno == errno.EAGAIN) + try: + print(s.recv(8, socket.MSG_DONTWAIT)) + except OSError as e: + print(e.errno == errno.EAGAIN) + + # the above steps should not have taken any substantial time + elapsed = time.ticks_diff(time.ticks_ms(), begin) + print(True if elapsed < 50 else elapsed) + + # Now instance1 will send us a UDP packet + multitest.broadcast("0-ready") + multitest.wait("1-sent") + + for _ in range(10): # retry if necessary, to allow for network delay + time.sleep_ms(100) + try: + print(s.recv(8, socket.MSG_DONTWAIT)) + break + except OSError as er: + if er.errno != errno.EAGAIN: + raise er + s.close() + + +# Client +def instance1(): + multitest.next() + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(socket.getaddrinfo(IP, PORT)[0][-1]) + multitest.wait("0-ready") + print(s.send(b"abcdefgh")) + multitest.broadcast("1-sent") + s.close() diff --git a/tests/multi_net/udp_recv_dontwait.py.exp b/tests/multi_net/udp_recv_dontwait.py.exp new file mode 100644 index 0000000000000..f61fd4bbe2ee5 --- /dev/null +++ b/tests/multi_net/udp_recv_dontwait.py.exp @@ -0,0 +1,7 @@ +--- instance0 --- +True +True +True +b'abcdefgh' +--- instance1 --- +8 From ba92063d6491753411ea0737da3eed19278964b2 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Fri, 16 May 2025 16:09:12 +1000 Subject: [PATCH 6/6] docs: Add a description of recv/recvfrom flags argument. Implementation added for various ports in the parent commits. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/socket.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/library/socket.rst b/docs/library/socket.rst index 944e7e631ac76..38e0aab704ad6 100644 --- a/docs/library/socket.rst +++ b/docs/library/socket.rst @@ -227,22 +227,28 @@ Methods has the same "no short writes" policy for blocking sockets, and will return number of bytes sent on non-blocking sockets. -.. method:: socket.recv(bufsize) +.. method:: socket.recv(bufsize, [flags]) Receive data from the socket. The return value is a bytes object representing the data received. The maximum amount of data to be received at once is specified by bufsize. + Most ports support the optional *flags* argument. Available *flags* are defined as constants + in the socket module and have the same meaning as in CPython. ``MSG_PEEK`` and ``MSG_DONTWAIT`` + are supported on all ports which accept the *flags* argument. + .. method:: socket.sendto(bytes, address) Send data to the socket. The socket should not be connected to a remote socket, since the destination socket is specified by *address*. -.. method:: socket.recvfrom(bufsize) +.. method:: socket.recvfrom(bufsize, [flags]) Receive data from the socket. The return value is a pair *(bytes, address)* where *bytes* is a bytes object representing the data received and *address* is the address of the socket sending the data. + See the `recv` function for an explanation of the optional *flags* argument. + .. method:: socket.setsockopt(level, optname, value) Set the value of the given socket option. The needed symbolic constants are defined in the pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy