winsocket组播

原文链接: https://my.oschina.net/lopo/blog/260685

为什么80%的码农都做不了架构师?>>>   hot3.png

//客户端
#include 
#include 
#include 
#pragma comment(lib,"ws2_32.lib")
#include 
#include
int main()
{
    //初始化套接字
    WSADATA wsaData;
    if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
    {
        printf("初始化套接字失败!\n");
        return -1;
    }
    printf("初始化套接字成功!\n");
    
    //建立客户端SOCKET
    SOCKET client;
    client=socket(AF_INET,SOCK_DGRAM,0);
    if(client==INVALID_SOCKET)
    {
        printf("建立客户端套接字失败; %d\n",WSAGetLastError());
        WSACleanup();
        return -1;
    }
    printf("建立客户端套接字成功!\n");
    
    sockaddr_in serveraddress;
    
         //加入组播
    struct ip_mreq mreq;
    memset(&mreq,0,sizeof(struct ip_mreq));
    mreq.imr_multiaddr.S_un.S_addr=inet_addr("224.168.00.01");    //组播源地址
    mreq.imr_interface.S_un.S_addr=INADDR_ANY;       //本地地址
    int m=setsockopt(client,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char FAR *)&mreq,sizeof(mreq));
    if(m==SOCKET_ERROR)
    {
        perror("setsockopt");
        return -1;
    }
    
    
    //接收数据
    char recvbuf[1000000];  //回头注意重新设定缓冲区大小
    int n;
    DWORD dwWrite;    //DWORD在windows下常用来保存地址(或者存放指针)
    BOOL bRet;
    int len=sizeof(sockaddr_in);
    
    
    //创建文件
    HANDLE hFile=CreateFile(_T("E:\实验室\YUV格式\shipin.yuv"),GENERIC_WRITE,0,0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
    if(hFile!=INVALID_HANDLE_VALUE)
    {
        printf("创建文件成功!\n");
    }
    
    while(1)
    {
        n=recvfrom(client,recvbuf,sizeof(recvbuf),0,(sockaddr*)&serveraddress,&len);
        if(n==SOCKET_ERROR)
        {
            printf("recvfrom error:%d\n",WSAGetLastError());
            printf("接收数据错误!\n");
            
            
        }
        //将接收到的数据写到hFile中
        bRet=WriteFile(hFile,recvbuf,n,&dwWrite,NULL);
        if(bRet==FALSE)
        {
            MessageBox(NULL,_T("Write Buf ERROR!"),_T("ERROR"),MB_OK);
            break;
        }
    }
    
    //传送成功
    MessageBox(NULL,_T("Receive file OK!"),_T("OK"),MB_OK);
    
    closesocket(client);
    WSACleanup();
    return 0;
}

 

//组播客户端
#include  
#include  
#include 
#pragma comment(lib,"ws2_32.lib") 
int main() 
{ 
WSADATA wsadata; 
SOCKET socklistener;
char buffer[1000] = {0};
SOCKADDR_IN sin,saclient;
int nSize,nbSize;
int err;
int iAddrLen = sizeof(saclient);
err=WSAStartup(MAKEWORD(2,2),&wsadata); 
if(err != 0)
{
return -1;
}
char hostname[128];
struct hostent*pHost;
int nfds;
struct timeval tv;
fd_set readfds;
socklistener = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
unsigned long cmd = 1;
int status = ioctlsocket(socklistener,FIONBIO,&cmd);
if(gethostname(hostname,128)==0) 
{ 
printf("%s\n",hostname);//计算机名字 
} 
pHost = gethostbyname(hostname); 
sin.sin_family =AF_INET;
sin.sin_port = htons(319);
sin.sin_addr.s_addr = inet_addr(inet_ntoa(*(struct in_addr*)pHost->h_addr_list[1]));
printf("ip %s\n",inet_ntoa(*(struct in_addr*)pHost->h_addr_list[1]));
if(bind(socklistener,(SOCKADDR FAR*)&sin,sizeof(sin)) != 0)
{
return -2;
}
//添加组
struct ip_mreq imr;
int sm;
memset(&imr,0,sizeof(struct ip_mreq));
imr.imr_multiaddr.S_un.S_addr = inet_addr("224.0.1.129");
imr.imr_interface.S_un.S_addr = inet_addr(inet_ntoa(*(struct in_addr*)pHost->h_addr_list[1]));
sm = setsockopt(socklistener,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char FAR*)&imr,sizeof(struct ip_mreq));
if(SOCKET_ERROR == sm)
{
printf("add member error\n");
return FALSE;
}
nfds = socklistener;
    tv.tv_sec = 10;
