多播的数据传输是基于UDP的,区别在于UDP以单一目标,多播可以同时向多个主机传输数据。
特点:
多播组是D类IP地址(224.0.0.0~239.255.255.255),加入多播组的客户端套接字,就可以接收该多播组的数据。 多播的数据格式和UDP的相同。只是与一般的UDP数据包不同,向网络传递一个多播数据包时,路由器将复制该数据包到多个主机。
有些路由器不支持多多播,或者为了防止网络拥堵故意阻断了多播,这种情况下也会使用隧道技术(这不需要多播程序开发人员考虑)。
为了传播多播数据包必须设置TTL,这个会决定数据包的传输距离,TTL用整数表示,每经过一个路由器就会减1,当TTL变为0的时候,该数据包就无法再被传递,只能销毁。
发送端如何设置TTL? 通过前面学的套接字可选项完成(setsockopt),TTL相关的协议层是IPPROTO_IP,选项名为IP_MULTICAST_TTL。
int send_sock;
int time_live=64;
...
send_sock=socket(PF_INET,SOCK_DGRAM,0);//设置UDP套接字
setsockopt(send_sock,IPPROTO_IP,IP_MULTICAST_TTL,(void *)&time_live,sizeof(time_live));
接收端如何加入多播组?
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));
...
struct ip_mreq
{
struct in_addr imr_multiaddr;
struct in_addr imr_interface;
}
in_addr结构体不需要多介绍。第一个成员写入加入的组IP地址(也就是多播组的IP地址),第二个成员写入要加入多播组的套接字所属主机IP地址(可以用INADDR_ANY)。
Sender:向AAA组发送这多播消息 Receiver:接收发送到AAA组的多播消息 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]); // Multicast IP,要设为多播地址
mul_adr.sin_port=htons(atoi(argv[2])); // Multicast Port目标端口
setsockopt(send_sock, IPPROTO_IP,
IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));//设置生存时间
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的发送函数发送数据
sleep(2);
}
fclose(fp);
close(send_sock);
return 0;
}
void error_handling(char *message)
{
fputs(message, stderr);
fputc('\n', stderr);
exit(1);
}
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); //接收套接字主机IP
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]);//多播组信息,字符串IP转网络字节序IP
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);
}
双方填入的端口号应该保持一致
广播是向同一网络中的所有主机传输数据的方法,也是基于UDP完成。 种类:
直接广播:除了网络地址外,主机地址全设为1,例如向网络地址为192.12.34中所有主机传输数据,就可以填写192.12.34.255,换而言之,直接广播可以向特定网络发送广播,即使自己并不属于那个网络。 本地广播: 使用的IP地址只能为255.255.255.255,也就是向本网络所有主机发送数据。
默认的套接字会阻止广播,因此需要设置SO_BROADCAST设为1。
int send_sock;
int bcast = 1;//对变量初始化将SO_BROADCAST的选项信息设为1
...
send_sock = (PF_INET,SOCK_DGRAM,0);
...
setSOCKOPT(send_sock,SOL_SOCKET,SO_BROADCAST,(void *)&bcast,sizeof(bcast));
...
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 <Boradcast 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);
}
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)//注册接收套接字IP地址和端口号
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);
}
Sender
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h> // for IP_MULTICAST_TTL option
#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));//设置TTL
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);
}
Receiver
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h> // for struct ip_mreq
#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[2]));
if(bind(hRecvSock, (SOCKADDR*) &adr, sizeof(adr))==SOCKET_ERROR)//注册接收套接字的IP和端口号
ErrorHandling("bind() error");
joinAdr.imr_multiaddr.s_addr=inet_addr(argv[1]);//初始化多播组
joinAdr.imr_interface.s_addr=htonl(INADDR_ANY);//设置本主机的任意IP
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);
}
本文章使用limfx的vscode插件快速发布