Linux内核分析_UDP协议中数据包的收发处理过程

1.前言

  实验基于Linux kernel 3.18.6,实验内容包括:

  (1)编写UDP客户端和服务端

  (2)将UDP客户端和服务端集成到MenuOS中

  (3)UDP发送数据的过程

  (4)UDP接收数据的过程

  

  本文中完整源码:https://github.com/dangolqy/udp

  实验楼环境:https://github.com/dangolqy/udp

2.UDP客户端和服务端

  参考博客:https://blog.csdn.net/lell3538/article/details/53335472

  服务端server.c

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<sys/types.h>
 6 #include<sys/socket.h>
 7 #include<netinet/in.h>
 8 #include<string.h>
 9  
10 #define MYPORT 5678
11  
12  
13 #define ERR_EXIT(m) \
14     do { \
15     perror(m); \
16     exit(EXIT_FAILURE); \
17     } while (0)
18  
19 void echo_ser(int sock)
20 {
21     char recvbuf[1024] = {0};
22     struct sockaddr_in peeraddr;
23     socklen_t peerlen;
24     int n;
25     
26     while (1)
27     {
28         
29         peerlen = sizeof(peeraddr);
30         memset(recvbuf, 0, sizeof(recvbuf));
31         n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
32                      (struct sockaddr *)&peeraddr, &peerlen);
33         if (n <= 0)
34         {
35             
36             if (errno == EINTR)
37                 continue;
38             
39             ERR_EXIT("recvfrom error");
40         }
41         else if(n > 0)
42         {
43             printf("接收到的数据:%s\n",recvbuf);
44             sendto(sock, recvbuf, n, 0,
45                    (struct sockaddr *)&peeraddr, peerlen);
46             printf("回送的数据:%s\n",recvbuf);
47         }
48     }
49     close(sock);
50 }
51  
52 int main(void)
53 {
54     int sock;
55     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
56         ERR_EXIT("socket error");
57     
58     struct sockaddr_in servaddr;
59     memset(&servaddr, 0, sizeof(servaddr));
60     servaddr.sin_family = AF_INET;
61     servaddr.sin_port = htons(MYPORT);
62     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
63     
64     printf("监听%d端口\n",MYPORT);
65     if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
66         ERR_EXIT("bind error");
67     
68     echo_ser(sock);
69     
70     return 0;
71 }

  客户端代码client.c

 1 #include <unistd.h>
 2 #include <sys/types.h>
 3 #include <sys/socket.h>
 4 #include <netinet/in.h>
 5 #include <arpa/inet.h>
 6 #include <stdlib.h>
 7 #include <stdio.h>
 8 #include <errno.h>
 9 #include <string.h>
10  
11 #define MYPORT 5678
12 char* SERVERIP = "127.0.0.1";
13  
14 #define ERR_EXIT(m) \
15     do \
16 { \
17     perror(m); \
18     exit(EXIT_FAILURE); \
19     } while(0)
20  
21 void echo_cli(int sock)
22 {
23     struct sockaddr_in servaddr;
24     memset(&servaddr, 0, sizeof(servaddr));
25     servaddr.sin_family = AF_INET;
26     servaddr.sin_port = htons(MYPORT);
27     servaddr.sin_addr.s_addr = inet_addr(SERVERIP);
28     
29     int ret;
30     char sendbuf[1024] = {0};
31     char recvbuf[1024] = {0};
32 
33     fgets(sendbuf, sizeof(sendbuf), stdin);
34      
35         printf("向服务器发送:%s\n",sendbuf);
36         sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
37         
38         ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
39         printf("从服务器接收:%s\n",recvbuf);
40     
41     close(sock);
42     
43     
44 }
45  
46 int main(void)
47 {
48     int sock;
49     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
50         ERR_EXIT("socket");
51     
52     echo_cli(sock);
53     
54     return 0;
55 }

  在实验楼环境中的运行结果:

3.将UDP客户端和服务端集成到MenuOS中

  仿照老师写好的TCP代码,将上面两个文件中的代码添加进main.c中。

  其中,server.c和client.c中的函数部分略作修改写进.h文件中作为要引用的头文件,main函数部分作为新的函数写进main.c中,最后再在main.c的main函数中增加udp菜单选项。具体如下:

  server.h

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<unistd.h>
 4 #include<errno.h>
 5 #include<sys/types.h>
 6 #include<sys/socket.h>
 7 #include<netinet/in.h>
 8 #include<string.h>
 9  
