Chinaunix首页 | 论坛 | 博客
  • 博客访问: 86387
  • 博文数量: 15
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 210
  • 用 户 组: 普通用户
  • 注册时间: 2014-01-05 15:27
文章分类

全部博文(15)

文章存档

2014年(15)

我的朋友

分类: LINUX

2014-03-30 10:25:17

简单的一个udp的服务程序主要有如下几步:
1:调用socket函数创建socket
2:调用bind函数指定在什么地址监听什么端口
3:调用recvfrom以及sendto进行首发包
看一个简单的示例代码:
  1. #include <sys/types.h>
  2. #include <sys/socket.h>
  3. #include <string.h>
  4. #include <netinet/in.h>
  5. #include <stdio.h>
  6. #include <stdlib.h>

  7. #define MAXLINE 80
  8. #define SERV_PORT 8888

  9. void do_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen)
  10. {
  11.     int n;
  12.     socklen_t len;
  13.     char mesg[MAXLINE];

  14.     for(;;)
  15.     {
  16.         len = clilen;
  17.         memset(mesg,0,MAXLINE);
  18.         /* waiting for receive data */
  19.         n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
  20.         /* sent data back to client */
  21.         sendto(sockfd, mesg, n, 0, pcliaddr, len);
  22.     }
  23. }

  24. int main(void)
  25. {
  26.     int sockfd;
  27.     struct sockaddr_in servaddr, cliaddr;

  28.     sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* create a socket */

  29.     /* init servaddr */
  30.     bzero(&servaddr, sizeof(servaddr));
  31.     servaddr.sin_family = AF_INET;
  32.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  33.     servaddr.sin_port = htons(SERV_PORT);

  34.     /* bind address and port to socket */
  35.     if(bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
  36.     {
  37.         perror("bind error");
  38.         exit(1);
  39.     }

  40.     do_echo(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));

  41.     return 0;
  42. }
先看recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
其中sockfd为socket系统调用创建的fd,mesg为接收使用的缓冲区,pcliaddr是客户端的地址,如果只是接收的话这个参数可以不设置,但是后面要回应的话,就需要在接收的时候保存客户端的地址。
先看一下系统调用的入口:

  1. SYSCALL_DEFINE6(recvfrom, int, fd, void __user *, ubuf, size_t, size,
  2.         unsigned, flags, struct sockaddr __user *, addr,
  3.         int __user *, addr_len)
  4. {
  5.     struct socket *sock;
  6.     struct iovec iov;
  7.     struct msghdr msg;
  8.     struct sockaddr_storage address;
  9.     int err, err2;
  10.     int fput_needed;

  11.     if (size > INT_MAX)
  12.         size = INT_MAX;
  13.     sock = sockfd_lookup_light(fd, &err, &fput_needed);  //根据fd找到对应的socket数据结构
  14.     if (!sock)
  15.         goto out;

  16.     msg.msg_control = NULL;  //根据传入参数赋值msghdr数据结构,接收缓冲区的地址和大小赋给msg_iov分量
  17.     msg.msg_controllen = 0;
  18.     msg.msg_iovlen = 1;
  19.     msg.msg_iov = &iov;
  20.     iov.iov_len = size;
  21.     iov.iov_base = ubuf;
  22.     msg.msg_name = (struct sockaddr *)&address; //定义局部变量address,用于保存客户端的地址
  23.     msg.msg_namelen = sizeof(address);
  24.     if (sock->file->f_flags & O_NONBLOCK)
  25.         flags |= MSG_DONTWAIT;
  26.     err = sock_recvmsg(sock, &msg, size, flags);//接收数据,sock->ops->recvmsg(iocb, sock, msg, size, flags);

  27.     if (err >= 0 && addr != NULL) {
  28.         err2 = move_addr_to_user((struct sockaddr *)&address, //把客户端的地址赋值给传入的参数
  29.                      msg.msg_namelen, addr, addr_len);
  30.         if (err2 < 0)
  31.             err = err2;
  32.     }

  33.     fput_light(sock->file, fput_needed);
  34. out:
  35.     return err;
  36. }
sock->ops->recvmsg会调用inet_recvmsg:

  1. int inet_recvmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
  2.          size_t size, int flags)
  3. {
  4.     struct sock *sk = sock->sk;
  5.     int addr_len = 0;
  6.     int err;

  7.     sock_rps_record_flow(sk);

  8.     err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT,   //udp_recvmsg
  9.                    flags & ~MSG_DONTWAIT, &addr_len);
  10.     if (err >= 0)
  11.         msg->msg_namelen = addr_len;
  12.     return err;
  13. }
