内核中的UDP socket流程(7)——udp_sendmsg

本文详细解析了UDP Socket的发送流程,从sock_sendmsg函数入手,深入到udp_sendmsg函数内部,阐述了如何处理待发送的数据包,包括错误检测、地址验证等关键步骤。
部署运行你感兴趣的模型镜像
sock_sendmsg的代码很简单
  1. int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
  2. {
  3.      struct kiocb iocb;
  4.      struct sock_iocb siocb;
  5.      int ret;

  6.      init_sync_kiocb(&iocb, NULL);
  7.      iocb.private = &siocb;
  8.      ret = __sock_sendmsg(&iocb, sock, msg, size);
  9.      if (-EIOCBQUEUED == ret)
  10.           ret = wait_on_sync_kiocb(&iocb);
  11.      return ret;
  12. }
首先定义了一个struct kiocb类型的iocb——linux内核中所有I/O操作都要依赖于合格结构,然后初始化它。然后调用__sock_sendmsg,而__sock_sendmsg又调用UDP的sendmsg去做真正的发送。
也就是说,对于UDP的socket来说,sendto调用,真正去做工作的是udp_sendmsg这个函数。
  1. int udp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
  2.           size_t len)
  3. {
  4.      struct inet_sock *inet = inet_sk(sk);
  5.      struct udp_sock *up = udp_sk(sk);
  6.      int ulen = len;
  7.      struct ipcm_cookie ipc;
  8.      struct rtable *rt = NULL;
  9.      int free = 0;
  10.      int connected = 0;
  11.      __be32 daddr, faddr, saddr;
  12.      __be16 dport;
  13.      u8 tos;
  14.      int err, is_udplite = IS_UDPLITE(sk);
  15.      int corkreq = up->corkflag || msg->msg_flags&MSG_MORE;
  16.      int (*getfrag)(void *, char *, int, int, int, struct sk_buff *);
将sk转为udp内部使用的类型指针,在TCP/IP中充斥了这样的转换。主要原因是因为对于上层来说,需要一个统一的类型,而到了底层的具体实现时,都会将上层抽象的数据类型,转为自己所需的类型。
并判断该UDP是否是udplite——udplite的定义见http://en.wikipedia.org/wiki/UDP_Lite
  1.      if (len > 0xFFFF)
  2.           return -EMSGSIZE;

  3.      /*
  4.      * Check the flags.
  5.      */

  6.      if (msg->msg_flags & MSG_OOB) /* Mirror BSD error message compatibility */
  7.           return -EOPNOTSUPP;
进行一些错误检测。
  1.      ipc.opt = NULL;
  2.      ipc.shtx.flags = 0;

  3.      if (up->pending) {
  4.           /*
  5.           * There are pending frames.
  6.           * The socket lock must be held while it's corked.
  7.           */
  8.           lock_sock(sk);
  9.           if (likely(up->pending)) {
  10.                if (unlikely(up->pending != AF_INET)) {
  11.                     release_sock(sk);
  12.                     return -EINVAL;
  13.                }
  14.                goto do_append_data;
  15.           }
  16.           release_sock(sk);
  17.      }
  18.      ulen += sizeof(struct udphdr);
如果该socket有pending的frame,那么直接将数据追加。如果没有就ulen加上udp首部的长度。
  1.      /*
  2.      * Get and verify the address.
  3.      */
  4.      if (msg->msg_name) {
  5.           struct sockaddr_in * usin = (struct sockaddr_in *)msg->msg_name;
  6.           if (msg->msg_namelen < sizeof(*usin))
  7.                return -EINVAL;
  8.           if (usin->sin_family != AF_INET) {
  9.                if (usin->sin_family != AF_UNSPEC)
  10.                     return -EAFNOSUPPORT;
  11.           }

  12.           daddr = usin->sin_addr.s_addr;
  13.           dport = usin->sin_port;
  14.           if (dport == 0)
  15.                return -EINVAL;
  16.      } else {
  17.           if (sk->sk_state != TCP_ESTABLISHED)
  18.                return -EDESTADDRREQ;
  19.           daddr = inet->inet_daddr;
  20.           dport = inet->inet_dport;
  21.           /* Open fast path for connected socket.
  22.              Route will not be used, if at least one option is set.
  23.           */
  24.           connected = 1;
  25.      }
  26.      ipc.addr = inet->inet_saddr;
如果msg->msg_name不为空,就说明指定了目的地址,对其进行检验。如果为空,就就需要对sock的状态进行检验,查看其是否是连接状态——UDP的socket同样是可以调用connect,这样就不需要每次都指定发送地址了。

您可能感兴趣的与本文相关的镜像

Stable-Diffusion-3.5

Stable-Diffusion-3.5

图片生成
Stable-Diffusion

Stable Diffusion 3.5 (SD 3.5) 是由 Stability AI 推出的新一代文本到图像生成模型,相比 3.0 版本,它提升了图像质量、运行速度和硬件效率

### 关于 `sendmsg` 和 `MSG_NOSIGNAL` 标志位的作用、原理及使用场景 #### 1. `sendmsg` 函数的作用 `sendmsg` 是一个通用的发送函数,用于通过套接字发送数据。它支持复杂的 I/O 操作,允许同时发送多个缓冲区的数据以及控制消息。其结构由 `struct msghdr` 定义[^1],包含以下关键字段: - **`msg_name`** 和 **`msg_namelen`**:存储目标地址信息(对于非面向连接的套接字如 UDP)。 - **`msg_iov`** 和 **`msg_iovlen`**:指定要发送的数据缓冲区及其数量。 - **`msg_control`** 和 **`msg_controllen`**:用于发送控制消息。 - **`msg_flags`**:指定发送选项。 `sendmsg` 的返回值表示成功发送的字节数,如果发生错误则返回 -1 并设置 `errno`[^4]。 #### 2. `MSG_NOSIGNAL` 标志位的作用 `MSG_NOSIGNAL` 是 `msg_flags` 中的一个可选标志位,其作用是防止在向已关闭读端的管道或套接字写入数据时触发 `SIGPIPE` 信号[^3]。通常情况下,当应用程序尝试向一个已关闭读端的套接字写入数据时,系统会发送 `SIGPIPE` 信号给进程。如果没有捕获该信号,默认行为会导致进程终止。 启用 `MSG_NOSIGNAL` 后,即使写入失败也不会触发 `SIGPIPE` 信号,而是返回 `-1` 并将 `errno` 设置为 `EPIPE`。这使得程序可以优雅地处理错误,而不会意外崩溃。 #### 3. 原理分析 `MSG_NOSIGNAL` 的实现依赖于内核对套接字状态的检查。当检测到对端已关闭读端时,内核会根据是否设置了 `MSG_NOSIGNAL` 来决定是否发送 `SIGPIPE` 信号。如果未设置该标志,则发送信号;否则仅返回错误码 `EPIPE`。 #### 4. 使用场景 以下是 `MSG_NOSIGNAL` 的典型使用场景: - **网络服务器**:在网络服务中,客户端可能随时断开连接。如果服务器频繁向已断开的客户端发送数据,可能会导致大量 `SIGPIPE` 信号被触发,从而影响服务稳定性。通过设置 `MSG_NOSIGNAL`,可以避免此类问题。 - **高并发应用**:在高并发环境下,某些连接可能因超时或其他原因被关闭。使用 `MSG_NOSIGNAL` 可以减少因信号处理带来的性能开销。 - **容错设计**:对于需要容忍部分连接失败的应用程序,`MSG_NOSIGNAL` 提供了一种简单的机制来忽略这些错误。 #### 示例代码 以下是一个使用 `sendmsg` 和 `MSG_NOSIGNAL` 的示例: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("Socket creation failed"); exit(EXIT_FAILURE); } struct msghdr msg; struct iovec iov; char buf[] = "Hello, World!"; iov.iov_base = buf; iov.iov_len = strlen(buf); msg.msg_name = NULL; msg.msg_namelen = 0; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = MSG_NOSIGNAL; // 设置 MSG_NOSIGNAL 标志 ssize_t sent = sendmsg(sockfd, &msg, 0); if (sent < 0) { if (errno == EPIPE) { printf("Connection closed by peer, but no SIGPIPE triggered.\n"); } else { perror("Sendmsg error"); } } else { printf("Sent %zd bytes\n", sent); } close(sockfd); return 0; } ``` #### 总结 `sendmsg` 是一个功能强大的套接字 API,支持复杂的数据传输需求。`MSG_NOSIGNAL` 标志位通过抑制 `SIGPIPE` 信号,增强了程序的健壮性和可靠性,特别适用于高并发和容错要求较高的场景。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值