inet_sockif:support multiple iov recv

Summary:
  Implement multiple iovlen recvmsg in inet_sockif, only TCP mode
  support at current.

Signed-off-by: chenrun1 <chenrun1@xiaomi.com>
This commit is contained in:
chenrun1 2024-10-22 15:02:39 +08:00 committed by Xiang Xiao
parent 87a7714103
commit 99a1f0dc65
3 changed files with 214 additions and 152 deletions

View file

@ -2273,11 +2273,6 @@ static ssize_t inet_recvmsg(FAR struct socket *psock,
{
ssize_t ret;
if (msg->msg_iovlen != 1)
{
return -ENOTSUP;
}
/* If a 'from' address has been provided, verify that it is large
* enough to hold this address family.
*/

View file

@ -657,6 +657,196 @@ static void tcp_notify_recvcpu(FAR struct tcp_conn_s *conn)
# define tcp_notify_recvcpu(c)
#endif /* CONFIG_NETDEV_RSS */
/****************************************************************************
* Name: tcp_recvfrom_one
*
* Description:
* This function attempts to receive data from the specified TCP connection
* 'conn' and stores it in the provided buffer 'buf'. It handles data that
* may already be in the read-ahead buffer and manages blocking or
* non-blocking operations based on connection flags.
* It also monitors connection state, handles connection timeouts, and
* updates the receive window when necessary.
*
* Input Parameters:
* conn - The TCP connection from which data is to be received.
* buf - The buffer to store the received data.
* len - The maximum number of bytes to receive.
* from - Socket address structure to store the source address
* (if provided).
* fromlen - Length of the address structure.
* flags - Flags indicating specific receive options
* (e.g., non-blocking).
*
* Returned Value:
* Returns the number of bytes received, or a negative error code in
* case of failure (e.g., -ENOTCONN if not connected).
*
* Assumptions:
* conn, buf, and from are non-NULL pointers.
*
****************************************************************************/
static ssize_t tcp_recvfrom_one(FAR struct tcp_conn_s *conn, FAR void *buf,
size_t len, FAR struct sockaddr *from,
FAR socklen_t *fromlen, int flags)
{
struct tcp_recvfrom_s state;
struct tcp_callback_s info;
ssize_t ret;
/* Initialize the state structure. This is done with the network
* locked because we don't want anything to happen until we are ready.
*/
tcp_recvfrom_initialize(conn, buf, len, from, fromlen, &state, flags);
/* Handle any any TCP data already buffered in a read-ahead buffer.
* NOTE that there may be read-ahead data to be retrieved even after
* the socket has been disconnected.
*/
tcp_readahead(&state);
/* The default return value is the number of bytes that we just copied
* into the user buffer. We will return this if the socket has become
* disconnected or if the user request was completely satisfied with
* data from the readahead buffers.
*/
ret = state.ir_recvlen;
/* Verify that the SOCK_STREAM has been and still is connected */
if (!_SS_ISCONNECTED(conn->sconn.s_flags))
{
/* Was any data transferred from the readahead buffer after we were
* disconnected? If so, then return the number of bytes received.
* We will wait to return end disconnection indications the next
* time that recvfrom() is called.
*
* If no data was received (i.e., ret == 0 -- it will not be
* negative) and the connection was gracefully closed by the remote
* peer, then return success. If ir_recvlen is zero, the caller of
* recvfrom() will get an end-of-file indication.
*/
if (ret <= 0 && !_SS_ISCLOSED(conn->sconn.s_flags))
{
/* Nothing was previously received from the read-ahead buffers.
* The SOCK_STREAM must be (re-)connected in order to receive
* any additional data.
*/
ret = -ENOTCONN;
}
}
/* In general, this implementation will not support non-blocking socket
* operations... except in a few cases: Here for TCP receive with
* read-ahead enabled. If this socket is configured as non-blocking
* then return EAGAIN if no data was obtained from the read-ahead
* buffers.
*/
else if (_SS_ISNONBLOCK(conn->sconn.s_flags) ||
(flags & MSG_DONTWAIT) != 0)
{
/* Return the number of bytes read from the read-ahead buffer if
* something was received (already in 'ret'); EAGAIN if not.
*/
if (ret <= 0)
{
/* Nothing was received */
ret = -EAGAIN;
}
}
/* It is okay to block if we need to. If there is space to receive
* anything more, then we will wait to receive the data. Otherwise
* return the number of bytes read from the read-ahead buffer
* (already in 'ret').
*/
else
/* We get here when we we decide that we need to setup the wait for
* incoming TCP/IP data. Just a few more conditions to check:
*
* 1) Make sure that there is buffer space to receive additional data
* (state.ir_buflen > 0). This could be zero, for example, we
* filled the user buffer with data from the read-ahead buffers. And
* 2) then we not want to wait if we already obtained some data from
* the read-ahead buffer. In that case, return now with what we
* have (don't want for more because there may be no timeout).
* 3) If however MSG_WAITALL flag is set, block here till all requested
* data are received (or there is a timeout / error).
*/
if (((flags & MSG_WAITALL) != 0 || state.ir_recvlen == 0) &&
state.ir_buflen > 0)
{
/* Set up the callback in the connection */
state.ir_cb = tcp_callback_alloc(conn);
if (state.ir_cb)
{
state.ir_cb->flags = (TCP_NEWDATA | TCP_DISCONN_EVENTS);
state.ir_cb->flags |= (flags & MSG_WAITALL) ? TCP_WAITALL : 0;
state.ir_cb->priv = (FAR void *)&state;
state.ir_cb->event = tcp_recvhandler;
/* Push a cancellation point onto the stack. This will be
* called if the thread is canceled.
*/
info.tc_conn = conn;
info.tc_cb = state.ir_cb;
info.tc_sem = &state.ir_sem;
tls_cleanup_push(tls_get_info(), tcp_callback_cleanup, &info);
/* Wait for either the receive to complete or for an
* error/timeout to occur. net_sem_timedwait will also
* terminate if a signal is received.
*/
ret = net_sem_timedwait(&state.ir_sem,
_SO_TIMEOUT(conn->sconn.s_rcvtimeo));
tls_cleanup_pop(tls_get_info(), 0);
if (ret == -ETIMEDOUT)
{
ret = -EAGAIN;
}
/* Make sure that no further events are processed */
tcp_callback_free(conn, state.ir_cb);
ret = tcp_recvfrom_result(ret, &state);
}
else if (ret <= 0)
{
ret = -EBUSY;
}
}
/* Receive additional data from read-ahead buffer, send the ACK timely.
* Revisit: Because IOBs are system-wide resources, consuming the read
* ahead buffer would update recv window of all connections in the
* system, not only this particular connection.
*/
if (tcp_should_send_recvwindow(conn))
{
netdev_txnotify_dev(conn->dev);
}
tcp_notify_recvcpu(conn);
tcp_recvfrom_uninitialize(&state);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
@ -685,168 +875,42 @@ ssize_t psock_tcp_recvfrom(FAR struct socket *psock, FAR struct msghdr *msg,
{
FAR struct sockaddr *from = msg->msg_name;
FAR socklen_t *fromlen = &msg->msg_namelen;
FAR void *buf = msg->msg_iov->iov_base;
size_t len = msg->msg_iov->iov_len;
struct tcp_recvfrom_s state;
FAR struct tcp_conn_s *conn;
struct tcp_callback_s info;
int ret;
ssize_t nrecv = 0;
ssize_t ret = 0;
int i;
net_lock();
conn = psock->s_conn;
/* Initialize the state structure. This is done with the network locked
* because we don't want anything to happen until we are ready.
*/
tcp_recvfrom_initialize(conn, buf, len, from, fromlen, &state, flags);
/* Handle any any TCP data already buffered in a read-ahead buffer. NOTE
* that there may be read-ahead data to be retrieved even after the
* socket has been disconnected.
*/
tcp_readahead(&state);
/* The default return value is the number of bytes that we just copied
* into the user buffer. We will return this if the socket has become
* disconnected or if the user request was completely satisfied with
* data from the readahead buffers.
*/
ret = state.ir_recvlen;
/* Verify that the SOCK_STREAM has been and still is connected */
if (!_SS_ISCONNECTED(conn->sconn.s_flags))
for (i = 0; i < msg->msg_iovlen; i++)
{
/* Was any data transferred from the readahead buffer after we were
* disconnected? If so, then return the number of bytes received. We
* will wait to return end disconnection indications the next time that
* recvfrom() is called.
*
* If no data was received (i.e., ret == 0 -- it will not be
* negative) and the connection was gracefully closed by the remote
* peer, then return success. If ir_recvlen is zero, the caller of
* recvfrom() will get an end-of-file indication.
*/
if (ret <= 0 && !_SS_ISCLOSED(conn->sconn.s_flags))
{
/* Nothing was previously received from the read-ahead buffers.
* The SOCK_STREAM must be (re-)connected in order to receive any
* additional data.
*/
ret = -ENOTCONN;
}
}
/* In general, this implementation will not support non-blocking socket
* operations... except in a few cases: Here for TCP receive with read-
* ahead enabled. If this socket is configured as non-blocking then
* return EAGAIN if no data was obtained from the read-ahead buffers.
*/
else if (_SS_ISNONBLOCK(conn->sconn.s_flags) ||
(flags & MSG_DONTWAIT) != 0)
{
/* Return the number of bytes read from the read-ahead buffer if
* something was received (already in 'ret'); EAGAIN if not.
*/
FAR void *buf = msg->msg_iov[i].iov_base;
size_t len = msg->msg_iov[i].iov_len;
ret = tcp_recvfrom_one(conn, buf, len, from, fromlen, flags);
if (ret <= 0)
{
/* Nothing was received */
ret = -EAGAIN;
break;
}
}
/* It is okay to block if we need to. If there is space to receive
* anything more, then we will wait to receive the data. Otherwise return
* the number of bytes read from the read-ahead buffer (already in 'ret').
*/
nrecv += ret;
else
/* User has not set MSG_WAITALL: If the first buffer is full
* when received for the first time, then check the next buffer,
* otherwise return the received data.
* User sets MSG_WAITALL: Ensure that each iov is filled before
* returning
*/
/* We get here when we we decide that we need to setup the wait for
* incoming TCP/IP data. Just a few more conditions to check:
*
* 1) Make sure that there is buffer space to receive additional data
* (state.ir_buflen > 0). This could be zero, for example, we filled
* the user buffer with data from the read-ahead buffers. And
* 2) then we not want to wait if we already obtained some data from the
* read-ahead buffer. In that case, return now with what we have (don't
* want for more because there may be no timeout).
* 3) If however MSG_WAITALL flag is set, block here till all requested
* data are received (or there is a timeout / error).
*/
if (((flags & MSG_WAITALL) != 0 || state.ir_recvlen == 0) &&
state.ir_buflen > 0)
{
/* Set up the callback in the connection */
state.ir_cb = tcp_callback_alloc(conn);
if (state.ir_cb)
if (!(flags & MSG_WAITALL) && ret < msg->msg_iov[i].iov_len)
{
state.ir_cb->flags = (TCP_NEWDATA | TCP_DISCONN_EVENTS);
state.ir_cb->flags |= (flags & MSG_WAITALL) ? TCP_WAITALL : 0;
state.ir_cb->priv = (FAR void *)&state;
state.ir_cb->event = tcp_recvhandler;
/* Push a cancellation point onto the stack. This will be
* called if the thread is canceled.
*/
info.tc_conn = conn;
info.tc_cb = state.ir_cb;
info.tc_sem = &state.ir_sem;
tls_cleanup_push(tls_get_info(), tcp_callback_cleanup, &info);
/* Wait for either the receive to complete or for an error/timeout
* to occur. net_sem_timedwait will also terminate if a signal is
* received.
*/
ret = net_sem_timedwait(&state.ir_sem,
_SO_TIMEOUT(conn->sconn.s_rcvtimeo));
tls_cleanup_pop(tls_get_info(), 0);
if (ret == -ETIMEDOUT)
{
ret = -EAGAIN;
}
/* Make sure that no further events are processed */
tcp_callback_free(conn, state.ir_cb);
ret = tcp_recvfrom_result(ret, &state);
}
else if (ret <= 0)
{
ret = -EBUSY;
break;
}
}
/* Receive additional data from read-ahead buffer, send the ACK timely.
*
* Revisit: Because IOBs are system-wide resources, consuming the read
* ahead buffer would update recv window of all connections in the system,
* not only this particular connection.
*/
if (tcp_should_send_recvwindow(conn))
{
netdev_txnotify_dev(conn->dev);
}
tcp_notify_recvcpu(conn);
net_unlock();
tcp_recvfrom_uninitialize(&state);
return (ssize_t)ret;
return nrecv ? nrecv : ret;
}
#endif /* CONFIG_NET_TCP */

View file

@ -647,8 +647,6 @@ static void udp_notify_recvcpu(FAR struct udp_conn_s *conn)
conn->rcvcpu = cpu;
}
return;
}
#else
# define udp_notify_recvcpu(c)
@ -683,10 +681,15 @@ ssize_t psock_udp_recvfrom(FAR struct socket *psock, FAR struct msghdr *msg,
FAR struct net_driver_s *dev;
struct udp_callback_s info;
struct udp_recvfrom_s state;
int ret;
ssize_t ret;
/* Perform the UDP recvfrom() operation */
if (msg->msg_iovlen != 1)
{
return -ENOTSUP;
}
/* Initialize the state structure. This is done with the network locked
* because we don't want anything to happen until we are ready.
*/