udp_recvmsg逻辑比较简单,没有数据就睡眠(没有制定nonblock),有数据就拷贝数据到缓冲区

  1. int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
  2.         size_t len, int noblock, int flags, int *addr_len)
  3. {
  4.     struct inet_sock *inet = inet_sk(sk);
  5.     struct sockaddr_in *sin = (struct sockaddr_in *)msg->msg_name;
  6.     struct sk_buff *skb;
  7.     unsigned int ulen;
  8.     int peeked;
  9.     int err;
  10.     int is_udplite = IS_UDPLITE(sk);
  11.     bool slow;

  12.     /*
  13.      * Check any passed addresses
  14.      */
  15.     if (addr_len)
  16.         *addr_len = sizeof(*sin);

  17.     if (flags & MSG_ERRQUEUE)
  18.         return ip_recv_error(sk, msg, len);

  19. try_again:
  20.     skb = __skb_recv_datagram(sk, flags | (noblock ? MSG_DONTWAIT : 0),  //从sk_receive_queue队列中取skb,没有的话睡眠
  21.                   &peeked, &err);
  22.     if (!skb)
  23.         goto out;

  24.     ulen = skb->len - sizeof(struct udphdr);
  25.     if (len > ulen)   //可以看到一次接收调用只会取一个skb
  26.         len = ulen;
  27.     else if (len < ulen)
  28.         msg->msg_flags |= MSG_TRUNC;   //比较skb中数据和接收buffer的大小

  29.     /*
  30.      * If checksum is needed at all, try to do it while copying the
  31.      * data. If the data is truncated, or if we only want a partial
  32.      * coverage checksum (UDP-Lite), do it before the copy.
  33.      */

  34.     if (len < ulen || UDP_SKB_CB(skb)->partial_cov) {  //如果只是拷贝skb的一部分数据的话,需要提前校验全部的数据,因为没法边拷贝边校验
  35.         if (udp_lib_checksum_complete(skb))            //如果硬件已经校验过的话,就不用了
  36.             goto csum_copy_err;
  37.     }

  38.     if (skb_csum_unnecessary(skb))
  39.         err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr),//把skb的数据拷贝到接收buffer中,包含线性区,frags以及frag_list
  40.                           msg->msg_iov, len);
  41.     else {
  42.         err = skb_copy_and_csum_datagram_iovec(skb,
  43.                                sizeof(struct udphdr),
  44.                                msg->msg_iov);

  45.         if (err == -EINVAL)
  46.             goto csum_copy_err;
  47.     }

  48.     if (err)
  49.         goto out_free;

  50.     if (!peeked)
  51.         UDP_INC_STATS_USER(sock_net(sk),
  52.                 UDP_MIB_INDATAGRAMS, is_udplite);

  53.     sock_recv_ts_and_drops(msg, sk, skb);

  54.     /* Copy the address. */
  55.     if (sin) {
  56.         sin->sin_family = AF_INET;
  57.         sin->sin_port = udp_hdr(skb)->source;  //客户端的端口号
  58.         sin->sin_addr.s_addr = ip_hdr(skb)->saddr;//客户端的ip地址,后续回应的时候,sendto系统调用使用
  59.         memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
  60.     }
  61.     if (inet->cmsg_flags)
  62.         ip_cmsg_recv(msg, skb);

  63.     err = len;
  64.     if (flags & MSG_TRUNC)
  65.         err = ulen;

  66. out_free:
  67.     skb_free_datagram_locked(sk, skb);
  68. out:
  69.     return err;

  70. csum_copy_err:
  71.     slow = lock_sock_fast(sk);
  72.     if (!skb_kill_datagram(sk, skb, flags))
  73.         UDP_INC_STATS_USER(sock_net(sk), UDP_MIB_INERRORS, is_udplite);
  74.     unlock_sock_fast(sk, slow);
  75.     if (noblock)
  76.         return -EAGAIN;

  77.     /* starting over for a new packet */
  78.     msg->msg_flags &= ~MSG_TRUNC;
  79.     goto try_again;
  80. }
该函数逻辑比较简单,其中取skb调用__skb_recv_datagram函数:

  1. struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
  2.                     int *peeked, int *err)
  3. {
  4.     struct sk_buff *skb;
  5.     long timeo;
  6.     /*
  7.      * Caller is allowed not to check sk->sk_err before skb_recv_datagram()
  8.      */
  9.     int error = sock_error(sk);

  10.     if (error)
  11.         goto no_packet;

  12.     timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT); //noblock ? 0 : sk->sk_rcvtimeo;初始化的时候sk_rcvtimeo设置为死等

  13.     do {
  14.         /* Again only user level code calls this function, so nothing
  15.          * interrupt level will suddenly eat the receive_queue.
  16.          *
  17.          * Look at current nfs client by the way...
  18.          * However, this function was correct in any case. 8)
  19.          */
  20.         unsigned long cpu_flags;

  21.         spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
  22.         skb = skb_peek(&sk->sk_receive_queue);
  23.         if (skb) {
  24.             *peeked = skb->peeked;
  25.             if (flags & MSG_PEEK) {
  26.                 skb->peeked = 1;
  27.                 atomic_inc(&skb->users);
  28.             } else
  29.                 __skb_unlink(skb, &sk->sk_receive_queue);
  30.         }
  31.         spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);

  32.         if (skb)  //有数据就返回,没有数据就等待
  33.             return skb;

  34.         error = -EAGAIN;
  35.         if (!timeo)
  36.             goto no_packet;
  37.      } while (!wait_for_packet(sk, err, &timeo));  //把自己放到sk_sleep对应的等待链表中,然后调用schedule_timeout
  38.      return NULL;
  39. no_packet:
  40.     *err = error;
  41.     return NULL;
  42. }
