UDP单播、多播详解(搭配代码)

单播发送与接收

  1.创建套接字。

socketfd_udp_single = socket(AF_INET, SOCK_DGRAM, 0);

  2.定义struct sockaddr_in Local_addr变量,存放本地IP以及端口号。

struct sockaddr_in Host_addr; //存放服务端IP信息
struct sockaddr_in Local_addr; //存放本地IP信息

//服务端IP信息
Host_addr.sin_family = AF_INET;
Host_addr.sin_addr.s_addr = inet_addr(hostIp);            //服务端IP
Host_addr.sin_port = htons(40001);    //服务端端口号

//本地IP信息
Local_addr.sin_family = AF_INET;
Local_addr.sin_addr.s_addr =  htonl(INADDR_ANY);       //本地IP 
Local_addr.sin_port = htons(50001);                //本地端口号

  3.把IP地址端口相关信息和套接字相互关联起来。

bind(socketfd_udp_single, (struct sockaddr *)&Local_addr, sizeof(Local_addr));	

  4.发送数据

int sendlen = sendto(socketfd_udp_single ,temp_buf,sendnum,0,(struct sockaddr*)&Host_addr,addrlen);

  5.接收数据

#define MAXDATASIZE 1000
void GetData()
{
    char RecvBuf[100];
	int i,numbytes;
	int  RecvLen = 0;
	int retUDPsingle=0;
	fd_set readsktUDPsingle;
	struct timeval time_out;

	FD_ZERO(&readsktUDPsingle);
	time_out.tv_sec =0;
	time_out.tv_usec = 10*1000;    //设置超时时间,select遇到读事件或者超时时间到就会解阻
	FD_SET(socketfd_udp_single,&readsktUDPsingle);
	retUDPsingle = select(socketfd_udp_single+1,&readsktUDPsingle,NULL,NULL,&time_out);

	if (retUDPgroup > 0)
	{	
		if(retUDPsingle>0)
		{
			printf("UDP接收到单播数据\r\n");
            /*接收数据*/
			RecvLen = recvfrom(socketfd_udp_single, RecvBuf, MAXDATASIZE, 0,NULL,NULL);	
		}
		if ( RecvLen > 0 )
		{
			printf("recv byte = %d \r\n",RecvLen);
			printf("receive: ");
			for ( i = 0; i < RecvLen; i++ )
			{
				printf("%2.2X ",(unsigned char)RecvBuf[i]);
			}
			printf("\n\n");
		}
	}
}

  单播发送与接收整体代码:

#include <sys/types.h>    
#include <sys/socket.h>    
#include <netinet/in.h>    
#include <arpa/inet.h>    
#include <time.h>    
#include <string.h>    
#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>

#define HOSTIP    "192.168.8.8"
#define HOSTPORT    20000
#define LOCALPORT   50000

int socketfd_udp_single;
struct sockaddr_in Host_addr; //存放服务端IP信息
struct sockaddr_in Local_addr; //存放本地主机IP信息
u8 sendbuff[] = "hello";

int main(int argc, char*argv[])
{
    Host_addr.sin_family = AF_INET;
    Host_addr.sin_addr.s_addr = inet_addr(HOSTIP);
    Host_addr.sin_port = htons(HOSTPORT);
    
    Local_addr.sin_family = AF_INET;
    Local_addr.sin_addr.s_addr =  htonl(INADDR_ANY);
    Local_addr.sin_port = htons(LOCALPORT);
    
    /*创建套接字*/
    if ( (socketfd_udp_single = socket(AF_INET, SOCK_DGRAM, 0)) == -1 )
    {
        perror("Error connecting to socket\n");
        return ;
    }
    else
    {
        /*绑定端口号,否则本机端口号一直改变*/	
        bind(socketfd_udp_single, (struct sockaddr *)&Local_addr, sizeof(Local_addr));	
    }

    while(1)
    {
        GetData();
        SendData(sendbuff);
        usleep(100*1000);
    }
}

void GetData()
{
    char RecvBuf[100];
    int i,numbytes;
    int  RecvLen = 0;
    int retUDPsingle=0;
    fd_set readsktUDPsingle;
    struct timeval time_out;
    
    FD_ZERO(&readsktUDPsingle);
    time_out.tv_sec =0;	
    time_out.tv_usec = 10*1000;
    FD_SET(socketfd_udp_single,&readsktUDPsingle);
    
    //select函数作用,本质是阻塞,当遇到可读事件或者超时时间到就会解阻,
    //select函数,超时时间到返回值为0,遇到可读事件返回值大于0,利用返回值区分是否接受到数据
    retUDPsingle = select(socketfd_udp_single+1,&readsktUDPsingle,NULL,NULL,&time_out);
    
    if (retUDPsingle>0)
    {	
        printf("UDP接收到单播数据\r\n");
        RecvLen = recvfrom(socketfd_udp_single, RecvBuf, MAXDATASIZE, 0,NULL,NULL);
        if ( RecvLen > 0 )
        {
            printf("recv byte = %d \r\n",RecvLen);
            printf("receive: ");
            for ( i = 0; i < RecvLen; i++ )
            {
                printf("%2.2X ",(unsigned char)RecvBuf[i]);
            }
            printf("\n\n");
        }
    }
}


