使用UDP进行广播
如果通信只能在两个单体之间完成,这样的方式是没有效率的。另一方面,广播允许同时要多个接收者传播信息。
在这一章,我们将会学习下列内容:
建立一个UDP广播套接口
使用套接口发送广播信息
使用套接口接收广播信息
在学完这一章之后,我们就会知道如何使用IPv4套接口广播程序来编写程序。
理解广播地址
要使用广播,我们必须了解IPv4的特定广播地址编写。我们可以记起IP地址可以分为左边的网络ID部分以及右边的主机ID部分。广播地址所用的约定就是主机ID位全部设置为1。
当我们的网卡正确配置以后,我们可以用下面的命令来显示我们所选用接口的广播地址:
# ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:A0:4B:06:F4:8D
inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1
RX packets:1955 errors:0 dropped:0 overruns:0 frame:31
TX packets:1064 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100
Interrupt:9 Baseaddress:0xe400
#
输出的第二行表明eth0接口的广播地址为192.168.0.255。这个地址的网络ID为前三个字节192.168.0,而这个地址的主机ID部分为255。这个值255为是表示主机ID全为1的十进制数。
在255.255.255.255上进行广播
特殊的广播地址255.255.255.255也可以用于广播。仅管这种格式的广播地址也许表示向全世界进行广播,但是却有更多的限制。这种广播类型绝不会被路由器路由,而一个更为特殊的广播地址,例如192.168.0.255也许会被路由,而这取决于路由器的配置。
一个通常的广播地址,如255.255.255.255,并没有被很好的进行定义。例如,一些UNIX会将其解析来表明一个广播应发生在主机的所有网络接口上,而其他的一些UNIX内核只会选择其中的一个接口,通常是第一个定义的。当一个主机有多个网卡时,这就会成为一个问题。正是由于这个原因,并不鼓励通用广播地址的使用。
如果必须向每个网络接口广播,那么我们的软件在广播之前应执行下面的步骤:
1 确定下一个或第一个接口名字
2 确定接口的广播地址
3 使用这个广播地址进行广播
4 对于系统中其余的活动网络接口重复执行步骤1到步骤3
在执行完这些步骤以后,我们就可以认为已经对我们软件中的每一个接口进行广播。
这一章的其余内容会专注于如何对一个网络接口进行广播。在我们掌握了这些概念以后,如果我们必须对第一个接口进行广播,我们就可以应用前面的步骤。
加强mkaddr.c子函数
我们在第10章所提供的mkaddr.c子程序的一个限制就是他不可以很好的处理255.255.255.255广播地址的情况。在这一部分,我们将会看到修正这个问题的原因。
下面的diff输出显示了为修正这个问题而在mkaddr.c源码中所做出的改变。应用这个改变可以允许我们使用255.255.255.255广播地址进行试验。
$ diff ../ch.11/mkaddr.c mkaddr.c
99,102c99,100
< ap->sin_addr.s_addr =
< inet_addr(host_part);
< if ( ap->sin_addr.s_addr
< == INADDR_NONE )
---
> if ( !inet_aton(host_part,
> &ap->sin_addr) )
$
前面的例子显示了inet_aton函数替换了有更多限制的inet_addr函数。inet_addr函数的问题在于当地址不可用时会返回INADDR_NONE值。当255.255.255.255转换为一个32位值时,他的返回值与INADDR_NONE相同。
因而就不能区分一个错误的IP地址输入与一个通用的广播地址。使用inet_aton函数可以避免这个问题。
由一个服务器广播
这一章将会演示一个简单的广播服务器程序与一个相对应的客户端程序。我们首先将会提供并解释服务器程序。
在这里所提供的服务器将会提供一个股票市场的索引模拟。服务器程序将会由外部的交易提供者处得到数据反馈,然后向所有感兴趣的客户广播股票市场索引交易。下面显示了stksrv.c服务器程序:
/*stksrv.c
*
* Example Stock Index Broadcast:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
extern int mkaddr(void *addr,
int *addrlen,
char *str_addr,
char *protocol);
#define MAXQ 4
static struct
{
char *index;
int start;
int volit;
int current;
}quotes[] =
{
{"DJIA",1030330,375},
{"NASDAQ",276175,125},
{"S&P 500",128331,50},
{"TSE 300",689572,75}
};
/*
* Initialize:
*/
static void initialize(void)
{
short x;
time_t td;
/*
* seed the random number generator:
*/
time(&td);
srand((int)td);
for(x=0;x<MAXQ;++x)
quotes[x].current = quotes[x].start;
}
/*
* randomly change one index quotation:
*/
static void gen_quote(void)
{
short x; /*index*/
short v; /* volatility of index */
short h; /* half of v */
short r; /* random change */
x = rand() % MAXQ;
v = quotes[x].volit;
h = (v/2) -2;
r = rand() % v;
if(r<h)
r = -r;
quotes[x].current += r;
}
/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what,stderr);
fputc('/n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
short x; /* index of stock indexes */
double I0; /* initial index value */
double I; /* index value */
char bcbuf[512],*bp;/* buffer and ptr */
int z; /* status return code */
int s; /* socket */
struct sockaddr_in adr_srvr; /* AF_INET */
int len_srvr; /* length */
struct sockaddr_in adr_bc; /* AF_INET */
int len_bc; /* length */
static int so_broadcast = TRUE;
static char
*sv_addr = "127.0.0.1:*",
*bc_addr = "127.255.255.255:9097";
/*
* Form a server address:
*/
if(argc >2)
/* server address */
sv_addr = argv[2];
if(argc >1)
/* broadcast address */
bc_addr = argv[1];
/*
* Form the server address:
*/
len_srvr = sizeof adr_srvr;
z = mkaddr(
&adr_srvr, /* returned address */
&len_srvr, /* returned length */
sv_addr, /* input string addr */
"udp"); /* udp protocol */
if(z==-1)
bail("bad server address");
/*
* Form the broadcast address:
*/
len_bc = sizeof adr_bc;
z = mkaddr(
&adr_bc, /* returned address */
&len_bc, /* returned length */
bc_addr, /* input string addr */
"udp"); /* udp protocol */
if(z==-1)
bail("bad broadcast address");
/*
* Create a UDP socket to use:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
/*
* Allow broadcasts:
*/
z = setsockopt(s,
SOL_SOCKET,
SO_BROADCAST,
&so_broadcast,
sizeof so_broadcast);
if(z==-1)
bail("setsockopt(SO_BROADCAST)");
/*
* bind an address to our socket,so that
* client programs can listen to this
* server
*/
z = bind(s,(struct sockaddr *)&adr_srvr,len_srvr);
if(z==-1)
bail("bind()");
/*
* Now start serving quotes:
*/
initialize();
for(;;)
{
/*
* update on quote in the list:
*/
gen_quote();
/*
* Form a packet to send out:
*/
bp = bcbuf;
for(x=0;x<MAXQ;++x)
{
I0 = quotes[x].start / 100.0;
I = quotes[x].current /100.0;
sprintf(bp,
"%-7.7s %8.2f %+.2f/n",
quotes[x].index,
I
I-I0);
bp += strlen(bp);
}
/*
* broadcast the updated info:
*/
z = sendto(s,bcbuf,strlen(bcbuf),0,(struct sockaddr *)&adr_bc,len_bc);
if(z==-1)
bail("sendto()");
sleep(4);
}
return 0;
}
接收广播
我们所提供的客户端程序必须监听我们的股票市场索引程序所发布的广播。下面是客户端程序的源码:
/*gquotes.c
*
* Get datagram stock market
* quotes from udp broadcast:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
extern int mkaddr(
void *addr,
char *addrlen,
char *str_addr,
char *protocol);
/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what,stderr);
fputc('/n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
int x;
struct sockaddr_in adr; /* AF_INET */
int len_inet; /* length */
int s; /* socket */
char dgram[512]; /* recv buffer */
static int so_reuseaddr = TRUE;
static char
*bc_addr = "127.255.255.255:9097";
/*
* use a server address from the command
* line,if one has been provided.
* otherwise,this program will default
* to using the arbitrary address
* 127.0.0.23:
*/
if(argc>1)
/* broadcast address: */
bc_addr = argv[1];
/*
* Create a UDP socket to use:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
/*
* Form the broadcast address:
*/
len_inet = sizeof adr;
z = mkaddr(&adr,
&len_inet,
bc_addr,
"udp");
if(z==-1)
bail("bad broadcast address");
/*
* Allow multiple listeners on the
* broadcast address:
*/
z = setsockopt(s,
SOL_SOCKET,
SO_REUSEADDR,
&so_reuseaddr,
sizeof so_reuseaddr);
if(z==-1)
bail("setsockopt(SO_REUSEADDR)");
/*
* bind out socket to the broadcast address:
*/
z = bind(s,
(struct sockaddr *)&adr,
len_inet);
if(z==-1)
bail("bind(2)");
for(;;)
{
/*
* wait for a broadcast message:
*/
z = recvfrom(s, /*socket */
dgram, /* receiving buffer */
sizeof dgram, /* max rcv buf size */
0, /* flags:no options */
(struct sockaddr *)&adr, /* addr */
&x); /* addr len */
if(z<0)
bail("recvfrom(2)");
fwrite(dgram,z,1,stdout);
putchar('/n');
fflush(stdout);
}
return 0;
}
如果通信只能在两个单体之间完成,这样的方式是没有效率的。另一方面,广播允许同时要多个接收者传播信息。
在这一章,我们将会学习下列内容:
建立一个UDP广播套接口
使用套接口发送广播信息
使用套接口接收广播信息
在学完这一章之后,我们就会知道如何使用IPv4套接口广播程序来编写程序。
理解广播地址
要使用广播,我们必须了解IPv4的特定广播地址编写。我们可以记起IP地址可以分为左边的网络ID部分以及右边的主机ID部分。广播地址所用的约定就是主机ID位全部设置为1。
当我们的网卡正确配置以后,我们可以用下面的命令来显示我们所选用接口的广播地址:
# ifconfig eth0
eth0 Link encap:Ethernet HWaddr 00:A0:4B:06:F4:8D
inet addr:192.168.0.1 Bcast:192.168.0.255 Mask:255.255.255.0
UP BROADCAST RUNNING PROMISC MULTICAST MTU:1500 Metric:1
RX packets:1955 errors:0 dropped:0 overruns:0 frame:31
TX packets:1064 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:100
Interrupt:9 Baseaddress:0xe400
#
输出的第二行表明eth0接口的广播地址为192.168.0.255。这个地址的网络ID为前三个字节192.168.0,而这个地址的主机ID部分为255。这个值255为是表示主机ID全为1的十进制数。
在255.255.255.255上进行广播
特殊的广播地址255.255.255.255也可以用于广播。仅管这种格式的广播地址也许表示向全世界进行广播,但是却有更多的限制。这种广播类型绝不会被路由器路由,而一个更为特殊的广播地址,例如192.168.0.255也许会被路由,而这取决于路由器的配置。
一个通常的广播地址,如255.255.255.255,并没有被很好的进行定义。例如,一些UNIX会将其解析来表明一个广播应发生在主机的所有网络接口上,而其他的一些UNIX内核只会选择其中的一个接口,通常是第一个定义的。当一个主机有多个网卡时,这就会成为一个问题。正是由于这个原因,并不鼓励通用广播地址的使用。
如果必须向每个网络接口广播,那么我们的软件在广播之前应执行下面的步骤:
1 确定下一个或第一个接口名字
2 确定接口的广播地址
3 使用这个广播地址进行广播
4 对于系统中其余的活动网络接口重复执行步骤1到步骤3
在执行完这些步骤以后,我们就可以认为已经对我们软件中的每一个接口进行广播。
这一章的其余内容会专注于如何对一个网络接口进行广播。在我们掌握了这些概念以后,如果我们必须对第一个接口进行广播,我们就可以应用前面的步骤。
加强mkaddr.c子函数
我们在第10章所提供的mkaddr.c子程序的一个限制就是他不可以很好的处理255.255.255.255广播地址的情况。在这一部分,我们将会看到修正这个问题的原因。
下面的diff输出显示了为修正这个问题而在mkaddr.c源码中所做出的改变。应用这个改变可以允许我们使用255.255.255.255广播地址进行试验。
$ diff ../ch.11/mkaddr.c mkaddr.c
99,102c99,100
< ap->sin_addr.s_addr =
< inet_addr(host_part);
< if ( ap->sin_addr.s_addr
< == INADDR_NONE )
---
> if ( !inet_aton(host_part,
> &ap->sin_addr) )
$
前面的例子显示了inet_aton函数替换了有更多限制的inet_addr函数。inet_addr函数的问题在于当地址不可用时会返回INADDR_NONE值。当255.255.255.255转换为一个32位值时,他的返回值与INADDR_NONE相同。
因而就不能区分一个错误的IP地址输入与一个通用的广播地址。使用inet_aton函数可以避免这个问题。
由一个服务器广播
这一章将会演示一个简单的广播服务器程序与一个相对应的客户端程序。我们首先将会提供并解释服务器程序。
在这里所提供的服务器将会提供一个股票市场的索引模拟。服务器程序将会由外部的交易提供者处得到数据反馈,然后向所有感兴趣的客户广播股票市场索引交易。下面显示了stksrv.c服务器程序:
/*stksrv.c
*
* Example Stock Index Broadcast:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
extern int mkaddr(void *addr,
int *addrlen,
char *str_addr,
char *protocol);
#define MAXQ 4
static struct
{
char *index;
int start;
int volit;
int current;
}quotes[] =
{
{"DJIA",1030330,375},
{"NASDAQ",276175,125},
{"S&P 500",128331,50},
{"TSE 300",689572,75}
};
/*
* Initialize:
*/
static void initialize(void)
{
short x;
time_t td;
/*
* seed the random number generator:
*/
time(&td);
srand((int)td);
for(x=0;x<MAXQ;++x)
quotes[x].current = quotes[x].start;
}
/*
* randomly change one index quotation:
*/
static void gen_quote(void)
{
short x; /*index*/
short v; /* volatility of index */
short h; /* half of v */
short r; /* random change */
x = rand() % MAXQ;
v = quotes[x].volit;
h = (v/2) -2;
r = rand() % v;
if(r<h)
r = -r;
quotes[x].current += r;
}
/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what,stderr);
fputc('/n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
short x; /* index of stock indexes */
double I0; /* initial index value */
double I; /* index value */
char bcbuf[512],*bp;/* buffer and ptr */
int z; /* status return code */
int s; /* socket */
struct sockaddr_in adr_srvr; /* AF_INET */
int len_srvr; /* length */
struct sockaddr_in adr_bc; /* AF_INET */
int len_bc; /* length */
static int so_broadcast = TRUE;
static char
*sv_addr = "127.0.0.1:*",
*bc_addr = "127.255.255.255:9097";
/*
* Form a server address:
*/
if(argc >2)
/* server address */
sv_addr = argv[2];
if(argc >1)
/* broadcast address */
bc_addr = argv[1];
/*
* Form the server address:
*/
len_srvr = sizeof adr_srvr;
z = mkaddr(
&adr_srvr, /* returned address */
&len_srvr, /* returned length */
sv_addr, /* input string addr */
"udp"); /* udp protocol */
if(z==-1)
bail("bad server address");
/*
* Form the broadcast address:
*/
len_bc = sizeof adr_bc;
z = mkaddr(
&adr_bc, /* returned address */
&len_bc, /* returned length */
bc_addr, /* input string addr */
"udp"); /* udp protocol */
if(z==-1)
bail("bad broadcast address");
/*
* Create a UDP socket to use:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
/*
* Allow broadcasts:
*/
z = setsockopt(s,
SOL_SOCKET,
SO_BROADCAST,
&so_broadcast,
sizeof so_broadcast);
if(z==-1)
bail("setsockopt(SO_BROADCAST)");
/*
* bind an address to our socket,so that
* client programs can listen to this
* server
*/
z = bind(s,(struct sockaddr *)&adr_srvr,len_srvr);
if(z==-1)
bail("bind()");
/*
* Now start serving quotes:
*/
initialize();
for(;;)
{
/*
* update on quote in the list:
*/
gen_quote();
/*
* Form a packet to send out:
*/
bp = bcbuf;
for(x=0;x<MAXQ;++x)
{
I0 = quotes[x].start / 100.0;
I = quotes[x].current /100.0;
sprintf(bp,
"%-7.7s %8.2f %+.2f/n",
quotes[x].index,
I
I-I0);
bp += strlen(bp);
}
/*
* broadcast the updated info:
*/
z = sendto(s,bcbuf,strlen(bcbuf),0,(struct sockaddr *)&adr_bc,len_bc);
if(z==-1)
bail("sendto()");
sleep(4);
}
return 0;
}
接收广播
我们所提供的客户端程序必须监听我们的股票市场索引程序所发布的广播。下面是客户端程序的源码:
/*gquotes.c
*
* Get datagram stock market
* quotes from udp broadcast:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif
extern int mkaddr(
void *addr,
char *addrlen,
char *str_addr,
char *protocol);
/*
* This function report the error and
* exits back to the shell:
*/
static void bail(const char *on_what)
{
fputs(strerror(errno),stderr);
fputs(": ",stderr);
fputs(on_what,stderr);
fputc('/n',stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
int x;
struct sockaddr_in adr; /* AF_INET */
int len_inet; /* length */
int s; /* socket */
char dgram[512]; /* recv buffer */
static int so_reuseaddr = TRUE;
static char
*bc_addr = "127.255.255.255:9097";
/*
* use a server address from the command
* line,if one has been provided.
* otherwise,this program will default
* to using the arbitrary address
* 127.0.0.23:
*/
if(argc>1)
/* broadcast address: */
bc_addr = argv[1];
/*
* Create a UDP socket to use:
*/
s = socket(AF_INET,SOCK_DGRAM,0);
if(s==-1)
bail("socket()");
/*
* Form the broadcast address:
*/
len_inet = sizeof adr;
z = mkaddr(&adr,
&len_inet,
bc_addr,
"udp");
if(z==-1)
bail("bad broadcast address");
/*
* Allow multiple listeners on the
* broadcast address:
*/
z = setsockopt(s,
SOL_SOCKET,
SO_REUSEADDR,
&so_reuseaddr,
sizeof so_reuseaddr);
if(z==-1)
bail("setsockopt(SO_REUSEADDR)");
/*
* bind out socket to the broadcast address:
*/
z = bind(s,
(struct sockaddr *)&adr,
len_inet);
if(z==-1)
bail("bind(2)");
for(;;)
{
/*
* wait for a broadcast message:
*/
z = recvfrom(s, /*socket */
dgram, /* receiving buffer */
sizeof dgram, /* max rcv buf size */
0, /* flags:no options */
(struct sockaddr *)&adr, /* addr */
&x); /* addr len */
if(z<0)
bail("recvfrom(2)");
fwrite(dgram,z,1,stdout);
putchar('/n');
fflush(stdout);
}
return 0;
}