Linux系统编程69 网络编程3 - 广播与组播

getsockopt, setsockopt 获取设置Socket选项

NAME
       getsockopt, setsockopt - get and set options on sockets

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

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

/*对某一个指定的Socket的某个层面上的众多属性中的某一个属性进行设置,不同属性参数可能类型不同,所以需要指明参数类型和大小

如:对指定Socket sd 的SOL_SOCKET层面 的 SO_BROADCAST属性进行设置,即打开该属性,表示允许将数据发送至广播地址,即打开广播属性。
int val = 1;
setsockopt(sd, SOL_SOCKET, SO_BROADCAST,&val, sizeof(val));
*/
       int setsockopt(int sockfd, int level, int optname,
                      const void *optval, socklen_t optlen);

RETURN VALUE
       On success, zero is returned for the standard options.  On error, -1 is returned, and errno is set appropriately.

广播篇:

man 7 socket 

   Socket options
       The  socket  options  listed  below can be set by using setsockopt(2) and read with getsockopt(2) with the socket level set to SOL_SOCKET for all sockets.  Unless otherwise noted, optval is a pointer to an int.
除非另有说明,否则optval是一个指向int型的指针

层名:SOL_SOCKET 
属性:SO_BROADCAST等等:允许将数据发送至广播地址,仅限于报式套接字
...

SO_BINDTODEVICE:支持在多网卡情况下,指定走某个网卡。

SO_BROADCAST
              Set or get the broadcast flag.  When enabled, datagram sockets are allowed to send packets to a broadcast address.  This option has no effect on stream-oriented sockets.
属性为flag

...

实验1 :全网广播

注意点:想要针对广播 进行数据发送,接收 就必须要打开SO_BROADCAST属性,对于从广播接收数据,如果不打开广播属性,有可能收的到,也有可能收不到,如果想一定收到,就必须打开SO_BROADCAST属性

这里 客户端 和 服务端 全部都打开了 SO_BROADCAST 广播属性

proto.h

#ifndef PROTO_H_
#define PROTO_H_

#define RCVPORT "1989"
#define NAMEMAX 512-8-8 // math+chinese 8 + UDP报头 8
#define IPSTRSIZE 40
struct msg_st
{
	uint32_t math;
	uint32_t chinese;
	uint8_t name[1];
}__attribute__((pack));
	

#endif

rcver.c 打开全网广播属性

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "proto.h"

int main()
{
	int sd;
	struct sockaddr_in laddr,raddr;
	struct msg_st *rbufp;
	socklen_t raddr_len;
	char ipstr[IPSTRSIZE];
	int size;

	//根据服务端 即将接受数据大小 申请空间 用于存储接收数据
	size = sizeof(struct msg_st) + NAMEMAX - 1;// -uint8_t name[1];
	rbufp = malloc(size);
	if(rbufp == NULL)
	{
		perror("malloc()");
		exit(1);
	}
	
	//创建Socket, 即确定传输协议族,传输协议,传输方式
	sd = socket(AF_INET,SOCK_DGRAM,0);//0默认是IPPROTO_UDP
	if(sd < 0)
	{
		perror("soccket()");
		exit(1);
	}

//对指定Socket sd 的SOL_SOCKET层面 的 SO_BROADCAST属性进行设置,即打开该属性,表示允许将数据发送至广播地址,即打开广播属性。
	int val = 1;
	if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST,&val, sizeof(val)) < 0)
	{
		perror("setsockopt()");
		exit(1);
	}

	//设置 本端,即服务端 地址信息, struct sockaddr_in结构体信息:协议族类型,端口,IP
	laddr.sin_family = AF_INET;
	laddr.sin_port = htons(atoi(RCVPORT));
	inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);//0.0.0.0:any address

	//绑定 Socket 到 本端地址
	if(bind(sd,(void *)&laddr,sizeof(laddr)) < 0)
	{
		perror("bind");
		exit(1);
	}

	/* !!!! */
	raddr_len = sizeof(raddr);

	while(1)
	{
		//接受数据,数据存储于rbufp,将对端地址信息(struct sockaddr_in)存储于raddr
		recvfrom(sd,rbufp,size,0,(void *)&raddr,&raddr_len);
		
		inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);

		printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));

		printf("NAME = %s\n",rbufp->name);
		printf("MATH = %d\n",ntohl(rbufp->math));
		printf("CHINESE = %d\n",ntohl(rbufp->chinese));
	}	

	close(sd);

	exit(0);

}

