参考
- 《TCP/IP网络编程》 尹圣雨
多播与广播
多播
多播(Multicast)方式的数据传输是基于UDP完成的。与UDP服务器端/客户端的实现方式区别在于,UDP数据传输以单一目标进行,而多播数据同时传递传递到加入(注册)特定组的大量主机
MBone(Multicast Backbone,多播主干网)
多播是基于MBone这个虚拟网络工作的。虚拟网络可以理解为通过网络中的特殊协议工作的软件概念上的网络。即MBone并非可以触及的物理网络。它是以物理网络为基础,通过软件方法实现的多播通信必备虚拟网络
多播的传输方式
多播的数据传输特点:
- 多播服务器端针对特定多播组,只发送1次数据
- 即使只发送1次数据,但该组内的所有客户端都会接收数据
- 多播组数可在IP地址范围内任意增加
- 加入特定组即可接收发往该多播组的数据
多播组是D类IP地址(224.0.0.0~239.255.255.255)
多播需要借助路由器完成,只需发送1次,由路由器负责复制文件并传递到主机。因此,多播主要用于多媒体数据的实时传输
虽然理论上可以完成多播通信,但不少路由器并不支持多播,或即便支持也因网络拥堵问题故意阻断多播。因此,为了在不支持多播的路由器中完成多播通信,也会使用隧道(Tunneling)技术
路由(Routing)和TTL(Time to Live)
为了传递多播数据包,必需设置TTL。TTL是决定数据包传递距离的主要因素。TTL用整数表示,并且每经过1个路由器就减1。TTL变为0时,该数据包无法再被传递,只能销毁。因此,TTL的值设置过大将影响网络流量;设置过小也会无法传递到目标
TTL的设置通过套接字可选项完成,与TTL相关的协议层为IPPROTO_IP,选项名为IP_MULTICAST_TTL。例如,将TTL设置为64:
int send_sock;
int time_live = 64;
...
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
...
加入多播组
加入多播组也通过设置套接字可选项完成,选项名为IP_ADD_MEMBERSHIP。可通过如下代码加入多播组:
int recv_sock;
struct ip_mreq join_adr;
...
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
...
join_adr.imr_multiaddr.s_addr = "多播组地址信息";
join_adr.imr_interface.s_addr = "加入多播组的主机地址信息";
setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr));
...
其中,ip_mreq结构体定义如下:
struct ip_mreq
{
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
};
第一个成员imr_multiaddr中写入加入的组IP地址;第二个成员imr_interface是加入该组的套接字所属主机的IP地址,也可使用INADDR_ANY
实现多播Sender和Receiver
多播中的Sender和Receiver替代服务器端和客户端。Sender比Receiver简单,因为Receiver需要经过加入组的过程,而Sender只需创建UDP套接字,并向多播地址发送数据
(1)Sender示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define TTL 64
#define BUF_SIZE 30
void error_handling(char* message);
int main(int argc, char* argv[])
{
int send_sock;
struct sockaddr_in mul_adr;
int time_live = TTL;
FILE* fp;
char buf[BUF_SIZE];
if (argc != 3)
{
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&mul_adr, 0, sizeof(mul_adr));
mul_adr.sin_family = AF_INET;
mul_adr.sin_addr.s_addr = inet_addr(argv[1]);
mul_adr.sin_port = htons(atoi(argv[2]));
setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live)); // 指定TTL信息,这是Sender中的必要过程
if ((fp = fopen("news.txt", "r")) == NULL)
{
error_handling("fopen() error");
}
while (!feof(fp))
{
fgets(buf, BUF_SIZE, fp);
sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr)); // 基于UDP套接字,所以利用sendto函数
sleep(2);
}
fclose(fp);
close(send_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
(2)Receiver示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char* message);
int main(int argc, char* argv[])
{
int recv_sock;
int str_len;
char buf[BUF_SIZE];
struct sockaddr_in adr;
struct ip_mreq join_adr;
if (argc != 3)
{
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&adr, 0, sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = htonl(INADDR_ANY);
adr.sin_port = htons(atoi(argv[2]));
if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
{
error_handling("bind() error");
}
join_adr.imr_multiaddr.s_addr = inet_addr(argv[1]); // 初始化多播组地址
join_adr.imr_interface.s_addr = htonl(INADDR_ANY); // 初始化待加入主机的IP地址
setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&join_adr, sizeof(join_adr)); // 加入多播组
while (1)
{
str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
if (str_len < 0)
{
break;
}
buf[str_len] = 0;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
基于Windows的实现
与Linux下只有头文件的声明稍有区别
(1)Sender
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <ws2tcpip.h>
#define TTL 64
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hSendSock;
SOCKADDR_IN mulAdr;
int timeLive = TTL;
FILE* fp;
char buf[BUF_SIZE];
if (argc != 3)
{
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ErrorHandling("WSAStartup() error!");
}
hSendSock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&mulAdr, 0, sizeof(mulAdr));
mulAdr.sin_family = AF_INET;
mulAdr.sin_addr.s_addr = inet_addr(argv[1]);
mulAdr.sin_port = htons(atoi(argv[2]));
setsockopt(hSendSock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&timeLive, sizeof(timeLive));
if ((fp = fopen("news.txt", "r")) == NULL)
{
ErrorHandling("fopen() error");
}
while (!feof(fp))
{
fgets(buf, BUF_SIZE, fp);
sendto(hSendSock, buf, strlen(buf), 0, (SOCKADDR*)&mulAdr, sizeof(mulAdr));
Sleep(2000);
}
closesocket(hSendSock);
WSACleanup();
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
(2)Receiver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <WinSock2.h>
#include <ws2tcpip.h>
#define BUF_SIZE 30
void ErrorHandling(char* message);
int main(int argc, char* argv[])
{
WSADATA wsaData;
SOCKET hRecvSock;\
SOCKADDR_IN adr;
struct ip_mreq joinAdr;
char buf[BUF_SIZE];
int strLen;
if (argc != 3)
{
printf("Usage : %s <GroupIP> <PORT>\n", argv[0]);
exit(1);
}
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
ErrorHandling("WSAStartup() error!");
}
hRecvSock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&adr, 0, sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = htonl(INADDR_ANY);
adr.sin_port = htons(atoi(argv[1]));
if (bind(hRecvSock, (SOCKADDR*)&adr, sizeof(adr)) == SOCKET_ERROR)
{
ErrorHandling("bind() error");
}
joinAdr.imr_multiaddr.s_addr = inet_addr(argv[1]);
joinAdr.imr_interface.s_addr = htonl(INADDR_ANY);
if (setsockopt(hRecvSock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)&joinAdr, sizeof(joinAdr)) == SOCKET_ERROR)
{
ErrorHandling("setsock() error");
}
while (1)
{
strLen = recvfrom(hRecvSock, buf, BUF_SIZE - 1, 0, NULL, 0);
if (strLen < 0)
{
break;
}
buf[strLen] = 0;
fputs(buf, stdout);
}
closesocket(hRecvSock);
WSACleanup();
return 0;
}
void ErrorHandling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
广播
广播(Broadcast)在“一次性向多个主机发送数据”这一点上与多播类似,但传输数据的范围有区别。多播即使在跨越不同网络的情况下,只要加入多播组就能接收数据。相反,广播只能向同一网络中的主机传输数据
广播的实现方法
广播也是基于UDP完成的。根据传输数据时使用的IP地址的形式,广播分为直接广播(Directed Broadcast)和本地广播(Local Broadcast),二者实现上的差别主要在于IP地址
(1)直接广播
直接广播的IP地址除了网络地址外,其余主机地址全部设置为1。即可以采用直接广播的方式向特定区域内所有主机传输数据
(2)本地广播
本地广播中使用的IP地址限定为255.255.255.255
实际上,数据通信中使用的IP地址是与UDP示例的唯一区别。但默认生成的套接字会阻止广播,因此,只需调用setsockopt函数,将SO_BROADCAST选项设置为1:
int send_sock;
int bcast = 1;
...
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
...
setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&bcast, sizeof(bcast));
...
实现广播数据的Sender和Receiver
(1)Sender
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char* message);
int main(int argc, char* argv[])
{
int send_sock;\
struct sockaddr_in broad_adr;
FILE* fp;
char buf[BUF_SIZE];
int so_brd = 1;
if (argc != 3)
{
printf("Usage : %s <Broadcast IP> <PORT>\n", argv[0]);
exit(1);
}
send_sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&broad_adr, 0, sizeof(broad_adr));
broad_adr.sin_family = AF_INET;
broad_adr.sin_addr.s_addr = inet_addr(argv[1]);
broad_adr.sin_port = htons(atoi(argv[2]));
setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, (void*)&so_brd, sizeof(so_brd));
if ((fp = fopen("news.txt", "r")) == NULL)
{
error_handling("fopen() error");
}
while (!feof(fp))
{
fgets(buf, BUF_SIZE, fp);
sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&broad_adr, sizeof(broad_adr));
sleep(2);
}
close(send_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
(2)Receiver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 30
void error_handling(char* message);
int main(int argc, char* argv[])
{
int recv_sock;
struct sockaddr_in adr;
int str_len;
char buf[BUF_SIZE];
if (argc != 2)
{
printf("Usage : %s <PORT>\n", argv[0]);
exit(1);
}
recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&adr, 0, sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = htonl(INADDR_ANY);
adr.sin_port = htons(atoi(argv[1]));
if (bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1)
{
error_handling("bind() error");
}
while (1)
{
str_len = recvfrom(recv_sock, buf, BUF_SIZE - 1, 0, NULL, 0);
if (str_len < 0)
{
break;
}
buf[str_len] = 0;
fputs(buf, stdout);
}
close(recv_sock);
return 0;
}
void error_handling(char* message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}