10 #define MYPORT 5678
11  
12  
13 #define ERR_EXIT(m) \
14     do { \
15     perror(m); \
16     exit(EXIT_FAILURE); \
17     } while (0)
18  
19 void echo_ser(int sock)
20 {
21     char recvbuf[1024] = {0};
22     char reply[1024]={'h','i'};
23     struct sockaddr_in peeraddr;
24     socklen_t peerlen;
25     int n;
26     
27     while(1){
28         peerlen = sizeof(peeraddr);
29         memset(recvbuf, 0, sizeof(recvbuf));
30         n = recvfrom(sock, recvbuf, sizeof(recvbuf), 0,
31                      (struct sockaddr *)&peeraddr, &peerlen);
32 
33         if(n > 0)
34         {
35             printf("server receive:%s\n",recvbuf);
36             sendto(sock, reply, strlen(reply), 0,
37                    (struct sockaddr *)&peeraddr, peerlen);
38             printf("server reply:%s\n",reply);
39         }
40         else
41          break;
42     }
43  
44     close(sock);
45 }

  client.h

 1 #include <unistd.h>
 2 #include <sys/types.h>
 3 #include <sys/socket.h>
 4 #include <netinet/in.h>
 5 #include <arpa/inet.h>
 6 #include <stdlib.h>
 7 #include <stdio.h>
 8 #include <errno.h>
 9 #include <string.h>
