广播,组播,单播
一.广播
1.1概念
主页socket里面简绍的数据包发送方式只有一个接受方,称为单播
如果同时发给局域网中的所有主机,称为广播
如果发给局域网中的部分主机,称为组播
注意:同一个套接字只能选择一种发送方式。
只有用户数据报(使用UDP协议)套接字才能广播
1.2广播地址
以192.168.1.0 (255.255.255.0) 网段为例,最大的主机地址192.168.1.255代表该网段的广播地址发到该地址的数据包被所有的主机接收,255.255.255.255在所有网段中都代表广播地址。
广播能发送给所有主机,因为广播的mac地址比较特殊,为全ff, ip地址也比较特殊,使用的是广播的ip地址,将这样的数据包先发送给交换机,然后由交换机发送给所有主机。局域网中每台主机都能收到这个数据包,以UDP为例,网卡收到这个数据包,链路层先校验,发现目的mac地址是广播地址,则可以通过,交给网络层,发现ip地址是广播的ip地址,则也允许通过,到达传输层,只要目的端口号符合,则就能叫到应用层处理。
1.3广播的流程
发送者:
创建套接字 socket( )
设置为允许发送广播权限 setsockopt( )
填充广播信息结构体 sockaddr_in
发送数据 sendto( )
接收者:
创建套接字 socket( )
填充广播信息结构体 sockaddr_in
将套接字与广播信息结构体绑定 bind( )
接收数据 recvfrom( )
1.4代码实现广播
代码说明:这个代码是实现一收一发,服务器收,客户端发,代码相比之前主页socket来说差不多就是改变了方式,设置了一个广播,如需要了解,请参考主页socket的文章,谢谢观看。
设置广播的方式:
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
服务器代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
printf("%s:%s:%d --", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
int main(int argc, const char *argv[]){
if(3 != argc){
printf("Usage : %s <ip> <port>\n", argv[0]);
return -1;
}
//创建用户数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//填充广播信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
//端口号可以随便指定
serveraddr.sin_port = htons(atoi(argv[2]));
//ip地址必须是广播的ip地址
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
//绑定
if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("bind error");
}
//定义结构体保存对方的信息
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
char buff[128] = {0};
//循环收数据
while(1){
if(-1 == recvfrom(sockfd, buff, 128, 0, (struct sockaddr *)&client_addr, &client_addr_len)){
ERRLOG("recvfrom error");
}
printf("客户端[%s:%d]发来数据[%s]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff);
memset(buff, 0, 128);
}
close(sockfd);
return 0;
}
客户端代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
printf("%s:%s:%d --", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
int main(int argc, const char *argv[]){
if(3 != argc){
printf("Usage : %s <ip> <port>\n", argv[0]);
return -1;
}
//创建用户数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//填充广播信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
//端口号可以随便指定
serveraddr.sin_port = htons(atoi(argv[2]));
//ip地址必须是广播的ip地址
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
//设置允许发送广播
int on = 1;
if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on))){
ERRLOG("setsockopt error");
}
char buff[128] = {0};
//循环发送数据
while(1){
fgets(buff, 128, stdin);
buff[strlen(buff)-1] = '\0';
if(-1 == sendto(sockfd, buff, 128, 0, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("sendto error");
}
memset(buff, 0, 128);
}
return 0;
}
二.组播
2.1概念
单播方式只能发给一个接收方。广播方式发给所有的主机。过多的广播会大量占用网络带宽,造成广播风暴,影响正常的通信。组播(又称为多播)是一种折中的方式。只有加入某个多播组的主机才能收到数据。多播方式既可以发给多个主机,又能避免象广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)。
2.2 组播的地址
D类地址(组播地址)不分网络地址和主机地址,第1字节的前4位固定为1110
主播的地址范围:224.0.0.1 – 239.255.255.255
2.3组播的流程
发送者:
创建套接字 socket( )
填充组播信息结构体 sockaddr_in
发送数据 sendto( )
接收者:
创建套接字 scoket( )
填充组播信息结构体 sockaddr_in
将套接字与组播信息结构体绑定 bind( )
设置为加入多播组 setsockopt( )
接收数据 recvfrom( )
2.3设置加入多播组
//使用的下面的结构体
struct ip_mreqn {
struct in_addr imr_multiaddr; /*多播组ip地址*/
struct in_addr imr_address; /*本地ip地址*/
int imr_ifindex; /*接口索引 0表示任意接口*/
};
struct ip_mreqn my_mreqn;
//填充
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &my_mreqn, sizeof(my_mreqn));
2.4代码实现
服务器(接收端):
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
printf("%s:%s:%d --", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
int main(int argc, const char *argv[]){
if(3 != argc){
printf("Usage : %s <ip> <port>\n", argv[0]);
return -1;
}
//创建用户数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//填充广播信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
//端口号可以随便指定
serveraddr.sin_port = htons(atoi(argv[2]));
//ip地址是多播组的ip地址!!!!!
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
//绑定套接字与 组播信息结构体
if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("bind error");
}
//设置加入多播组
struct ip_mreqn my_mreqn;
memset(&my_mreqn, 0, sizeof(my_mreqn));
my_mreqn.imr_multiaddr.s_addr = inet_addr(argv[1]);
my_mreqn.imr_address.s_addr = inet_addr("192.168.60.109");
my_mreqn.imr_ifindex = 0;//任意接口
if(-1 == setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &my_mreqn, sizeof(my_mreqn))){
ERRLOG("setsockopt error");
}
//定义结构体保存对方的信息
struct sockaddr_in client_addr;
memset(&client_addr, 0, sizeof(client_addr));
socklen_t client_addr_len = sizeof(client_addr);
char buff[128] = {0};
//循环收数据
while(1){
if(-1 == recvfrom(sockfd, buff, 128, 0, (struct sockaddr *)&client_addr, &client_addr_len)){
ERRLOG("recvfrom error");
}
printf("客户端[%s:%d]发来数据[%s]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buff);
memset(buff, 0, 128);
}
close(sockfd);
return 0;
}
客户端(发送发):
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#define ERRLOG(errmsg) do{\
printf("%s:%s:%d --", __FILE__, __func__, __LINE__);\
perror(errmsg);\
exit(-1);\
}while(0)
int main(int argc, const char *argv[]){
if(3 != argc){
printf("Usage : %s <ip> <port>\n", argv[0]);
return -1;
}
//创建用户数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd){
ERRLOG("socket error");
}
//填充广播信息结构体
struct sockaddr_in serveraddr;
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
//端口号可以随便指定
serveraddr.sin_port = htons(atoi(argv[2]));
//ip地址广播的地址
//224.0.0.1 ~ 239.255.255.255
serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t serveraddr_len = sizeof(serveraddr);
char buff[128] = {0};
//循环发送数据
while(1){
fgets(buff, 128, stdin);
buff[strlen(buff)-1] = '\0';
if(-1 == sendto(sockfd, buff, 128, 0, (struct sockaddr *)&serveraddr, serveraddr_len)){
ERRLOG("sendto error");
}
memset(buff, 0, 128);
}
return 0;
}