tv.tv_usec = 2;
while(1)
{
FD_ZERO(&readfds);
FD_SET(nfds,&readfds);
sm = select(nfds + 1,&readfds,0,0,&tv);
sm = FD_ISSET(nfds,&readfds);
if(sm < 0)
{
printf("select error\n");
}
nSize = sizeof(SOCKADDR_IN);
if((nbSize = recvfrom(socklistener,buffer,1000,0,(SOCKADDR FAR *)&saclient, &nSize)) == SOCKET_ERROR)
{
printf("receive Error\n");
continue;
}
buffer[nbSize] = '\0';
printf("success\n");
}
WSACleanup(); 
return 0;
}

 服务端

const int MAX_BUF_LEN = 255;

int main(int argc, char* argv[])
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
// 启动socket api
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return -1;
}
if ( LOBYTE( wsaData.wVersion ) != 2 ||
HIBYTE( wsaData.wVersion ) != 2 )
{
WSACleanup( );
return -1; 
}
// 创建socket
SOCKET connect_socket;
connect_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(INVALID_SOCKET == connect_socket)
{
err = WSAGetLastError();
printf("socket error! error code is %d/n", err);
return -1;
}
char hostname[128];
struct hostent*pHost;
if(gethostname(hostname,128)==0) 
{ 
printf("%s\n",hostname);//计算机名字 
} 
pHost = gethostbyname(hostname); 
printf("ip %s\n",inet_ntoa(*(struct in_addr*)pHost->h_addr_list[0]));
SOCKADDR_IN sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(321);
sin.sin_addr.s_addr = inet_addr("224.0.1.129");//组播地址
bool bOpt = true;
//设置该套接字为广播类型
setsockopt(connect_socket, SOL_SOCKET, SO_BROADCAST, (char*)&bOpt, sizeof(bOpt));
int nAddrLen = sizeof(SOCKADDR);
char buff[MAX_BUF_LEN] = "";
int nLoop = 0;
while(1)
{
nLoop++;
sprintf(buff, "%8d", nLoop);
// 发送数据
int nSendSize = sendto(connect_socket, buff, strlen(buff), 0, (SOCKADDR*)&sin, nAddrLen);
if(SOCKET_ERROR == nSendSize)
{
err = WSAGetLastError();
printf("sendto error!, error code is %d/n", err);
return -1;
}
printf("Send: %s\n", buff);
Sleep(500);
}
return 0;
}