void SendData(u8 *sendbuff)
{
    int i,sendnum;
    int addrlen;
    sendnum = sizeof(sendbuff)
    if ( sendnum > 0 )
    {
        addrlen = sizeof(Host_addr);
        int sendlen = sendto(socketfd_udp_single,sendbuff,sendnum,0,Host_addr,addrlen);
    }
}

多播发送与接收

       关于多播,本人理解,多播发送是服务端往组播地址以及对应的端口号发送数据,而客户端从组播地址以及对应端口号读取数据,此处客户端的地址还是本地地址,不要有固定思维,认为多播和单播一样,只需要将本地的IP地址和端口号改为对应多播地址的IP和端口号就行了,这样的想法是错误的,单播和多播有着本质区别。组播地址就像是一个中转机构一样,将服务器的数据转发给客户端。

       具体操作步骤如下:

       多播发送分为服务器与客户端,服务器主要用来发送组播数据,客户端主要作用是从主播地址接收组播数据,服务器配置相对较简单,下面先来介绍服务器端的配置步骤:

1.创建套接字。

mcastsocketfd = socket(AF_INET, SOCK_DGRAM, 0);

  2.定义struct sockaddr_in Mcast_addr变量,存放组播组播地址以及端口号。

struct sockaddr_in mcast_addr; //存放本地IP信息

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);        /*设置多播端口*/

    3.发送数据

sendlen = sendto(socketfd_udp_single ,temp_buf,sendnum,0,(struct sockaddr*)&Host_addr,addrlen);

  服务端整体代码:

#include <unistd.h> 
#include <sys/socket.h>    
#include <netinet/in.h>    
#include <arpa/inet.h>    
#include <sys/time.h>  
#include <string.h>    
#include <stdio.h>    
#include <sys/types.h>     
#include <stdlib.h>

#define MCAST_PORT 2000            /*组播地址对应的端口号*/
#define MCAST_ADDR "224.1.0.1"    /*一个局部连接多播地址,路由器不进行转发*/

int main(int argc, char*argv)
{
    struct sockaddr_in mcast_addr;   
    int retsend,retrecv;  
    char sendbuf[9] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09};
   
    int fd_mcast = socket(AF_INET, SOCK_DGRAM, 0);         /*建立套接字*/
    if (fd_mcast == -1)
    {
        perror("socket()");
        exit(1);
    }
   
    memset(&mcast_addr, 0, sizeof(mcast_addr));
    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(fd_mcast,sendbuf,sizeof(sendbuf),0,(struct sockaddr*)&mcast_addr,sizeof(mcast_addr)) ;
        if( n < 0)
        {   
            perror("sendto()");
            exit(1);
        }  
        sleep(1);       /*1s发送一次数据*/
    }
    return 0;
}


客户端的配置步骤相对于服务端,略微麻烦些,服务端是往多播组内发送数据,因此客户端必须先加入多播组之后,才可以接收组播数据。
下面为组播客户端的配置步骤:

  1.创建套接字。

mcastsocketfd = socket(AF_INET, SOCK_DGRAM, 0);

  2.定义struct sockaddr_in Local_addr变量,存放本地IP以及端口号(注意此处的端口号不同于单播,而是组播端口号)

struct sockaddr_in Local_addr; //存放本地IP信息

//本地IP信息
Local_addr.sin_family = AF_INET;
Local_addr.sin_addr.s_addr =  htonl(INADDR_ANY);       //本地IP 
Local_addr.sin_port = htons(Mcast_port);                    //组播端口号

 3.把IP地址端口相关信息和套接字相互关联起来,但是在绑定之前最好需要采用socket端口复用的选项,因为当迅速关闭再重新创建socket之后bind会出错,根据网上资料,socket关闭后释放端口号需要一段延时,此问题的解决办法:采用socket端口复用的选项。

//采用socket端口复用
int yes = 1;
if(setsockopt(mcastsocketfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))<0)
{
    printf("reusing ADDR failed\r\n");
}
				
//绑定socket
int err = bind(mcastsocketfd,(struct sockaddr*)&Mcast_addr, sizeof(Mcast_addr)) ;
if(err < 0)
{	
	perror("bind()");
	exit(1);
}

wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw==4.设置循环许可

int loop = 1;
				
