diff options
| author | Laytan Laats <laytanlaats@hotmail.com> | 2025-12-21 18:01:50 +0100 |
|---|---|---|
| committer | Laytan Laats <laytanlaats@hotmail.com> | 2025-12-21 19:10:42 +0100 |
| commit | 9cace192776656d2237c25e402fd321f05ae1aeb (patch) | |
| tree | ef8fc5f8fcea5c397c23a4d440e0a446f1738aea | |
| parent | 06076e02c0acd0559eeefb32a1a420e4d7243aa7 (diff) | |
net(docs): recv of 0 bytes with no error is a graceful close
| -rw-r--r-- | core/net/errors.odin | 8 | ||||
| -rw-r--r-- | core/net/socket.odin | 19 | ||||
| -rw-r--r-- | tests/core/net/test_core_net.odin | 35 |
3 files changed, 57 insertions, 5 deletions
diff --git a/core/net/errors.odin b/core/net/errors.odin index 4853327b0..de53640fc 100644 --- a/core/net/errors.odin +++ b/core/net/errors.odin @@ -149,7 +149,8 @@ TCP_Recv_Error :: enum i32 { Invalid_Argument, // The socket is not connected. Not_Connected, - // Connection was closed/broken/shutdown while receiving data. + // Connection was closed due to an error or shutdown. + // NOTE: a graceful close is indicated by a `0, nil` (0 bytes received and no error) return. Connection_Closed, // Timed out before being able to receive any data. Timeout, @@ -170,7 +171,8 @@ UDP_Recv_Error :: enum i32 { Insufficient_Resources, // Invalid socket or buffer given. Invalid_Argument, - // "Connection" was refused by remote, or closed/broken/shutdown while receiving data. + // "Connection" was refused, or closed due to an error. + // NOTE: a graceful close is indicated by a `0, nil` (0 bytes received and no error) return. Connection_Refused, // Timed out before being able to receive any data. Timeout, @@ -193,7 +195,7 @@ TCP_Send_Error :: enum i32 { Insufficient_Resources, // Invalid socket or buffer given. Invalid_Argument, - // Connection was closed/broken/shutdown while receiving data. + // Connection was closed/broken/shutdown while sending data. Connection_Closed, // The socket is not connected. Not_Connected, diff --git a/core/net/socket.odin b/core/net/socket.odin index 7e96ba2b2..edb47cd0b 100644 --- a/core/net/socket.odin +++ b/core/net/socket.odin @@ -193,21 +193,36 @@ close :: proc(socket: Any_Socket) { _close(socket) } +/* + Receive data into a buffer from a TCP socket. + + If no error occurs, `recv_tcp` returns the number of bytes received and `buf` will contain this data received. + If the connection has been gracefully closed, the return value is `0, nil` (0 bytes read and no error). +*/ recv_tcp :: proc(socket: TCP_Socket, buf: []byte) -> (bytes_read: int, err: TCP_Recv_Error) { return _recv_tcp(socket, buf) } +/* + Receive data into a buffer from a UDP socket. + + If no error occurs, `recv_udp` returns the number of bytes received and `buf` will contain this data received. + If the "connection" has been gracefully closed, the return value is `0, nil` (0 bytes read and no error). +*/ recv_udp :: proc(socket: UDP_Socket, buf: []byte) -> (bytes_read: int, remote_endpoint: Endpoint, err: UDP_Recv_Error) { return _recv_udp(socket, buf) } /* - Receive data from into a buffer from any socket. + Receive data into a buffer from any socket. Note: `remote_endpoint` parameter is non-nil only if the socket type is UDP. On TCP sockets it will always return `nil`. - Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error` + Errors that can be returned: `TCP_Recv_Error`, or `UDP_Recv_Error`. + + If no error occurs, `recv_any` returns the number of bytes received and `buf` will contain this data received. + If the connection has been gracefully closed, the return value is `0, nil, nil` (0 bytes read and no error). */ recv_any :: proc(socket: Any_Socket, buf: []byte) -> ( bytes_read: int, diff --git a/tests/core/net/test_core_net.odin b/tests/core/net/test_core_net.odin index ec45744f3..9b3973a60 100644 --- a/tests/core/net/test_core_net.odin +++ b/tests/core/net/test_core_net.odin @@ -624,6 +624,41 @@ test_nonblocking_option :: proc(t: ^testing.T) { } } +// Test that when the server closes it's connection, the client's next receive is `0, nil` to indicate a correct close. +@(test) +test_connection_close :: proc(t: ^testing.T) { + server, listen_err := net.listen_tcp({address=net.IP4_Address{127, 0, 0, 1}, port=0}) + testing.expect_value(t, listen_err, nil) + defer net.close(server) + + server_ep, bound_endpoint_err := net.bound_endpoint(server) + testing.expect_value(t, bound_endpoint_err, nil) + + client, dial_err := net.dial_tcp(server_ep) + testing.expect_value(t, dial_err, nil) + defer net.close(client) + + server_client, _, accept_err := net.accept_tcp(server) + testing.expect_value(t, accept_err, nil) + + send_buf: [512]byte = 1 + sent, send_err := net.send(server_client, send_buf[:]) + testing.expect_value(t, sent, 512) + testing.expect_value(t, send_err, nil) + net.close(server_client) + + recv_buf: [512]byte = --- + received, recv_err := net.recv(client, recv_buf[:]) + testing.expect_value(t, received, 512) + testing.expect_value(t, recv_err, nil) + + testing.expect_value(t, recv_buf, send_buf) + + received, recv_err = net.recv(client, recv_buf[:]) + testing.expect_value(t, received, 0) + testing.expect_value(t, recv_err, nil) +} + @(private) address_to_binstr :: proc(address: net.Address) -> (binstr: string) { switch t in address { |