//获取本机IP
char hostname[128]; if(gethostname(hostname,128)==0) { printf("%s\n",hostname);//计算机名字 } struct hostent *pHost = gethostbyname(hostname); for (int i = 0; pHost != NULL && pHost->h_addr_list[i] != NULL; i++) { printf("%s\n",inet_ntoa(*(struct in_addr *)pHost->h_addr_list[i])); }


 UDP组播是采用的无连接,数据报的连接方式,所以是不可靠的.也就是数据能不能到达接受端和数据到达的顺序都是不能保证的.但是由于UDP不用保证数据的可靠性,所有数据的传送速度是很快的.

1. 组播的“根”

  组播从概念上来讲分为两部分:控制部分和数据部分。控制部分决定着组播的对象的组织方式。而数据部分决定了数据的传输方式。

  控制层有“有根”,“无根”两种情况。对于有根的控制层,存在着一个root和若干个leaf. root负责管理这个组播组,只有他能邀请一个leaf加入一个组播组(ATM就是有根控制的一个典型的例子)。对于无根的控制层,没有root,只有若干的leaf. 每一个leaf都能自己加入一个组播组(IP就是无根控制的典型例子)

  数据层也有“有根”,“无根”两种情况。对于有根数据层,从root发出的数据能到达每一个leaf,而从leaf发出的数据只能到达root.对于无根数据层,每一个leaf发出的数据能到达组播组中的每一个leaf(甚至包括他自己)。每一个leaf也能接受组播组里的任何数据包。

二.IP组播地址

       IP组播通信需要一个特殊的组播地址.IP组播地址是一组D类IP地址,范围从224.0.0.0 到 239.255.255.255。其中还有很多地址是为特殊的目的保留的。224.0.0.0到224.0.0.255的地址最好不要用,因为他们大多是为了特殊的目的保持的(比如IGMP协议)

三.IGMP协议

  IGMP(internet网关管理协议)是IP组播的基础.在IP协议出现以后,为了加入对组播的支持,IGMP产生了。IGMP所做的实际上就是告诉路由器,在这个路由器所在的子网内有人对发送到某一个组播组的数据感兴趣,这样当这个组播组的数据到达后面,路由器就不会抛弃它,而是把他转送给所有感兴趣的客户。假如不同子网内的A,B要进行组播通信,那么,位与A,B之间的所有路由器必须都要支持IGMP协议,否则A,B之间不能进行通信。

  当一个应用加入一个组播组后,就会向这个子网的所有路由器发送一个IGMP加入命令,告诉他子网内有人对发送到某一个组播组的数据感兴趣.路由器也会定时向子网内的所有终端发送一条查询消息,用于询问是否还有人对某个组播组的数据感兴趣。如果有的话,终端就会回应一条IGMP消息,路由器则继续转发这个组播组的数据。如果没有人回应这条消息,那么路由器就认为已经没有终端对这个组播组的数据感兴趣,就不会在转发关于这个组播组的数据了。在IGMP第二版中,一个终端推出组播组以后,会向路由器发送一个推出消息,路由器也会通过这个消息来判断是否还要继续转发关于这个组播组的数据了(IGMP第一版中没有这个功能)[这些事情都是底层的系统做的,你只要坐享其成就好了]

 四. winsock 1组播

  winsock 1的组播主要有以下几个步骤:

1. 建立支持数据报的scoket
2. 把socket和本地的一个端口绑定(以后会通过这个端口进行数据的收发)
3. 通过setsockopt  IP_ADD_MEMBERSHIP加入一个组播组
4. 然后就能通过sendto / recvfrom进行数据的收法
5. 通过 setsockopt IP_DROP_MEMBERSHIP离开一个组播组
6. 关闭socket

  如果你仅仅是想向一个组播组发送数据,而不要接受数据,那么可不用加入组播组,而直接通过sendto向组播组发送数据


五.winsock 2组播

  winsock 2组播主要是通过WSAJoinLeaf来实现的(WSAJoinLeaf的行为,返回值根据socket的模式,组播的实现构架有很大的关系)

  winsock 2组播的主要有以下几个步骤

1. 建立支持数据报的socket(用WSASocket建立socket,同2. 时设置组播的一些属性)
3. 把socket和本地的一个端口绑定(以后会通过这个端口进行数据的收发)
4. 通过WSASocket加入一个组播组
5. 通过sendto / recvfrom进行数据的收发
6. 直接关闭socket,

7. 退出组播组


组播发送过程

IP 多点广播允许应用程序发送网络中的一组主机可以接收到的单个 IP 数据报。该组中的主机可能驻留在单个子网中,也可能驻留在连接可使用多点广播的路由器的不同子网中。主机可以随时加入或离开组。对主机组中的成员位置或数目没有任何限制。范围在 224.0.0.1  239.255.255.255 之间的 D 类因特网地址标识主机组。

应用程序可使用 socket() API 和无连接的 SOCK_DGRAM 类型套接字发送或接收多点广播数据报。多点广播是一种一对多的传送方法。不能使用类型为 SOCK_STREAM 的面向连接的套接字进行多点广播。在创建类型为SOCK_DGRAM 的套接字后,应用程序可使用 setsockopt() 函数来控制与该套接字相关联的多点广播特征。setsockopt() 函数接受下列 IPPROTO_IP 级别标志:

  • IP_ADD_MEMBERSHIP:加入指定的多点广播组。

  • IP_DROP_MEMBERSHIP:离开指定的多点广播组。

  • IP_MULTICAST_IF:设置通过其发送出局多点广播数据报的接口。

  • IP_MULTICAST_TTL:在 IP 头中设置出局多点广播数据报的有效时间TTL)。

  • IP_MULTICAST_LOOP:指定当发送主机是多点广播组的成员时,是否将出局多点广播数据报的副本传送至发送主机。 

    套接字事件流:发送多点广播数据报 
    以下套接字调用序列提供图形的描述。它还描述发送和接收多点广播数据报的两个应用程序之间的关系。每一组流包含指向有关特定 API 的使用注意事项的链接。如果需要有关使用特定 API 的更多详细信息,可使用这些链接。发送多点广播数据报使用以下函数调用序列:

    套接字事件流:接收多点广播数据报 
    接收多点广播数据报
    使用以下函数调用序列:

    注意:

    必须对通过其接收多点广播数据报的每个本地接口调用 IP_ADD_MEMBERSHIP 选项。

     

  1. read() 函数读取正在发送的多点广播数据报。

  2. close() 函数关闭所有打开的套接字描述符。

  1. socket() 函数返回表示端点的套接字描述符。该语句还标识将对此套接字使用带有 UDP传输(SOCK_DGRAM)的 INET(网际协议)地址系列。此套接字会将数据报发送至另一应用程序。

  2. setsockopt() 函数设置 SO_REUSEADDR 套接字选项,以允许多个应用程序接收目标为同一本地端口号的数据报。

  3. bind() 函数指定本地端口号。在此示例中,IP 地址被指定为 INADDR_ANY 以接收发送至多点广播组的数据报。

  4. setsockopt() 函数使用 IP_ADD_MEMBERSHIP 套接字选项,它将加入接收数据报的多点广播组。在加入组时,指定 D 类组地址和本地接口的 IP 地址。系统必须对接收多点广播数据报的每个本地接口调用 IP_ADD_MEMBERSHIP 套接字选项。

  5. socket() 函数返回表示端点的套接字描述符。该语句还标识将对此套接字使用带有 TCP 传输(SOCK_DGRAM)的 INET(网际协议)地址系列。此套接字会将数据报发送至另一应用程序。

  6. sockaddr_in 结构指定目标 IP 地址和端口号。在此示例中,地址为 225.1.1.1,而端口号为5555

  7. setsockopt() 函数设置 IP_MULTICAST_LOOP 套接字选项,所以发送系统不会接收它传送的多点广播数据报的副本。

  8. setsockopt() 函数使用 IP_MULTICAST_IF 套接字选项,它定义通过其发送多点广播数据报的本地接口。

  9. sendto() 函数将多点广播数据报发送至指定组 IP 地址。

  10. close() 函数关闭所有打开的套接字描述符。

单播和组播广播最大区别是地址的区别,发送端的最大区别,接收端是配置的区别。

转载于:https://my.oschina.net/lopo/blog/260685

你可能感兴趣的:(winsocket组播)