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

1.前言

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

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

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

  (3)UDP发送数据的过程

  (4)UDP接收数据的过程

  

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

  实验楼环境:https://www.shiyanlou.com/courses/1198

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

          https://hujianboo.github.io/2018/12/20/udp%E5%8D%8F%E8%AE%AE%E6%A0%88%E6%BA%90%E7%A0%81%E8%BF%BD%E8%B8%AA%E6%B5%85%E6%9E%90/

  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接收数据的过程

  UDP数据包的接收分为两部分:

  (1)网络层将数据包递交给UDP,UDP接收数据包并对其进行校验,校验成功后放入接收队列中等待用户进程的读取;

  (2)用户进程使用系统调用来读取已经在接收队列中的数据。

  下面分两步来介绍:

5.1 从IP层接收数据包

  这部分函数的调用过程如下图:

  

  在udp_rcv处设置断点,首先调用__udp4_lib_rcv函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1730)。

  

  该函数中验证数据包是否完整,

  

原文地址:https://www.cnblogs.com/dangolqy/p/10152971.html

时间: 2024-11-05 19:39:58

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

20135239 益西拉姆 linux内核分析 进程的切换和系统的一般执行过程

week 8 进程的切换和系统的一般执行过程 [ 20135239 原文请转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000] 一.进程调度与进程调度的时机分析 操作系统原理中介绍了大量进程调度算法,这些算法从实现的角度看仅仅是从运行队列中选择一个新进程,选择的过程中运用了不同的策略而已.对于理解操作系统的工作机制,反而是进程的调度时机与进程的切换机制更为关键. 不同类型的进程有不同的调度需求 第一

Linux内核分析——进程的切换和系统的一般执行过程

进程的切换和系统的一般执行过程 一.进程切换的关键代码switch_to分析 (一)进程调度与进程调度的时机分析 1.不同类型的进程有不同的调度需求 第一种分类: (1)I/O-bound:频繁进行I/O,花费很多时间等待I/O操作的完成. (2)CPU-bound:计算密集型,需要大量CPU时间进行计算. 第二种分类: (1)批处理进程:不必交互.很快响应. (2)实时进程:要求响应时间短. (3)交互式进程(shell). 2.调度策略:是一组规则,它们决定什么时候以怎样的方式选择一个新进程

《Linux内核分析》期末总结

Linux内核设计期中总结 版权声明:本文为博主原创文章,未经博主允许不得转载. 前八周博客汇总及总结 Linux内核设计第一周——从汇编语言出发理解计算机工作原理 我们学习了汇编语言的基础知识,这一部分和内核代码没有直接的关系,但是,老师具体带我们了解了函数调用过程中的堆栈变化,以及函数在调用的过程中是如何传递参数的.这一部分是为了之后学习进程上下文切换.中断上下文切换打基础的. Linux内核设计第二周——操作系统工作原理 老师编写了一个简单小型的内核代码,并带领我们阅读了其中的关键代码.主

Linux内核--网络栈实现分析(七)--数据包的传递过程(下)

本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7545855 更多请查看专栏,地址http://blog.csdn.net/column/details/linux-kernel-net.html 作者:闫明 注:标题中的”(上)“,”(下)“表示分析过程基于数据包的传递方向:”(上)“表示分析是从底层向上分析.”(下)“表示分析是从上向下分析. 在博文Linux内核--网络栈

Linux内核分析 - 网络[十四]:IP选项

Linux内核分析 - 网络[十四]:IP选项 标签: linux内核网络structsocketdst 2012-04-25 17:14 5639人阅读 评论(1) 收藏 举报  分类: 内核协议栈(22)  版权声明:本文为博主原创文章,未经博主允许不得转载. 内核版本:2.6.34      在发送报文时,可以调用函数setsockopt()来设置相应的选项,本文主要分析IP选项的生成,发送以及接收所执行的流程,选取了LSRR为例子进行说明,主要分为选项的生成.选项的转发.选项的接收三部分

《linux 内核分析》 第4周

王一 原创作品转载请注明出处 <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.linux 系统的状态 Linux在x86平台下支持0内核态和3用户态.在内核态32位平台能访问0x00000000以上的空间,而用户态只能访问小于0xc0000000一下的地址空间 (此处的地址空间为逻辑地址).当用户态切换到内核态的时候主要方式为中断. 1.当int128调用时,系统会自动的两个状态下的cs:eip,ss:es

Linux内核分析8

周子轩 原创作品转载请注明出处  <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 实验目的: 使用gdb跟踪分析一个schedule()函数,理解Linux系统中进程调度的时机. 实验过程: 登陆实验楼虚拟机http://www.shiyanlou.com/courses/195 打开shell终端,执行以下命令: cd LinuxKernel rm -rf menu git clone https://git

LINUX内核分析第七周学习总结——可执行程序的装载

LINUX内核分析第六周学习总结——进程的描述和进程的创建 张忻(原创作品转载请注明出处) <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 一.知识概要 (一)预处理.编译.链接和目标文件的格式 1.可执行程序是怎么得来的 2.目标文件的格式ELF 3.静态链接的ELF可执行文件和进程的地址空间 (二)可执行程序.共享库和动态加载 1.装载可执行程序之前的工作 2.装载时动态链接和运行时动态链接应用举例 (三)

《Linux内核分析》第六周学习小结

进程的描述和进程的创建 一.进程的描述 进程描述符task_struct数据结构: (1)操作系统的三大功能: 进程管理.内存管理.文件系统 (2)进程的作用: 将信号.进程间通信.内存管理和文件系统联系起来 (3)进程控制块PCB——task_struct数据结构 提供了内核需要了解的信息 (4)task_struct结构庞大,有400多行代码.包含了进程状态.内核堆栈等相关信息的定义. (5)Linux的进程和操作系统原理中描述的进程状态有所不同,实际内核中,就绪和运行状态都用TASK_RU