C语言网络编程实现组播(多播)

1、组播IP划分

224.0.0.0~224.0.0.255		为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255		是公用组播地址,可以用于Internet;欲使用需申请。
224.0.2.0~238.255.255.255	为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255	为本地管理组播地址,仅在特定的本地范围内有效。

2、多播地址与 MAC 地址的映射

IPv4 的 D 类地址是多播地址。IEEE 把一块以太网多播组地址分给 IANA 以支持IP多播。块的地址都以01:00:5e 开头,第 25 位为 0,低 23 位为 IPv4 多播地址( D类地址 )的低 23 位。
IPv4 多播地址与 MAC 地址的映射关系如图所示

C语言网络编程实现组播(多播)_第1张图片

由于多播地址( D类地址 )中的最高 5bit 在映射过程中被忽略,因此每个以太网多播地址对应的多播组是不唯一的。32 个不同的多播组号被映射为一个以太网地址。例如,多播地址 224.128.64.32(十六进制 e0.80.40.20)和 224.0.64.32(十六进制 e0.00.40.20)都映射为同一以太网地址01:00:5e:00:40:20。

既然地址映射是不唯一的,那么设备驱动程序或IP层就必须对数据报进行过滤。因为网卡可能接收到主机不想接收的多播数据帧,如下图,假如主机 1 加入的多播为 224.128.64.32,主机 2 加入的多播为 224.0.64.32,我们想给 224.0.64.32 所在的多播组 ( 主机 2 ) 发送信息,数据经过网卡时,224.128.64.32 (主机 1 ) 和 224.0.64.32 (主机 2 ) 所在多播组的网卡都会收到数据,因为它们的 MAC 地址都是 01:00:5e:00:40:20。这时候,如果网卡不提供足够的多播数据帧过滤功能,设备驱动程序就必须接收所有多播数据帧,然后对它们进行过滤,这个过滤过程是网络驱动或IP层自动完成。

C语言网络编程实现组播(多播)_第2张图片

4、套接字选项

#include 
#include 

int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t*optlen);

sockfd:标识一个套接口的描述字。
level:选项定义的层次;支持
SOL_SOCKET
IPPROTO_TCP
IPPROTO_IP
IPPROTO_IPV6

optname:需设置的选项。
optval:指针,指向存放选项待设置的新值的缓冲区。
optlen:optval缓冲区长度。 

a、选项 IP_MULTICASE_TTL

允许设置超时TTL,范围为0~255之间的任何值,例如:

unsigned char ttl=255; 
setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,&ttl,sizeof(ttl));

 b、选项IP_MULTICAST_IF

用于设置组播的默认网络接口,会从给定的网络接口发送,另一个网络接口会忽略此数据。例如:

struct in_addr
{
    in_addr_t s_addr;
}
struct in_addr addr; 
setsockopt(s,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr))

c 、选项IP_MULTICAST_LOOP

用于控制数据是否回送到本地的回环接口。例如:

unsigned char loop; 
setsockopt(s,IPPROTO_IP,IP_MULTICAST_LOOP,&loop,sizeof(loop))

参数loop设置为0禁止回送,设置为1允许回送

d、选项IP_ADD_MEMBERSHIP

加入某个广播组,之后就可以向这个广播组发送数据或者从广播组接收数据。此选项的值为mreq结构,成员imn_multiaddr是需要加入的广播组IP地址,成员imr_interface是本机需要加入广播组的网络接口IP地址。例如:

struct ip_mreq 
{ 
      struct in_addr imn_multiaddr; /*加入或者退出的广播组IP地址*/ 
      struct in_addr imr_interface; /*加入或者退出的网络接口IP地址*/ 
}

struct ip_mreq mreq; 
setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq))

 e、选项IP_DROP_MEMBERSHIP

用于从一个广播组中退出。例如:

struct ip_mreq mreq; 
setsockopt(s,IPPROTP_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(sreq))

其中mreq包含了在IP_ADD_MEMBERSHIP中相同的值.
 

f、选项SO_REUSEADDR

是一个 socket 选项,用于在关闭 TCP 连接后立即再次使用相同的地址和端口。如果不使用该选项,那么一般情况下要等待一段时间(TIME_WAIT)才能再次使用相同的地址和端口。

int opt = 1;
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const void  *)&opt, sizeof(opt));

g、选项SO_BINDTODEVICE

指定网卡去发送数据
 

struct ifreq
{
    char ifr_name[IFNAMSIZ]; /* Interface name */
    union {

        struct sockaddrifr_addr;
        struct sockaddrifr_dstaddr;
        struct sockaddrifr_broadaddr;
        struct sockaddrifr_netmask;
        struct sockaddrifr_hwaddr;
        short ifr_flags;
        int ifr_ifindex;
        int ifr_metric;
        int ifr_mtu;
        struct ifmapifr_map;
        char ifr_slave[IFNAMSIZ];
        char ifr_newname[IFNAMSIZ];
        char *ifr_data;
    };
};