int err = setsockopt(mcastsocketfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
if(err<0)
{
	printf("setsockeopt():IP_MULTICAST_LOOP\r\n");
}

   5.将本机加入多播组

struct ip_mreq	mreq;	
memset (&mreq, 0, sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr(_Mcast_addr);	//多播地址
mreq.imr_interface.s_addr = htonl(INADDR_ANY);	//本机地址
			
int err = setsockopt(mcastsocketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
if(err<0)
{	
	perror("setsockopt():IP_ADD_MEMBERSHIP");
}	

  注意:有的时候加入多播组会失败,会显示no such device,这是因为本地路由表内没有加入组播地址
  解决办法:输入命令 route add -net 224.1.0.1 netmask 255.255.255.255 eth1"),或者直接在加入组播组之前,先加一条代码:

system("route add -net 224.1.0.1 netmask 255.255.255.255 eth1");

 其中224.1.0.1为多播组地址,需要更改为你对应的多播组地址,eth1为网口2,也需要更改为你对应的网口。

 6.接收数据

void GetData()
{
	char RecvBuf[MAXDATASIZE];
	int i,numbytes;
	int  RecvLen = 0;
	int ret,retUDPgroup,retUDPsingle=0;
	int maxfd;
	int addr_len;
	fd_set readskt,readsktUDPgroup,readsktUDPsingle;
	struct timeval time_out;

	FD_ZERO(&readsktUDPgroup);
	time_out.tv_sec =0;
	time_out.tv_usec = 10*1000;

	FD_SET(mcastsocketfd,&readsktUDPgroup);
    //此处selset需要特别说明一下,如同上述单播接收一样,也是用来解阻,此处就不多加赘述。
	retUDPgroup = select(mcastsocketfd+1,&readsktUDPgroup,NULL,NULL,&time_out);
	
	if (retUDPgroup>0)
	{	
        printf("UDP接收到多播数据\r\n");
        addr_len = sizeof(Mcast_addr);
        RecvLen = recvfrom(mcastsocketfd, RecvBuf, MAXDATASIZE, 0,(struct                                             
                           sockaddr*)&Mcast_addr,(socklen_t*)&addr_len);
        if ( RecvLen > 0 )
        {
            for ( i = 0; i < RecvLen; i++ )
            {
                printf("%2.2X ",(unsigned char)RecvBuf[i]);
            }
            printf("\n\n");	
        }
    }
}

  客户端总体代码为:

#include <unistd.h> 
#include <sys/socket.h>    
#include <netinet/in.h>    
#include <arpa/inet.h>    
#include <sys/time.h>  
#include <string.h>    
#include <stdio.h>    
#include <sys/types.h>     
#include <stdlib.h>

#define _Mcast_port 2000
#define _Mcast_addr "224.1.0.1"  
int mcastsocketfd; 

int main(int argc, char*argv)
{
    struct sockaddr_in Local_addr; //存放本地IP信息

    //创建套接字
    mcastsocketfd = socket(AF_INET, SOCK_DGRAM, 0);

    //本地IP信息
    Local_addr.sin_family = AF_INET;
    Local_addr.sin_addr.s_addr =  htonl(INADDR_ANY);       //本地IP 
    Local_addr.sin_port = htons(_Mcast_port);               //组播端口号

    //采用socket端口复用
    int yes = 1;
    if(setsockopt(mcastsocketfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(yes))<0)
    {
        printf("reusing ADDR failed\r\n");
    }
				
    //绑定socket
    int err = bind(mcastsocketfd,(struct sockaddr*)&Mcast_addr, sizeof(Mcast_addr)) ;
    if(err < 0)
    {	
	    perror("bind()");
	    exit(1);
    }
    
    //设定循环许可
    int loop = 1;				
    err = setsockopt(mcastsocketfd, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop));
    if(err<0)
    {
	    printf("setsockeopt():IP_MULTICAST_LOOP\r\n");
    }
    
    //将本机地址加入多播组
    struct ip_mreq	mreq;	
    memset (&mreq, 0, sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr(_Mcast_addr);	//多播地址
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);	//本机地址
			
    err = setsockopt(mcastsocketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));
    if(err<0)
    {	
	    perror("setsockopt():IP_ADD_MEMBERSHIP");
    }	
    
    //读取数据
    while(1)
    {
        getData();
        usleep(10*1000);
    }
    
}

void GetData()
{
	char RecvBuf[MAXDATASIZE];
	int i,numbytes;
	int  RecvLen = 0;
	int ret,retUDPgroup,retUDPsingle=0;
	int maxfd;
	int addr_len;
	fd_set readskt,readsktUDPgroup,readsktUDPsingle;
	struct timeval time_out;

	FD_ZERO(&readsktUDPgroup);
	time_out.tv_sec =0;
	time_out.tv_usec = 10*1000;

	FD_SET(mcastsocketfd,&readsktUDPgroup);
    //此处selset需要特别说明一下,如同上述单播接收一样,也是用来解阻,此处就不多加赘述。
	retUDPgroup = select(mcastsocketfd+1,&readsktUDPgroup,NULL,NULL,&time_out);
	
	if (retUDPgroup>0)
	{	
        printf("UDP接收到多播数据\r\n");
        addr_len = sizeof(Mcast_addr);
        RecvLen = recvfrom(mcastsocketfd, RecvBuf, MAXDATASIZE, 0,(struct                                             
                           sockaddr*)&Mcast_addr,(socklen_t*)&addr_len);
        if ( RecvLen > 0 )
        {
            for ( i = 0; i < RecvLen; i++ )
            {
                printf("%2.2X ",(unsigned char)RecvBuf[i]);
            }
            printf("\n\n");	
        }
    }
}

猜你喜欢

转载自blog.csdn.net/qq_37733540/article/details/94552995