​javascript:void(0)​


前面介绍的函数基本上都是TCP协议的,如listen,connect,accept 等函数,这都是为可靠传输协议TCP定制的。对于另一个不可靠udp协议(通信系统其可靠性交由上层应用层负责),则主要由两个函数完成,sendto 和 recvfrom 函数。这里先介绍 sendto 函数。

说明:sendto 和 recvfrom 函数不限于udp协议,这里只是udp协议当中是采用这两个函数实现的,所以就放在udp协议中介绍。

对于 udp 协议的介绍和编程实现请参考下文:​​UDP 客户/服务器简单 Socket 程序​

简要介绍下UDP数据报格式,相比TCP数据报格式,实在是简洁不少。

                                  【Linux 内核网络协议栈源码剖析】sendto 函数剖析_套接字

上面的各个字段含义一目了然(上面是16是表示该字段占16bit,udp头部占8字节),其中长度指的是此 UDP 数据报的长度(包括 UDP 数据报头部和 “数据” 部分)。

一、应用层——sendto 函数

[cpp]​ view plain​​​ ​​ copy​​​ ​​ print​​​​?​

  1. #include <sys/socket.h>
  2. ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags,
  3. const struct sockaddr *to, socklen_t *addrlen);
  4. //若成功则返回写的字节数,出错则返回-1
  5. /*参数解析
  6. 前面三个参数分别表示:套接字描述符,指向写出缓冲区的指针和写字节数。
  7. to:指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小由addrlen参数指定
  8. */

该函数的作用是:向指定端口发送给定地址中的指定大小数据(如客户端sockfd,向 to 指定的远端套接字发送buff 缓冲区内nbytes 个字节数据)

二、BSD Socket层——sock_sendto 函数

[cpp]​ view plain​​​ ​​ copy​​​ ​​ print​​​​?​

  1. /*
  2. *  Send a datagram to a given address. We move the address into kernel
  3. *  space and check the user space data area is readable before invoking
  4. *  the protocol.
  5. */
  6. //发送数据给指定的远端地址,主要用于UDP协议
  7. //前面三个参数分别表示套接口描述字、指向缓冲区的指针和读写字节数
  8. //addr指向一个含有数据包接收者的协议地址(含ip地址和端口号)的套接口地址结构
  9. //其大小由addr_len参数指定
  10. //该函数的作用就是向指定地址的远端发送数据包:将buff缓冲区中len大小的数据发送给addr指定的远端套接字
  11. static int sock_sendto(int fd, void * buff, int len, unsigned flags,
  12. struct sockaddr *addr, int addr_len)
  13. {
  14. struct socket *sock;
  15. struct file *file;
  16. char address[MAX_SOCK_ADDR];
  17. int err;
  18. //参数有效性检查
  19. if (fd < 0 || fd >= NR_OPEN || ((file = current->files->fd[fd]) == NULL))
  20. return(-EBADF);
  21. //找到给定文件描述符对应的socket结构
  22. if (!(sock = sockfd_lookup(fd, NULL)))
  23. return(-ENOTSOCK);
  24. if(len<0)
  25. return -EINVAL;
  26. //检查权限,buff中len个字节区域是否可读
  27. err=verify_area(VERIFY_READ,buff,len);
  28. if(err)
  29. return err;
  30. //从addr拷贝addr_len大小的数据到address
  31. if((err=move_addr_to_kernel(addr,addr_len,address))<0)
  32. return err;
  33. //调用下层函数sendto,inet域为inet_sendto函数
  34. return(sock->ops->sendto(sock, buff, len, (file->f_flags & O_NONBLOCK),
  35. flags, (struct sockaddr *)address, addr_len));
  36. }

三、INET Socket层——inet_sendto 函数