udp的接收系统调用比较简单,不涉及各种状态的转换,有数据的时候就拷贝,没有的话等待,当底层收到包的时候,会唤醒当前进程

下面分析bind流程:bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)
该系统调用会调inet_bind:
  1. int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
  2. {
  3.     struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;
  4.     struct sock *sk = sock->sk;
  5.     struct inet_sock *inet = inet_sk(sk);
  6.     unsigned short snum;
  7.     int chk_addr_ret;
  8.     int err;

  9.     /* If the socket has its own bind function then use it. (RAW) */
  10.     if (sk->sk_prot->bind) {  //udp没有自己的bind函数
  11.         err = sk->sk_prot->bind(sk, uaddr, addr_len);
  12.         goto out;
  13.     }
  14.     err = -EINVAL;
  15.     if (addr_len < sizeof(struct sockaddr_in))
  16.         goto out;

  17.     if (addr->sin_family != AF_INET) {
  18.         /* Compatibility games : accept AF_UNSPEC (mapped to AF_INET)
  19.          * only if s_addr is INADDR_ANY.
  20.          */
  21.         err = -EAFNOSUPPORT;
  22.         if (addr->sin_family != AF_UNSPEC ||
  23.             addr->sin_addr.s_addr != htonl(INADDR_ANY))
  24.             goto out;
  25.     }

  26.     chk_addr_ret = inet_addr_type(sock_net(sk), addr->sin_addr.s_addr);

  27.     /* Not specified by any standard per-se, however it breaks too
  28.      * many applications when removed. It is unfortunate since
  29.      * allowing applications to make a non-local bind solves
  30.      * several problems with systems using dynamic addressing.
  31.      * (ie. your servers still start up even if your ISDN link
  32.      * is temporarily down)
  33.      */
  34.     err = -EADDRNOTAVAIL;
  35.     if (!sysctl_ip_nonlocal_bind &&   //bind绑定的地址一般为INADDR_ANY或者一个本地的ip值
  36.         !(inet->freebind || inet->transparent) &&
  37.         addr->sin_addr.s_addr != htonl(INADDR_ANY) &&
  38.         chk_addr_ret != RTN_LOCAL &&
  39.         chk_addr_ret != RTN_MULTICAST &&
  40.         chk_addr_ret != RTN_BROADCAST)
  41.         goto out;

  42.     snum = ntohs(addr->sin_port); //bind对应的端口号
  43.     err = -EACCES;
  44.     if (snum && snum < PROT_SOCK && !capable(CAP_NET_BIND_SERVICE))  //0-1023只有root用户可以设置
  45.         goto out;

  46.     /* We keep a pair of addresses. rcv_saddr is the one
  47.      * used by hash lookups, and saddr is used for transmit.
  48.      *
  49.      * In the BSD API these are the same except where it
  50.      * would be illegal to use them (multicast/broadcast) in
  51.      * which case the sending device address is used.
  52.      */
  53.     lock_sock(sk);

  54.     /* Check these errors (active socket, double bind). */
  55.     err = -EINVAL;
  56.     if (sk->sk_state != TCP_CLOSE || inet->inet_num)
  57.         goto out_release_sock;

  58.     inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;//inet_rcv_saddr用于hash查找,参考__udp4_lib_lookup_skb
  59.     if (chk_addr_ret == RTN_MULTICAST || chk_addr_ret == RTN_BROADCAST)
  60.         inet->inet_saddr = 0; /* Use device */

  61.     /* Make sure we are allowed to bind here. */
  62.     if (sk->sk_prot->get_port(sk, snum)) {    //udp_v4_get_port
  63.         inet->inet_saddr = inet->inet_rcv_saddr = 0;
  64.         err = -EADDRINUSE;
  65.         goto out_release_sock;
  66.     }

  67.     if (inet->inet_rcv_saddr)
  68.         sk->sk_userlocks |= SOCK_BINDADDR_LOCK;
  69.     if (snum)
  70.         sk->sk_userlocks |= SOCK_BINDPORT_LOCK;
  71.     inet->inet_sport = htons(inet->inet_num);  //用于hash查找
  72.     inet->inet_daddr = 0;
  73.     inet->inet_dport = 0;
  74.     sk_dst_reset(sk);
  75.     err = 0;
  76. out_release_sock:
  77.     release_sock(sk);
  78. out:
  79.     return err;
  80. }

















阅读(2678) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~