1. 程式人生 > >Linux核心分析_UDP協議中資料包的收發處理過程

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接收資料的過程