10  
11 #define MYPORT 5678
12 char* SERVERIP = "127.0.0.1";
13 
14  
15 void echo_cli(int sock)
16 {
17     struct sockaddr_in servaddr;
18     memset(&servaddr, 0, sizeof(servaddr));
19     servaddr.sin_family = AF_INET;
20     servaddr.sin_port = htons(MYPORT);
21     servaddr.sin_addr.s_addr = inet_addr(SERVERIP);
22     
23     int ret;
24     char sendbuf[1024] = {'h','e','l','l','o'};
25     char recvbuf[1024] = {0};
26         
27     printf("client send to server:%s\n",sendbuf);
28     sendto(sock, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
29         
30     ret = recvfrom(sock, recvbuf, sizeof(recvbuf), 0, NULL, NULL);
31     printf("client receive from server:%s\n",recvbuf);
32     
33     close(sock);
34     
35 }

  main.c(部分)

 1 #include "server.h"
 2 #include "client.h"
 3 
 4 int UdpReplyhi()
 5 {
 6     int sock;
 7     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
 8         ERR_EXIT("socket error");
 9     
10     struct sockaddr_in servaddr;
11     memset(&servaddr, 0, sizeof(servaddr));
12     servaddr.sin_family = AF_INET;
13     servaddr.sin_port = htons(MYPORT);
14     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
15     
16     printf("server listen to port:%d\n",MYPORT);
17     if (bind(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)
18         ERR_EXIT("bind error");
19     
20     echo_ser(sock);
21     
22     return 0;
23 }
24 
25 int StartUdpReplyHi(int argc, char *argv[])
26 {
27     int pid;
28     /* fork another process */
29     pid = fork();
30     if (pid < 0)
31     {
32         /* error occurred */
33         fprintf(stderr, "Fork Failed!");
34         exit(-1);
35     }
36     else if (pid == 0)
37     {
38         /*     child process     */
39         UdpReplyhi();
40         printf("Reply hi UDP Service Started!\n");
41     }
42     else
43     {
44         /*     parent process     */
45         printf("Please input hello...\n");
46     }
47 }
48 
49 int UdpHello(int argc, char *argv[])
50 {
51     int sock;
52     if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
53         ERR_EXIT("socket");
54     
55     echo_cli(sock);
56     
57     return 0;
58 }
1 MenuConfig("udpreplyhi", "Reply hi UDP Service", StartUdpReplyHi);
2 MenuConfig("udphello", "Hello UDP Client", UdpHello);

  在实验楼中的运行结果:

4.UDP发送数据的过程

  参考博客:https://blog.csdn.net/u010246947/article/details/18253345

       http://blog.chinaunix.net/uid-14528823-id-4468600.html

  UDP报文发送的内核主要调用流程如下图:

  

  在udp_sendmsg处设置断点,查看函数调用栈:

   查看inet_sendmsg函数的代码(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/af_inet.c#721),在红色框标注出来的地方,调用对应传输层协议的sendmsg方法,在这里就是udp_sendmsg。

  单步执行至ip_make_skb,查看该函数的源码,它调用了__ip_make_skb(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/ip_output.c#1320)。

  在这个函数中,主要进行从缓冲队列中拿出数据送至skb中,添加IP协议头等操作。

1342    while ((tmp_skb = __skb_dequeue(queue)) != NULL) {
1343        __skb_pull(tmp_skb, skb_network_header_len(skb));
1344        *tail_skb = tmp_skb;
1345        tail_skb = &(tmp_skb->next);
1346        skb->len += tmp_skb->len;
1347        skb->data_len += tmp_skb->len;
1348        skb->truesize += tmp_skb->truesize;
1349        tmp_skb->destructor = NULL;
1350        tmp_skb->sk = NULL;
1351    }
1353    /* Unless user demanded real pmtu discovery (IP_PMTUDISC_DO), we allow
1354     * to fragment the frame generated here. No matter, what transforms
1355     * how transforms change size of the packet, it will come out.
1356     */
1357    skb->ignore_df = ip_sk_ignore_df(sk);
1358
1359    /* DF bit is set when we want to see DF on outgoing frames.
1360     * If ignore_df is set too, we still allow to fragment this frame
1361     * locally. */
1362    if (inet->pmtudisc == IP_PMTUDISC_DO ||
1363        inet->pmtudisc == IP_PMTUDISC_PROBE ||
1364        (skb->len <= dst_mtu(&rt->dst) &&
1365         ip_dont_fragment(sk, &rt->dst)))
1366        df = htons(IP_DF);
1367
1368    if (cork->flags & IPCORK_OPT)
1369        opt = cork->opt;
1370
1371    if (cork->ttl != 0)
1372        ttl = cork->ttl;
1373    else if (rt->rt_type == RTN_MULTICAST)
1374        ttl = inet->mc_ttl;
1375    else
1376        ttl = ip_select_ttl(inet, &rt->dst);
1377
1378    iph = ip_hdr(skb);
1379    iph->version = 4;
1380    iph->ihl = 5;
1381    iph->tos = (cork->tos != -1) ? cork->tos : inet->tos;
1382    iph->frag_off = df;
1383    iph->ttl = ttl;
1384    iph->protocol = sk->sk_protocol;
1385    ip_copy_addrs(iph, fl4);
1386    ip_select_ident(skb, sk);
1387
1388    if (opt) {
1389        iph->ihl += opt->optlen>>2;
1390        ip_options_build(skb, opt, cork->addr, rt, 0);
1391    }
1392
1393    skb->priority = (cork->tos != -1) ? cork->priority: sk->sk_priority;
1394    skb->mark = sk->sk_mark;
1395    /*
1396     * Steal rt from cork.dst to avoid a pair of atomic_inc/atomic_dec
1397     * on dst refcount
1398     */
1399    cork->dst = NULL;
1400    skb_dst_set(skb, &rt->dst);
1401
1402    if (iph->protocol == IPPROTO_ICMP)
1403        icmp_out_count(net, ((struct icmphdr *)
1404            skb_transport_header(skb))->type);
1405
1406    ip_cork_release(cork);

  继续单步执行到udp_send_skb函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#783)。

  该函数中为数据包添加了udp报头,包括计算校验和等内容。

794    /*
795     * Create a UDP header
796     */
797    uh = udp_hdr(skb);
798    uh->source = inet->inet_sport;
799    uh->dest = fl4->fl4_dport;
800    uh->len = htons(len);
801    uh->check = 0;
802
803    if (is_udplite)                   /*     UDP-Lite      */
804        csum = udplite_csum(skb);
805
806    else if (sk->sk_no_check_tx) {   /* UDP csum disabled */
807
808        skb->ip_summed = CHECKSUM_NONE;
809        goto send;
810
811    } else if (skb->ip_summed == CHECKSUM_PARTIAL) { /* UDP hardware csum */
812
813        udp4_hwcsum(skb, fl4->saddr, fl4->daddr);
814        goto send;
815
816    } else
817        csum = udp_csum(skb);
818
819    /* add protocol-dependent pseudo-header */
820    uh->check = csum_tcpudp_magic(fl4->saddr, fl4->daddr, len,
821                      sk->sk_protocol, csum);
822    if (uh->check == 0)
823        uh->check = CSUM_MANGLED_0;

  继续单步执行,到这里udp数据包已经准备好,即将交给IP层进行发送。传输层的数据发送相关流程到此结束。

5.UDP接收数据的过程

  

猜你喜欢

转载自www.cnblogs.com/dangolqy/p/10152971.html
今日推荐