[cpp]​ view plain​​​ ​​ copy​​​ ​​ print​​​​?​

  1. //INET socket层
  2. tatic int inet_sendto(struct socket *sock, void *ubuf, int size, int noblock,
  3. unsigned flags, struct sockaddr *sin, int addr_len)
  4. //得到socket对应的sock结构
  5. struct sock *sk = (struct sock *) sock->data;
  6. //判断该套接字的有效性,是否处于关闭状态(半关闭)
  7. if (sk->shutdown & SEND_SHUTDOWN)
  8. {
  9. send_sig(SIGPIPE, current, 1);
  10. return(-EPIPE);
  11. }
  12. if (sk->prot->sendto == NULL)
  13. return(-EOPNOTSUPP);
  14. if(sk->err)
  15. return inet_error(sk);
  16. /* We may need to bind the socket. */
  17. //自动绑定一个本地端口号
  18. if(inet_autobind(sk)!=0)
  19. return -EAGAIN;
  20. //调用下层传输层函数udp_sendto函数
  21. return(sk->prot->sendto(sk, (unsigned char *) ubuf, size, noblock, flags,
  22. (struct sockaddr_in *)sin, addr_len));

四、传输层

udp_sento 函数

[cpp]​ view plain​​​ ​​ copy​​​ ​​ print​​​​?​

  1. static int udp_sendto(struct sock *sk, unsigned char *from, int len, int noblock,
  2. unsigned flags, struct sockaddr_in *usin, int addr_len)
  3. {
  4. struct sockaddr_in sin;
  5. int tmp;
  6. /*
  7. *  Check the flags. We support no flags for UDP sending
  8. */
  9. //udp除了MSG_DONTROUTE外,不支持任何其他标志位
  10. if (flags&~MSG_DONTROUTE)
  11. return(-EINVAL);
  12. /*
  13. *  Get and verify the address.
  14. */
  15. //对远端地址的合法性检查,由于不涉及网络数据传送,所以无法验证这个地址存在性
  16. if (usin)
  17. {
  18. //如果明确指定远端地址,就直接检查该地址的有效性
  19. if (addr_len < sizeof(sin)) //大小
  20. return(-EINVAL);
  21. memcpy(&sin,usin,sizeof(sin));
  22. if (sin.sin_family && sin.sin_family != AF_INET) //本地地址有效性
  23. return(-EINVAL);
  24. if (sin.sin_port == 0) //端口号有效性
  25. return(-EINVAL);
  26. }
  27. else
  28. {
  29. //如果没有明确指定远端地址,则检查之前是否调用了connect函数进行了地址绑定
  30. if (sk->state != TCP_ESTABLISHED)
  31. return(-EINVAL);
  32. //如果进行了绑定,则将远端地址设置为这个绑定的地址
  33. sin.sin_family = AF_INET;
  34. sin.sin_port = sk->dummy_th.dest;
  35. sin.sin_addr.s_addr = sk->daddr;
  36. }
  37. /*
  38. *  BSD socket semantics. You must set SO_BROADCAST to permit
  39. *  broadcasting of data.
  40. */
  41. //处理尚未指定本地地址的情况
  42. if(sin.sin_addr.s_addr==INADDR_ANY)
  43. sin.sin_addr.s_addr=ip_my_addr();
  44. //处理广播的情况
  45. if(!sk->broadcast && ip_chk_addr(sin.sin_addr.s_addr)==IS_BROADCAST)
  46. return -EACCES;         /* Must turn broadcast on first */
  47. sk->inuse = 1;//加锁
  48. /* Send the packet. */
  49. //转调用udp_send函数
  50. tmp = udp_send(sk, &sin, from, len, flags);
  51. /* The datagram has been sent off.  Release the socket. */
  52. //数据包以发送,释放该套接字,前面介绍到这个函数的两个功能
  53. //取决于sk_dead字段是否设置
  54. release_sock(sk);
  55. return(tmp);
  56. }

udp_send 函数

