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

简介: 作者:gfree.wind@gmail.com原文:http://blog.chinaunix.net/space.php?uid=23629988&do=blog&id=91590继续分析udp_sendmsg,     ipc.
作者:gfree.wind@gmail.com

继续分析udp_sendmsg,
  1.      ipc.oif = sk->sk_bound_dev_if;
  2.      err = sock_tx_timestamp(msg, sk, &ipc.shtx);
  3.      if (err)
  4.           return err;
  5.      if (msg->msg_controllen) {
  6.           err = ip_cmsg_send(sock_net(sk), msg, &ipc);
  7.           if (err)
  8.                return err;
  9.           if (ipc.opt)
  10.                free = 1;
  11.           connected = 0;
  12.      }
  13.      if (!ipc.opt)
  14.           ipc.opt = inet->opt;
ipc.oif设置为socket bind的interface,设置发送数据包时间戳产生策略。msg->msg_controllen若不为0,那么就调用ip_cmsg_send——从函数名字上看,好像是要发送cmsg,然而实际上却没有任何数据发送。请看它的定义。
  1. int ip_cmsg_send(struct net *net, struct msghdr *msg, struct ipcm_cookie *ipc)
  2. {
  3.      int err;
  4.      struct cmsghdr *cmsg;

  5.      for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
  6.           if (!CMSG_OK(msg, cmsg))
  7.                return -EINVAL;
  8.           if (cmsg->cmsg_level != SOL_IP)
  9.                continue;
  10.           switch (cmsg->cmsg_type) {
  11.           case IP_RETOPTS:
  12.                err = cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr));
  13.                err = ip_options_get(net, &ipc->opt, CMSG_DATA(cmsg),
  14.                               err 40 ? err : 40);
  15.                if (err)
  16.                     return err;
  17.                break;
  18.           case IP_PKTINFO:
  19.           {
  20.                struct in_pktinfo *info;
  21.                if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct in_pktinfo)))
  22.                     return -EINVAL;
  23.                info = (struct in_pktinfo *)CMSG_DATA(cmsg);
  24.                ipc->oif = info->ipi_ifindex;
  25.                ipc->addr = info->ipi_spec_dst.s_addr;
  26.                break;
  27.           }
  28.           default:
  29.                return -EINVAL;
  30.           }
  31.      }
  32.      return 0;
  33. }
从以上代码可以看出,cmsg只支持两种类型:1个是IP_RETOPTS——设置包的option,另外一个是IP_PKTINFO——具体定义,可以使用man 7 ip来查看。
  1.      saddr = ipc.addr;
  2.      ipc.addr = faddr = daddr;

  3.      if (ipc.opt && ipc.opt->srr) {
  4.           if (!daddr)
  5.                return -EINVAL;
  6.           faddr = ipc.opt->faddr;
  7.           connected = 0;
  8.      }
  9.      tos = RT_TOS(inet->tos);
  10.      if (sock_flag(sk, SOCK_LOCALROUTE) ||
  11.          (msg->msg_flags & MSG_DONTROUTE) ||
  12.          (ipc.opt && ipc.opt->is_strictroute)) {
  13.           tos |= RTO_ONLINK;
  14.           connected = 0;
  15.      }
前面对于地址赋值的几行代码,我还没有看出什么用途来——哪个高手指教一下。后面是几种情况下,设置发送该包是不需要路由标志——表示到达目的地址是不需要下一跳的。
  1.      if (ipv4_is_multicast(daddr)) {
  2.           if (!ipc.oif)
  3.                ipc.oif = inet->mc_index;
  4.           if (!saddr)
  5.                saddr = inet->mc_addr;
  6.           connected = 0;
  7.      }