struct ifreq interface;
memset(&interface,0,sizeof(interface));
strncpy(interface.ifr_ifrn.ifrn_name, “br0”, strlen(ifname));
setsockopt(fd , SOL_SOCKET, SO_BINDTODEVICE, (char *)&interface, sizeof(interface))

5、文件控制

#include 

int fcntl(int fd, int cmd);

int curFlags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, curFlags|O_NONBLOCK);设置非阻塞模式

6、接收示例

/* 
*broadcast_client.c - 多播的客户端 
*/ 
#define MCAST_PORT 8888; 
#define MCAST_ADDR "224.0.0.88" /*一个局部连接多播地址,路由器不进行转发*/ 
#define MCAST_INTERVAL 5 /*发送间隔时间*/ 
#define BUFF_SIZE 256 /*接收缓冲区大小*/
 
int main(int argc, char*argv[]) 
{ 
	int s; /*套接字文件描述符*/ 
	struct sockaddr_in local_addr; /*本地地址*/ 
	int err = -1; 

	s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ 
	if (s == -1) { 
		perror("socket()"); 
		return -1; 
	} 
      
										/*初始化地址*/ 
	memset(&local_addr, 0, sizeof(local_addr)); 
	local_addr.sin_family = AF_INET; 
	local_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
	local_addr.sin_port = htons(MCAST_PORT); 

								/*绑定socket*/ 
	err = bind(s,(struct sockaddr*)&local_addr, sizeof(local_addr)) ; 
	if(err < 0) { 
		perror("bind()"); 
		return -2; 
	} 

										/*设置回环许可*/ 
	int loop = 1; 
	err = setsockopt(s,IPPROTO_IP, IP_MULTICAST_LOOP,&loop, sizeof(loop)); 
	if(err < 0) { 
		perror("setsockopt():IP_MULTICAST_LOOP"); 
		return -3; 
	} 

	struct ip_mreq mreq; /*加入广播组*/ 
	mreq.imr_multiaddr.s_addr = inet_addr(MCAST_ADDR); /*广播地址*/ 
	mreq.imr_interface.s_addr = htonl(INADDR_ANY); /*网络接口为默认*/ 
											/*将本机加入广播组*/ 
	err = setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof 
	(mreq)); 
	if (err < 0) { 
		perror("setsockopt():IP_ADD_MEMBERSHIP"); 
		return -4; 
	} 
      
	int times = 0; 
	int addr_len = 0; 
	char buff[BUFF_SIZE]; 
	int n = 0; 
						/*循环接收广播组的消息,5次后退出*/ 
	for(times = 0;times<5;times++)  { 
		
		addr_len = sizeof(local_addr); 
		memset(buff, 0, BUFF_SIZE); /*清空接收缓冲区*/ 
										/*接收数据*/ 
		n = recvfrom(s, buff, BUFF_SIZE, 0,(struct sockaddr*)&local_addr, &addr_len); 
		if( n== -1)  { 
			perror("recvfrom()"); 
		} 
										/*打印信息*/ 
		printf("Recv %dst message from server:%s\n", times, buff); 
		sleep(MCAST_INTERVAL); 
	} 

									/*退出广播组*/ 
	err = setsockopt(s, IPPROTO_IP, IP_DROP_MEMBERSHIP,&mreq, sizeof 
	(mreq)); 

	close(s); 
	return 0; 
}

7、发送示例

/* 
*broadcast_server.c - 多播服务程序 
*/ 
#define MCAST_PORT 8888; 
#define MCAST_ADDR "224.0.0.88"/ /*一个局部连接多播地址,路由器不进行转发*/ 
#define MCAST_DATA "BROADCAST TEST DATA" /*多播发送的数据* 
#define MCAST_INTERVAL 5 /*发送间隔时间*/ 
int main(int argc, char*argv) 
{ 
    int s; 
    
    struct sockaddr_in mcast_addr; 
    s = socket(AF_INET, SOCK_DGRAM, 0); /*建立套接字*/ 
    
    if (s == -1) { 
        perror("socket()"); 
        return -1; 
    } 

    memset(&mcast_addr, 0, sizeof(mcast_addr));    /*初始化IP多播地址为0*/ 
    
    mcast_addr.sin_family = AF_INET;             /*设置协议族类行为AF*/ 
    mcast_addr.sin_addr.s_addr = inet_addr(MCAST_ADDR);/*设置多播IP地址*/ 
    mcast_addr.sin_port = htons(MCAST_PORT);             /*设置多播端口*/ 

    /*向多播地址发送数据*/ 
    while(1) { 
    
        int n = sendto(s,MCAST_DATA, sizeof(MCAST_DATA),0, (struct sockaddr*)&mcast_addr, sizeof(mcast_addr)) ; 
        
        if( n < 0) { 
            perror("sendto()"); 
            return -2; 
        } 

        sleep(MCAST_INTERVAL); /*等待一段时间*/ 
    } 

    return 0;
}

你可能感兴趣的:(网络编程,网络,服务器,linux)