[cpp]​ view plain​​​ ​​ copy​​​ ​​ print​​​​?​

  1. //根据被调用出清楚参数情况
  2. static int udp_send(struct sock *sk, struct sockaddr_in *sin,
  3. unsigned char *from, int len, int rt)
  4. {
  5. struct sk_buff *skb;
  6. struct device *dev;
  7. struct udphdr *uh;
  8. unsigned char *buff;
  9. unsigned long saddr;
  10. int size, tmp;
  11. int ttl;
  12. /*
  13. *  Allocate an sk_buff copy of the packet.
  14. */
  15. //计算所需要分配的封装数据的缓冲区大小
  16. size = sk->prot->max_header + len;
  17. //分配指定大小的sk_buff 结构用于封装数据
  18. skb = sock_alloc_send_skb(sk, size, 0, &tmp);
  19. if (skb == NULL)
  20. return tmp;
  21. skb->sk       = NULL;    /* to avoid changing sk->saddr */
  22. skb->free     = 1;//发送完后数据包立即释放,udp不提供超时重传
  23. skb->localroute = sk->localroute|(rt&MSG_DONTROUTE);//指定路由类型
  24. /*
  25. *  Now build the IP and MAC header.
  26. */
  27. buff = skb->data;//udp首部和有效负载
  28. saddr = sk->saddr;//本地地址
  29. dev = NULL;
  30. ttl = sk->ip_ttl;
  31. #ifdef CONFIG_IP_MULTICAST
  32. //如果目的地址是多播,则设置TTL值为1,表示局限于本地网络,不可跨越路由器
  33. if (MULTICAST(sin->sin_addr.s_addr))
  34. ttl = sk->ip_mc_ttl;
  35. #endif
  36. //创建MAC首部和IP首部
  37. tmp = sk->prot->build_header(skb, saddr, sin->sin_addr.s_addr,
  38. &dev, IPPROTO_UDP, sk->opt, skb->mem_len,sk->ip_tos,ttl);
  39. skb->sk=sk;//关联  /* So memory is freed correctly */
  40. /*
  41. *  Unable to put a header on the packet.
  42. */
  43. if (tmp < 0 ) //创建失败
  44. {
  45. sk->prot->wfree(sk, skb->mem_addr, skb->mem_len);
  46. return(tmp);
  47. }
  48. buff += tmp;//定位到udp首部位置
  49. saddr = skb->saddr; /*dev->pa_addr;*/
  50. //数据报sk_buff中挂载的数据部分长度:下面注释,len是有效数据负载长度
  51. skb->len = tmp + sizeof(struct udphdr) + len;    /* len + UDP + IP + MAC */
  52. skb->dev = dev;//网络接口设备
  53. /*
  54. *  Fill in the UDP header.
  55. */
  56. //udp首部字段的初始化
  57. uh = (struct udphdr *) buff;
  58. uh->len = htons(len + sizeof(struct udphdr));//长度字段
  59. uh->source = sk->dummy_th.source;//源端端口,sk中tcp首部字段
  60. uh->dest = sin->sin_port;//目的端口
  61. buff = (unsigned char *) (uh + 1);//定位到数据部分
  62. //MAC header | IP Header | UDP Header | Data
  63. //uh本身已经指向了udp首地址,uh+1,表示后移一个udp首部大小位置,定位到了数据负载
  64. /*
  65. *  Copy the user data.
  66. */
  67. //从from拷贝len大小的数据到buff,即把应用层中待发送的缓冲区的数据拷贝到数据包的数据负载中
  68. //然后通过数据包整体打包发送出去。
  69. //就好比货物搭上了货轮开往目的地,为啥不是火车呢,因为火车线路已经固定好了,只能这么走。
  70. memcpy_fromfs(buff, from, len);
  71. /*
  72. *  Set up the UDP checksum.
  73. */
  74. //同tcp,这里进行udp校验和检查
  75. udp_send_check(uh, saddr, sin->sin_addr.s_addr, skb->len - tmp, sk);
  76. /*
  77. *  Send the datagram to the interface.
  78. */
  79. udp_statistics.UdpOutDatagrams++;
  80. //调用ip_queue_xmit函数将数据包发往网络层模块处理。以下处理就和TCP协议一样了,二者的差异只在于传输层
  81. //该函数以及更下层数据传送前面已经介绍,
  82. sk->prot->queue_xmit(sk, dev, skb, 1);
  83. return(len);
  84. }

关于ip_queue_xmit 函数的介绍以及更下层的数据传送,参见博文:​​【Linux 内核网络协议栈源码剖析】数据包发送​

可以看出,udp是一种无连接传输层协议,不像tcp那样需要服务器监听,也不必等待客户端与服务器建立连接后才能通信,效率优于tcp协议,但udp则不能保证数据传输的可靠性。

udp 的数据传输,实现并不像tcp那样要建立一条数据传输通道,而是直接创建套接字后,直接传送数据到给定的远端(提供远端地址),数据传送过程无超时重传和序列号校验工作,适用于数据传输的连续性比数据的完整性更重要的场合,允许数据在传输过程中有部分丢失,如IP电话、流媒体通信等。