snder.c 打开全网广播属性

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "proto.h"

int main(int argc,char *argv[])
{
	int sd;
	struct sockaddr_in raddr;
	struct msg_st *sbufp;
	int size;

	if(argc < 3)
	{
		fprintf(stderr,"Usage ...\n");
		exit(1);
	}

//需要发送的数据包大小,即柔型数组大小
	size = sizeof(struct msg_st) + strlen(argv[2]);
//根据需要发送数据包大小 申请空间
	sbufp = malloc(size);
	if(sbufp == NULL)
	{
		perror("malloc()");
		exit(1);
	}
//创建Socket, 即确定传输协议族,传输协议,传输方式
	sd = socket(AF_INET,SOCK_DGRAM,0);//IPPROTO_UDP
	if(sd < 0)
	{
		perror("soccket()");
		exit(1);
	}

//对指定Socket sd 的SOL_SOCKET层面 的 SO_BROADCAST属性进行设置,即打开该属性,表示允许将数据发送至广播地址,即打开广播属性。
	int val = 1;
	if(setsockopt(sd, SOL_SOCKET, SO_BROADCAST,&val, sizeof(val)) < 0)
	{
		perror("setsockopt()");
		exit(1);
	}
	//bind() 可省略绑定到本端地址,系统会自动分配
	
// 拷贝命令行中的 名字到 数据包对应位置
	strcpy(sbufp->name,argv[2]);
	sbufp->math = htonl(rand()%100);
	sbufp->chinese = htonl(rand()%100);

//设置 服务端地址信息, struct sockaddr_in结构体信息:协议族类型,端口,IP
	raddr.sin_family = AF_INET;
	raddr.sin_port = htons(atoi(RCVPORT));
	//地址为广播地址,255.255.255.255 广播地址
	inet_pton(AF_INET,"255.255.255.255",&raddr.sin_addr);

	//向 服务端 发送数据
	if(sendto(sd,sbufp,size,0,(void *)&raddr,sizeof(raddr)) < 0)
	{
		perror("sendto()");
		exit(1);
	}

	puts("OK");

	close(sd);

	exit(0);

}

组播篇:

man 7 ip

   Socket options
       IP supports some protocol-specific socket options that can be set with setsockopt(2) and read with getsockopt(2).  The socket option level for IP is IPPROTO_IP.  A  boolean  integer  flag  is zero when it is false, otherwise true.

层名:IPPROTO_IP
属性:

