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