如果目的地址是多播地址且没有bind interface,那么就使用本地多播interface,如果没有设置源地址,那么就使用本地多播地址。
  1.      if (connected)
  2.           rt = (struct rtable *)sk_dst_check(sk, 0);

  3.      if (rt == NULL) {
  4.           struct flowi fl = { .oif = ipc.oif,
  5.                         .mark = sk->sk_mark,
  6.                         .nl_u = { .ip4_u =
  7.                                { .daddr = faddr,
  8.                               .saddr = saddr,
  9.                               .tos = tos } },
  10.                         .proto = sk->sk_protocol,
  11.                         .flags = inet_sk_flowi_flags(sk),
  12.                         .uli_u = { .ports =
  13.                                 { .sport = inet->inet_sport,
  14.                               .dport = dport } } };
  15.           struct net *net = sock_net(sk);

  16.           security_sk_classify_flow(sk, &fl);
  17.           err = ip_route_output_flow(net, &rt, &fl, sk, 1);
  18.           if (err) {
  19.                if (err == -ENETUNREACH)
  20.                     IP_INC_STATS_BH(net, IPSTATS_MIB_OUTNOROUTES);
  21.                goto out;
  22.           }

  23.           err = -EACCES;
  24.           if ((rt->rt_flags & RTCF_BROADCAST) &&
  25.               !sock_flag(sk, SOCK_BROADCAST))
  26.                goto out;
  27.           if (connected)
  28.                sk_dst_set(sk, dst_clone(&rt->dst));
  29.      }
如果是面向连接的socket,那么首先检查该socket之前使用的路由。如果没有有效的路由,那么就使用ip_route_output_flow来查找到一个路由。如果没有找到,就返回错误。如果是面向连接的socket,那么就将该路由保存到socket结构中。
  1.       if (msg->msg_flags&MSG_CONFIRM)
  2.           goto do_confirm;
如果设置了MSG_CONFIRM标志,那么就跳转到do_confirm.MSG_CONFIRM的具体作用,请查看man 2 sendto。
  1. back_from_confirm:

  2.      saddr = rt->rt_src;
  3.      if (!ipc.addr)
  4.           daddr = ipc.addr = rt->rt_dst;

  5.      lock_sock(sk);
  6.      if (unlikely(up->pending)) {
  7.           /* The socket is already corked while preparing it. */
  8.           /* ... which is an evident application bug. --ANK */
  9.           release_sock(sk);

  10.           LIMIT_NETDEBUG(KERN_DEBUG "udp cork app bug 2\n");
  11.           err = -EINVAL;
  12.           goto out;
  13.      }
  14.      /*
  15.      * Now cork the socket to pend data.
  16.      */
  17.      inet->cork.fl.fl4_dst = daddr;
  18.      inet->cork.fl.fl_ip_dport = dport;
  19.      inet->cork.fl.fl4_src = saddr;
  20.      inet->cork.fl.fl_ip_sport = inet->inet_sport;
  21.      up->pending = AF_INET;
