- 英文小册原文地址:beej.us/guide/bgnet…
- 作者:Beej
- 中文翻译地址:www.chanmufeng.com/posts/netwo…
到目前为止,我们已经讨论了怎么从一台主机发送数据到另一台主机。但是,如果有这种可能,你肯定想同时把数据发送给多台主机。
用UDP
(只能用UDP
,TCP
不行)和标准的IPv4,可以通过一种叫做广播(broadcasting)的机制来实现。IPv6不支持广播,你得用一种更高级的技术——多播(multicasting)来实现。这个技术我们这次不会讲,毕竟我们现在还停留在IPv4的32位阶段呢,就不异想天开了。
Wait!你别溜走,自己偷摸开始广播,我还没开始介绍呢。你必须设置套接字选项SO_BROADCAST
,然后才能在网络上发送广播数据包,这就好比是在导弹发射开关上盖的一个小塑料罩,你的一根指头就可以控制所有!
但是,说真的,使用广播数据包有一个危险,因为每个收到广播包的系统都要拨开一层层像洋葱皮一样的封装,直到系统知道这个资料是要发送到哪个 port 为止,然后系统会开始处理这个数据或者将其丢弃。
无论怎样,接收广播数据包的每台机器都要做很多工作,而且因为它们都在本地网络上,所以可能会有很多机器做很多不必要的工作。
现在,有很多方法可以解决这个问题。。。
等一下,真的有很多方法吗?
那是什么表情阿?让我告诉你吧,发送广播包的方式有很多,所以重点就是:你该如何指定广播数据的目地地址?
有两种常用的方法:
- 将数据发送到指定子网的广播地址,就是把子网( subnet's network)的主机( host)部分全部填
1
。例如,我的网络是192.168.1.0
,我的子网掩码是255.255.255.0
,所以地址的最后一个字节是我的主机号(因为根据网络掩码,前3个字节是网络号),所以我的广播地址是192.168.1.255
。在Unix下,ifconfig
会告诉你这些信息的。你可以将这种类型的广播数据包发送到远程网络和本地网络,但你会面临数据包被目标路由器丢弃的风险。(如果他们不放弃它,那这些广播数据可能会淹没他们的局域网。) - 将数据发送到“全局(global)”广播地址。这是
255.255.255.255
,即INADDR_BROADCAST。许多机器会自动将其与您的网络号码「按位与」,以将其转换为网络广播地址,但有些机器却不会。具有讽刺意味的是,路由器不会把这种类型的广播数据包从你的本地网络转发出去。
所以如果你想要将数据送到广播地址,但是并没有设置 SO_BROADCAST
socket 选项时会怎么样呢?好,我们用之前的 talker 与 listener 来炒个冷饭,然后看看会发生什么事情。
$ talker 192.168.1.2 foo
sent 3 bytes to 192.168.1.2
$ talker 192.168.1.255 foo
sendto: Permission denied
$ talker 255.255.255.255 foo
sendto: Permission denied
如你所见,数据发送得并不顺利......因为我们没有设置SO_BROADCAST
这个socket选项,设置一下,然后你就可以用sendto()
将数据发送到任何你想发送的地方去了。
事实上,这是UDP
应用程序可以广播和不能广播的唯一区别。因此,我们改造一下之前的talker
程序,添加一个设置SO_BROADCAST
套接字选项的部分。代码如下:
/*标签:addr,BROADCAST,their,广播,World,include,数据包,Hello From: https://blog.51cto.com/u_13887950/5806588
** broadcaster.c -- a datagram "client" like talker.c, except
** this one can broadcast
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#define SERVERPORT 4950 // the port users will be connecting to
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in their_addr; // connector's address information
struct hostent *he;
int numbytes;
int broadcast = 1;
//char broadcast = '1'; // if that doesn't work, try this
if (argc != 3) {
fprintf(stderr,"usage: broadcaster hostname message\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { // get the host info
perror("gethostbyname");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
// this call is what allows broadcast packets to be sent:
if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &broadcast,
sizeof broadcast) == -1) {
perror("setsockopt (SO_BROADCAST)");
exit(1);
}
their_addr.sin_family = AF_INET; // host byte order
their_addr.sin_port = htons(SERVERPORT); // short, network byte order
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
memset(their_addr.sin_zero, '\0', sizeof their_addr.sin_zero);
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0,
(struct sockaddr *)&their_addr, sizeof their_addr)) == -1) {
perror("sendto");
exit(1);
}
printf("sent %d bytes to %s\n", numbytes,
inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
}