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

  linux-3.18.6内核代码:http://codelab.shiyanlou.com/source/xref/linux-3.18.6/

 

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

  参考博客:https://blog.csdn.net/xiaoyu_750516366/article/details/83473758

          https://blog.csdn.net/xiaoyu_750516366/article/details/83552187

  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)。

  

  该函数中首先对数据包进行验证,获取数据包的源IP和目的IP,检查数据包是否完整,计算校验和等。

1740	/*
1741	 *  Validate the packet.
1742	 */
1743	if (!pskb_may_pull(skb, sizeof(struct udphdr)))
1744		goto drop;		/* No space for header. */
1745
1746	uh   = udp_hdr(skb);
1747	ulen = ntohs(uh->len);
1748	saddr = ip_hdr(skb)->saddr;
1749	daddr = ip_hdr(skb)->daddr;
1750
1751	if (ulen > skb->len)
1752		goto short_packet;
1753
1754	if (proto == IPPROTO_UDP) {
1755		/* UDP validates ulen. */
1756		if (ulen < sizeof(*uh) || pskb_trim_rcsum(skb, ulen))
1757			goto short_packet;
1758		uh = udp_hdr(skb);
1759	}
1760
1761	if (udp4_csum_init(skb, uh, proto))
1762		goto csum_error;

   接着执行,调用到udp_queue_rcv_skb函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1484)。

  

  该函数用于接收数据包,继续执行看到该函数调用__udp_queue_rcv_skb函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1441)。

  

  这个函数用于把接收数据放入接收队列之中,主要通过调用sock_queue_rcv_skb函数实现(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/core/sock.c#437)。

1450	rc = sock_queue_rcv_skb(sk, skb);

  sk_receive_queue即是udp的接收队列。

437int sock_queue_rcv_skb(struct sock *sk, struct sk_buff *skb)
438{
439	int err;
440	unsigned long flags;
441	struct sk_buff_head *list = &sk->sk_receive_queue;

  如果数据过大或内存不足时,接收失败。

443	if (atomic_read(&sk->sk_rmem_alloc) >= sk->sk_rcvbuf) {
444		atomic_inc(&sk->sk_drops);
445		trace_sock_rcvqueue_full(sk, skb);
446		return -ENOMEM;
447	}
448
449	err = sk_filter(sk, skb);
450	if (err)
451		return err;
452
453	if (!sk_rmem_schedule(sk, skb, skb->truesize)) {
454		atomic_inc(&sk->sk_drops);
455		return -ENOBUFS;
456	}

   将数据放入队列中,等待用户进程的读取。

468	__skb_queue_tail(list, skb);
469	spin_unlock_irqrestore(&list->lock, flags);
470
471	if (!sock_flag(sk, SOCK_DEAD))
472		sk->sk_data_ready(sk);

 

5.2 用户进程从接收队列中读取数据

  接口函数是udp_recvmsg,在这里设置断点,查看函数调用栈(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/ipv4/udp.c#1244)。

  

  

  单步执行至__skb_recv_datagram函数(http://codelab.shiyanlou.com/source/xref/linux-3.18.6/net/core/datagram.c#164)。

  

   该函数从接收队列中获取skb,使用do while循环来判断是否有新的数据包可取。

179	do {
180		/* Again only user level code calls this function, so nothing
181		 * interrupt level will suddenly eat the receive_queue.
182		 *
183		 * Look at current nfs client by the way...
184		 * However, this function was correct in any case. 8)
185		 */
186		unsigned long cpu_flags;
187		struct sk_buff_head *queue = &sk->sk_receive_queue;
188		int _off = *off;
189
190		last = (struct sk_buff *)queue;
191		spin_lock_irqsave(&queue->lock, cpu_flags);
192		skb_queue_walk(queue, skb) {
193			last = skb;
194			*peeked = skb->peeked;
195			if (flags & MSG_PEEK) {
196				if (_off >= skb->len && (skb->len || _off ||
197							 skb->peeked)) {
198					_off -= skb->len;
199					continue;
200				}
201				skb->peeked = 1;
202				atomic_inc(&skb->users);
203			} else
204				__skb_unlink(skb, queue);
205
206			spin_unlock_irqrestore(&queue->lock, cpu_flags);
207			*off = _off;
208			return skb;
209		}
210		spin_unlock_irqrestore(&queue->lock, cpu_flags);
211
212		if (sk_can_busy_loop(sk) &&
213		    sk_busy_loop(sk, flags & MSG_DONTWAIT))
214			continue;
215
216		/* User doesn't want to wait */
217		error = -EAGAIN;
218		if (!timeo)
219			goto no_packet;
220
221	} while (!wait_for_more_packets(sk, err, &timeo, last));

   回到udp_recvmsg函数中,下面将刚才获取的数据复制到用户空间之中。

1265	ulen = skb->len - sizeof(struct udphdr);
1266	copied = len;
1267	if (copied > ulen)
1268		copied = ulen;
1269	else if (copied < ulen)
1270		msg->msg_flags |= MSG_TRUNC;
1271
1272	/*
1273	 * If checksum is needed at all, try to do it while copying the
1274	 * data.  If the data is truncated, or if we only want a partial
1275	 * coverage checksum (UDP-Lite), do it before the copy.
1276	 */
1277
1278	if (copied < ulen || UDP_SKB_CB(skb)->partial_cov) {
1279		if (udp_lib_checksum_complete(skb))
1280			goto csum_copy_err;
1281	}
1282
1283	if (skb_csum_unnecessary(skb))
1284		err = skb_copy_datagram_iovec(skb, sizeof(struct udphdr),
1285					      msg->msg_iov, copied);
1286	else {
1287		err = skb_copy_and_csum_datagram_iovec(skb,
1288						       sizeof(struct udphdr),
1289						       msg->msg_iov);
1290
1291		if (err == -EINVAL)
1292			goto csum_copy_err;
1293	}
1294
1295	if (unlikely(err)) {
1296		trace_kfree_skb(skb, udp_recvmsg);
1297		if (!peeked) {
1298			atomic_inc(&sk->sk_drops);
1299			UDP_INC_STATS_USER(sock_net(sk),
1300					   UDP_MIB_INERRORS, is_udplite);
1301		}
1302		goto out_free;
1303	}
1304
1305	if (!peeked)
1306		UDP_INC_STATS_USER(sock_net(sk),
1307				UDP_MIB_INDATAGRAMS, is_udplite);
1308
1309	sock_recv_ts_and_drops(msg, sk, skb);
1310
1311	/* Copy the address. */
1312	if (sin) {
1313		sin->sin_family = AF_INET;
1314		sin->sin_port = udp_hdr(skb)->source;
1315		sin->sin_addr.s_addr = ip_hdr(skb)->saddr;
1316		memset(sin->sin_zero, 0, sizeof(sin->sin_zero));
1317		*addr_len = sizeof(*sin);
1318	}
1319	if (inet->cmsg_flags)
1320		ip_cmsg_recv(msg, skb);
1321
1322	err = copied;
1323	if (flags & MSG_TRUNC)
1324		err = ulen;

 

  至此,UDP协议中数据的收发过程已经分析完毕。

  除文中列举出来的博客之外,还参考了书籍《Linux内核源码剖析——TCP/IP实现》(樊东东,莫澜编著)。

  

posted on 2018-12-20 23:42  dangol  阅读(4804)  评论(0编辑  收藏  举报