IP_ADD_MEMBERSHIP (since Linux 1.2)  加入多播组
              Join a multicast group.  Argument is an ip_mreqn structure.

                  struct ip_mreqn {
	/* 多播组地址  大整数 IP multicast group address */
                      struct in_addr imr_multiaddr; 
                      
   /* 当前自己IP地址  IP address of local  interface */
                      struct in_addr imr_address;  
                      
/*网络索引号(读取命令: ip ad sh) interface index 
if_nametoindex():名字转换成索引号
unsigned int if_nametoindex(const char *ifname);
*/
                      int            imr_ifindex;   
                  };

           /* 大整数 Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

离开多播组:
IP_ADD_SOURCE_MEMBERSHIP (since Linux 2.4.22 / 2.5.68) 
       
IP_MULTICAST_IF (since Linux 1.2) 创建多播组

proto.h

#ifndef PROTO_H_
#define PROTO_H_

#define MGROUP "224.2.2.2" //多播地址
#define RCVPORT "1989"
#define NAMEMAX 512-8-8
#define IPSTRSIZE 40
struct msg_st
{
	uint32_t math;
	uint32_t chinese;
	uint8_t name[1];
}__attribute__((pack));
	
	
#endif

snder.c 客户端

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "proto.h"

int main(int argc,char *argv[])
{
	int sd;
	struct sockaddr_in raddr;
	struct msg_st *sbufp;
	int size;


	if(strlen(argv[2]) > NAMEMAX)
	{
		fprintf(stderr,"Name is too long!\n");
		exit(1);
	}

	size = sizeof(struct msg_st) + strlen(argv[2]);
	sbufp = malloc(size);
	if(sbufp == NULL)
	{
		perror("malloc()");
		exit(1);
	}

	sd = socket(AF_INET,SOCK_DGRAM,0);//IPPROTO_UDP
	if(sd < 0)
	{
		perror("soccket()");
		exit(1);
	}

//设置 创建多播组需要的信息
 	struct ip_mreqn mreq;
	inet_pton(AF_INET,MGROUP,&mreq.imr_multiaddr);//设置多播地址
	inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);//设置当前自己IP地址
	mreq.imr_ifindex = if_nametoindex("eth0");//设置网络设备索引号 指定所用网卡名

//对指定Socket sd 的IPPROTO_IP层面 的IP_MULTICAST_IF属性进行设置,表示创建一个多播组。mreq参数为创建多播组需要的信息。
	if(setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF,&mreq, sizeof(mreq)) < 0)
	{
		perror("setsockopt()");
		exit(1);
	}

	//bind()

	strcpy(sbufp->name,argv[2]);
	sbufp->math = htonl(rand()%100);
	sbufp->chinese = htonl(rand()%100);
	
	raddr.sin_family = AF_INET;
	raddr.sin_port = htons(atoi(RCVPORT));
	inet_pton(AF_INET,MGROUP,&raddr.sin_addr);//对端地址为多播组地址


	if(sendto(sd,sbufp,size,0,(void *)&raddr,sizeof(raddr)) < 0)
	{
		perror("sendto()");
		exit(1);
	}

	puts("OK");

	close(sd);

	exit(0);

}

rcver.c

#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include "proto.h"

int main()
{
	int sd;
	struct sockaddr_in laddr,raddr;
	struct msg_st *rbufp;
	socklen_t raddr_len;
	char ipstr[IPSTRSIZE];
	int size;

	size = sizeof(struct msg_st) + NAMEMAX - 1;// -uint8_t name[1];
	rbufp = malloc(size);
	if(rbufp == NULL)
	{
		perror("malloc()");
		exit(1);
	}
	

	sd = socket(AF_INET,SOCK_DGRAM,0);//IPPROTO_UDP
	if(sd < 0)
	{
		perror("soccket()");
		exit(1);
	}

//设置 加入多播组需要的信息
 	struct ip_mreqn mreq;
	inet_pton(AF_INET,MGROUP,&mreq.imr_multiaddr);//设置多播地址
	inet_pton(AF_INET,"0.0.0.0",&mreq.imr_address);//设置当前自己IP地址
	mreq.imr_ifindex = if_nametoindex("eth0");//设置网络索引号

//对指定Socket sd 的IPPROTO_IP层面 的IP_ADD_MEMBERSHIP属性进行设置,表示加入一个多播组,mreq参数为加入多播组需要的信息。
	if(setsockopt(sd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mreq, sizeof(mreq)) < 0)
	{
		perror("setsockopt()");
		exit(1);
	}

	laddr.sin_family = AF_INET;
	laddr.sin_port = htons(atoi(RCVPORT));
	inet_pton(AF_INET,"0.0.0.0",&laddr.sin_addr);//0.0.0.0:any address

	if(bind(sd,(void *)&laddr,sizeof(laddr)) < 0)
	{
		perror("bind");
		exit(1);
	}

	/* !!!! */
	raddr_len = sizeof(raddr);

	while(1)
	{
		recvfrom(sd,rbufp,size,0,(void *)&raddr,&raddr_len);
		
		inet_ntop(AF_INET,&raddr.sin_addr,ipstr,IPSTRSIZE);

		printf("---MESSAGE FROM %s:%d---\n",ipstr,ntohs(raddr.sin_port));

		printf("NAME = %s\n",rbufp->name);
		printf("MATH = %d\n",ntohl(rbufp->math));
		printf("CHINESE = %d\n",ntohl(rbufp->chinese));
	}	

	close(sd);

	exit(0);

}

猜你喜欢

转载自blog.csdn.net/LinuxArmbiggod/article/details/115386880