使用路由的源地址作为包的源地址,检查这个socket是否还有pending的数据——如果有的话,根据注释就是出错了,而且这个不应该发生。正常情况下,设置inet->cork.fl,并置socket为pending状态。
  1. do_append_data:
  2.      up->len += ulen;
  3.      getfrag = is_udplite ? udplite_getfrag : ip_generic_getfrag;
  4.      err = ip_append_data(sk, getfrag, msg->msg_iov, ulen,
  5.                sizeof(struct udphdr), &ipc, &rt,
  6.                corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
  7.      if (err)
  8.           udp_flush_pending_frames(sk);
  9.      else if (!corkreq)
  10.           err = udp_push_pending_frames(sk);
  11.      else if (unlikely(skb_queue_empty(&sk->sk_write_queue)))
  12.           up->pending = 0;
  13.      release_sock(sk);
使用ip_append_data将这次要发送的数据追加到该socket上——这个函数下次进行具体的分析。如果出错,就drop所有socket上的数据包。如果corkreq为0,那么就调用udp_push_pending_frames去发送数据。如果该socket的发送队列为空,那么就将socket的pending状态重置。最后释放socket锁——从这里可以看出,在使用socket发送数据时,当多线程一起发送时,虽然不能保证包的顺序,但是可以保证每个数据包不会与其他数据包混在一起。
  1. out:
  2.      ip_rt_put(rt);
  3.      if (free)
  4.           kfree(ipc.opt);
  5.      if (!err)
  6.           return len;
  7.      /*
  8.      * ENOBUFS = no kernel mem, SOCK_NOSPACE = no sndbuf space. Reporting
  9.      * ENOBUFS might not be good (it's not tunable per se), but otherwise
  10.      * we don't have a good statistic (IpOutDiscards but it can be too many
  11.      * things). We could add another new stat but at least for now that
  12.      * seems like overkill.
  13.      */
  14.      if (err == -ENOBUFS || test_bit(SOCK_NOSPACE, &sk->sk_socket->flags)) {
  15.           UDP_INC_STATS_USER(sock_net(sk),
  16.                     UDP_MIB_SNDBUFERRORS, is_udplite);
  17.      }
  18.      return err;
最后进行资源的释放。如果没有出错,就返回发送的数据长度,如果出错,就增加错误统计。
目录
相关文章
|
7月前
|
存储 Python
Python网络编程基础(Socket编程) UDP 发送和接收数据
【4月更文挑战第10天】对于UDP客户端而言,发送数据是一个相对简单的过程。首先,你需要构建一个要发送的数据报,这通常是一个字节串(bytes)。然后,你可以调用socket对象的`sendto`方法,将数据报发送到指定的服务器地址和端口。
|
7月前
|
存储 Python
Python网络编程基础(Socket编程)UDP客户端编程
【4月更文挑战第9天】在UDP通信中,客户端负责发送数据到服务器,并接收来自服务器的响应。与服务器不同,客户端通常不需要绑定到特定的地址和端口,因为它可以临时使用任何可用的端口来发送数据。下面,我们将详细讲解UDP客户端编程的基本步骤。
|
7月前
|
网络协议 Python
Python网络编程基础(Socket编程)创建UDP socket对象
【4月更文挑战第8天】在Python中创建UDP服务器涉及使用`socket`模块创建socket对象,如`udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)`,然后绑定到特定IP地址和端口,如`udp_socket.bind(('localhost', 12345))`。服务器通过`recvfrom`在无限循环中监听和接收数据报。这只是基础,实际应用还需处理接收、解析、响应及错误处理等。接下来可学习如何利用socket对象进行数据交互以构建完整服务器。
|
2月前
|
存储 网络协议 Java
【网络】UDP回显服务器和客户端的构造,以及连接流程
【网络】UDP回显服务器和客户端的构造,以及连接流程
61 2
|
2月前
|
网络协议 Linux 网络性能优化
Linux基础-socket详解、TCP/UDP
综上所述,Linux下的Socket编程是网络通信的重要组成部分,通过灵活运用TCP和UDP协议,开发者能够构建出满足不同需求的网络应用程序。掌握这些基础知识,是进行更复杂网络编程任务的基石。
178 1
|
7月前
|
网络协议 Java
Java的Socket编程:TCP/IP与UDP深入探索
Java的Socket编程:TCP/IP与UDP深入探索
114 0
|
3月前
|
C语言
C语言 网络编程(七)UDP通信创建流程
本文档详细介绍了使用 UDP 协议进行通信的过程,包括创建套接字、发送与接收消息等关键步骤。首先,通过 `socket()` 函数创建套接字,并设置相应的参数。接着,使用 `sendto()` 函数向指定地址发送数据。为了绑定地址,需要调用 `bind()` 函数。接收端则通过 `recvfrom()` 函数接收数据并获取发送方的地址信息。文档还提供了完整的代码示例,展示了如何实现 UDP 的发送端和服务端功能。
|
3月前
|
网络协议 Linux
TCP 和 UDP 的 Socket 调用
【9月更文挑战第6天】
|
5月前
|
Java 数据格式
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
Java面试题:简述Java Socket编程的基本流程,包括客户端和服务器的创建与通信。
105 0
|
6月前
|
存储 网络协议 数据处理
【Socket】解决UDP丢包问题
UDP(用户数据报协议)是一种无连接的传输层协议,因其不保证数据包的顺序到达和不具备内置重传机制,导致在网络拥塞、接收缓冲区溢出或发送频率过快等情况下容易出现丢包现象。为应对这些问题,可以在应用层实现重传机制、使用前向纠错码等方法。这些方法在一定程度上可以缓解UDP通信中的丢包问题,提高数据传输的可靠性和效率。