本文主要是网络编程——广播和多播的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows windows11 Ubuntu Ubuntu16.04的64位版本 VMware® Workstation 16 Pro 16.2.3 build-19376536 SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 开发板 正点原子 i.MX6ULL Linux阿尔法开发板 uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) linux内核 linux-4.15(NXP官方提供) STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
一、广播编程 1. 广播的概念 在前面介绍的数据包发送方式只有一个接受方,称为单播 ,那什么叫广播呢?广播 (broadcast
)是指封包在计算机网络中传输时,目的地址为网络中所有设备的一种传输方式 。实际上,这里所说的“所有设备”也是限定在一个范围之中,称为广播域 。
也就是说,如果同时发给局域网中的所有主机 ,称为广播。需要注意的是只有用户数据报(使用UDP协议)套接字才能广播 。
2. 广播的缺点
无法针对每个客户的要求和时间及时提供个性化服务。
网络允许服务器提供数据的带宽有限,客户端的最大带宽=服务总带宽。例如有线电视的客户端的线路支持100
个频道(如果采用数字压缩技术,理论上可以提供500
个频道),即使服务商有更大的财力配置更多的发送设备、改成光纤主干,也无法超过此极限。也就是说无法向众多客户提供更多样化、更加个性化的服务。
广播禁止在Internet
宽带网上传输。
3. 广播分类 广播分为两种,直接广播 和本地广播 (也叫受限广播),两者的主要差别如下
直接广播的目标网络和发送端不在同一个网络,本地广播的目标网络就是发送端本机所在的网络。
直接广播的IP
地址,除了网络地址外,其余主机地址全部设置为1
.如希望向网络地址192.168.5
的所有主机传输数据时,发送端的目标IP
要设置为192.168.5.255
传输。而本地广播中使用的IP
地址限定为255.255.255.255
。
需要注意的是本地广播只能在局域网内转发 ,主要用于不知道目标主机的掩码与网络地址的情况,本地转发同本地网络下的所有主机。
4. 广播地址 其实上边已经提过了,主机地址全部为1
的地址就是该网段的广播地址。以192.168.1.0
(255.255.255.0
) 网段为例,最大的主机地址192.168.1.255
代表该网段的广播地址,发到该地址的数据包被所有的主机接收。
【注意】 255.255.255.255
在所有网段中都代表广播地址。
5. 广播编程步骤
创建用户数据报套接字,注意soclet()
函数第二个参数要写为SOCK_DGRAM
。
设置网络属性,允许广播数据包,这是因为默认的情况下创建的套接字是不允许广播数据包的。
接收方地址指定为广播地址。
指定端口信息。
发送数据包。
创建用户数据报套接字,注意soclet()
函数第二个参数要写为SOCK_DGRAM
。
绑定本机IP
地址和端口,绑定的端口必须和发送方指定的端口相同。
等待接收数据。
6. 使用实例 6.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } printf ("Boardcast server starting....OK(UDP)!\n" ); struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); char ipv4_addr[16 ]; char buf[BUFSIZ]; while (1 ) { bzero(buf, BUFSIZ); if (recvfrom(socket_fd, buf, BUFSIZ-1 , 0 ,(struct sockaddr *)&cin , &addrlen ) < 0 ) { perror("recvfrom" ); continue ; } if (!inet_ntop (AF_INET, (void *) &cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Recived from(%s:%d), data:%s" , ipv4_addr, ntohs(cin .sin_port), buf); if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client(%s:%d) is exiting!\n" , ipv4_addr, ntohs(cin .sin_port)); } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); printf ("\n\t Attention: The IP address must be the IP address of the local nic or 0.0.0.0 \n\n" ); }
6.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_br = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &b_br, sizeof (int )); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } printf ("Broadcast client staring...OK(UDP)!\n" ); char buf[BUFSIZ]; while (1 ) { bzero(buf, BUFSIZ); printf (">" ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { perror("fgets" ); continue ; } if (sendto(socket_fd, buf, strlen (buf), 0 , (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("sendto" ); continue ; } if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s boardcast_ip serv_port" , str); printf ("\n\t boardcast_ip: boardcast ip address(x.x.x.255 or 255.255.255.255)" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); }
6.3 Makefile
由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} all : $(TARGET_LIST) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
6.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序server
和客户端程序client
:
1 2 gcc -g -O2 -Wall client.c -o client gcc -g -O2 -Wall server.c -o server
对于服务器端,我们执行以下命令启动服务器进程:
1 ./server 0.0.0.0 5001 # 允许监听所有网卡IP及端口
对于客户端,我们执行以下命令启动客户端进程:
1 2 ./client 192.168.10.255 5001 # 连接到本地一块网卡的IP ./client 192.168.239.255 5001 # 连接到本地一块网卡的IP
然后我们就会看到如下现象:
二、组播编程 1. 组播的概念 我们知道单播方式只能发给一个接收方。广播方式发给所有的主机,而过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。而组播 (又称为多播 )是一种折中的方式,只有加入某个多播组的主机才能收到数据。
组播指的是报文从一个源发出,被转发到一组特定的接收者,相同的报文在每条链路上最多有一份。相较于传统的单播和广播,组播可以有效地节约网络带宽、降低网络负载,所以被广泛应用于IPTV
、实时数据传送和多媒体会议等网络业务中。
组播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)
2. 组播地址 2.1IPv4
组播地址 IANA
将D
类地址空间分配给IPv4
组播使用。IPv4
地址一共32
位,D
类地址最高4
位为1110
,地址范围从224.0.0.0
到239.255.255.255
,具体分类及含义如下:
224.0.0.0 ~ 224.0.0.255 永久组地址。IANA为路由协议预留的IP地址(也称为保留组地址),用于标识一组特定的网络设备,供路由协议、拓扑查找等使用,不用于组播转发。
224.0.1.0 ~ 231.255.255.255 233.0.0.0 ~ 238.255.255.255 ASM组地址,全网范围内有效。
232.0.0.0 ~ 232.255.255.255 缺省情况下的的SSM组地址范围,全网范围内有效。
239.0.0.0 ~ 239.255.255.255 本地管理组地址,仅在本地管理域内有效。在不同的管理域内重复使用相同的本地管理组地址不会导致冲突。
2.2 IPv6
组播地址 IPv6
地址长度是128
位,IPv6
组播地址格式如图所示:
IPv6
组播地址总是以FF
开头,高8
位取值为1111 1111
。
flags
字段(4
位)用来标识组播地址的状态。例如取值为0
表示保留组地址,取值为1
或2
表示ASM
范围内的组播地址,取值为3
表示SSM
范围内的组播地址。
scope
字段(4
位)用来标识组播组的应用范围,指示组播组应用范围是只包含同一本地网络、同一站点、同一机构中的节点,还是包含全球地址空间内的任何节点。
Group ID
(112
位)组播组标识符,用在由scope
字段所指定的范围内标识组播组。
FF0x::/32 保留组地址。
FF1x::/32(x不能是1或者2) FF2x::/32(x不能是1或者2) ASM组地址,全网范围内有效。
FF3x::/32(x不能是1或者2) 缺省的SSM组地址范围,全网范围内有效。
3. 组播编程步骤
创建用户数据报套接字,注意soclet()
函数第二个参数要写为SOCK_DGRAM
。
接收方地址指定为组播地址。
指定端口信息。
发送数据包。
创建用户数据报套接字,注意soclet()
函数第二个参数要写为SOCK_DGRAM
。
加入多播组。
绑定本机IP
地址和端口,绑定的端口必须和发送方指定的端口相同。
等待接收数据。
4. 两个socket
选项 这里我们需要将接收端加入到多播组中,所以我们可能会用到两个socket
的选项,IP_ADD_MEMBERSHIP
*和IP_DROP_MEMBERSHIP
,这两个都属于IPPROTO_IP
层。
我们可以使用man 7 ip
命令查看帮助手册的Socket options
部分来查看这两个选项的详细说明。这两个选项对应的数据类型都是struct ip_mreq
类型的结构体变量,该结构体原型如下:
1 2 3 4 5 6 struct ip_mreq { struct in_addr imn_multiaddr ; struct in_addr imr_interface ; };
使用示例如下:
1 2 3 4 5 6 #define MULTICAST_IP "235.10.10.3" struct ip_mreq mreq ;bzero(&mreq, sizeof (mreq)); mreq.imr_multiaddr.s_addr = inet_addr(MULTICAST_IP); mreq.imr_interface.s_addr = htonl(INADDR_ANY); setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof (mreq));
5. 使用实例 5.1 server
服务器端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <unistd.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 4 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } int b_reuse = 1 ; setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof (int )); struct ip_mreq mreq ; bzero(&mreq, sizeof (mreq)); mreq.imr_multiaddr.s_addr = inet_addr(argv[3 ]); mreq.imr_interface.s_addr = inet_addr(argv[1 ]); setsockopt(socket_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof (mreq)); struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } if (bind(socket_fd, (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("bind" ); exit (-1 ); } printf ("Multicast server starting....OK(UDP)!\n" ); struct sockaddr_in cin ; socklen_t addrlen = sizeof (cin ); char ipv4_addr[16 ]; char buf[BUFSIZ]; while (1 ) { bzero(buf, BUFSIZ); if (recvfrom(socket_fd, buf, BUFSIZ-1 , 0 ,(struct sockaddr *)&cin , &addrlen ) < 0 ) { perror("recvfrom" ); continue ; } if (!inet_ntop (AF_INET, (void *) &cin .sin_addr, ipv4_addr, sizeof (cin ))) { perror ("inet_ntop" ); exit (-1 ); } printf ("Recived from(%s:%d), data:%s" , ipv4_addr, ntohs(cin .sin_port), buf); if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client(%s:%d) is exiting!\n" , ipv4_addr, ntohs(cin .sin_port)); } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s serv_ip serv_port Multicast_ip" , str); printf ("\n\t serv_ip: server ip address" ); printf ("\n\t serv_port: server port(>5000)" ); printf ("\n\t Multicast_ip: Multicast ip address(between 224~239 segment. eg. 235.10.10.3)\n" ); printf ("\n\t Attention: The serv_ip address must be the IP address of the local nic or 0.0.0.0 \n\n" ); }
5.2 client
客户端 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> void usage (char *str) ; int main (int argc, char *argv[]) { int port = -1 ; if (argc != 3 ) { usage(argv[0 ]); exit (-1 ); } port = atoi(argv[2 ]); if (port < 5000 ) { usage(argv[0 ]); exit (-1 ); } int socket_fd = -1 ; if ((socket_fd = socket(AF_INET, SOCK_DGRAM, 0 )) < 0 ) { perror ("socket" ); exit (-1 ); } struct sockaddr_in sin ; bzero (&sin , sizeof (sin )); sin .sin_family = AF_INET; sin .sin_port = htons(port); if (inet_pton(AF_INET, argv[1 ], (void *)&sin .sin_addr) != 1 ) { perror ("inet_pton" ); exit (-1 ); } printf ("Multicast client staring...OK(UDP)!\n" ); char buf[BUFSIZ]; while (1 ) { bzero(buf, BUFSIZ); printf (">" ); if (fgets(buf, BUFSIZ - 1 , stdin ) == NULL ) { perror("fgets" ); continue ; } if (sendto(socket_fd, buf, strlen (buf), 0 , (struct sockaddr *)&sin , sizeof (sin )) < 0 ) { perror("sendto" ); continue ; } if (!strncasecmp(buf, "quit" , strlen ("quit" ))) { printf ("Client is exiting!\n" ); break ; } } close(socket_fd); return 0 ; } void usage (char *str) { printf ("\n%s Multicast_ip serv_port" , str); printf ("\n\t Multicast_ip: boardcast ip address(between 224~239 segment. eg. 235.10.10.3)" ); printf ("\n\t serv_port: server port(>5000)\n\n" ); }
5.3 Makefile
由于需要生成两个可执行程序,自己输命令有些繁琐,这里使用make
来进行。
点击查看 Makefile 文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 CC = gcc DEBUG = -g -O2 -Wall CFLAGS += $(DEBUG) TARGET_LIST = ${patsubst %.c, %, ${wildcard *.c}} all : $(TARGET_LIST) %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ .PHONY : all clean clean_o clean_outclean : clean_o clean_out @rm -vf $(TARGET_LIST) clean_o : @rm -vf *.o clean_out : @rm -vf *.out
5.4 测试结果 我们执行以下命令编译链接程序,生成两个可执行文件:
然后会有如下提示,并生成两个可执行文件吗,分别为服务器端程序server
和客户端程序client
:
1 2 gcc -g -O2 -Wall client.c -o client gcc -g -O2 -Wall server.c -o server
对于服务器端,我们执行以下命令启动服务器进程:
1 2 ./server 0.0.0.0 5001 235.10.10.3 # 允许监听所有网卡IP及端口 ./server 0.0.0.0 5001 235.10.10.3 # 允许监听所有网卡IP及端口
对于客户端,我们执行以下命令启动客户端进程:
1 2 ./client 235.10.10.3 5001 # 发送数据到组播IP ./client 235.10.10.3 5001 # 发送数据到组播IP
然后我们就会看到如下现象:
这里我们可以七佛那个两个服务器,看会不会都能收到数据,服务器启动时,命令重要加入组播地址,用于让服务器加入多播组,客户端直接想组播地址发送数据即可。