zoukankan      html  css  js  c++  java
  • 广播和ip多播

    套接字选项和I/O控制命令

    • 套接字创建之后,可以使用套接字选项和ioctl命令操作他的属性,以改变套接字的默认行为。有些套接字选项仅仅是返回信息,有些选项可以影响套接字的行为。I/O控制命令缩写为ioctl,他也影响套接字的行为。

    套接字选项

    • 选项影响套接字的操作,如封包路由和OOB数据传输,获取和设置套接字选项的函数分别是getsockopt和setsockopt,他们的用法如下。
    int getsockopt(
    SOCKET s,//套接字句柄
    int level,//指定此选项被定义在哪个级别,如SIL_SOCKET、IPPROTO_TCP、IPPROTO_IP等
    int optname,//套接字选项名称,如SO_ACCEPTCONN
    char* optval,//指定一个缓冲区,所请求的选项的值将会被返回到这里
    int* optlen//指定上面缓冲区大小,返回所需大小
    );//函数调用出错返回SOCKET_ERROR
    
    • 协议是分层的,每层又有多个协议,这就造成了选项有不同的级别(level),最高层的是应用层,套接字就工作在这一层,这一层属性对应着SOL_SOCKET级别,在下一层是传输层有TCP和UDP协议,分别对应IPPROTO_TCP、IPPROTO_UDP级别,在下面是网络层有IP协议,对应着IPPROTO_IP级别。各级别的属性不同,同一级别不同属性也可能不同,所以一定要指定恰当的level参数。
    • 比如,阻塞模式下调用recbform在指定端口接收网络封包时,如果过一段时间封包还达不到recvform能够超时返回,而不是永远等待下去,仅需要设置套接字选项即可,如下所示,其中nTine是要等待的时间
    BOOL SetTimeout(SOCKET s,int nTime,BOOL bRecv)//自定义设置套接字超时值的函数
    {
    int ret=::setsockopt(s,SOL_SOCKET,bRecv?SO_REVTIMEO:SO_SNDTIMEO,(char*)&nTime,sizeof(nTime));
    return ret!=SOCKET_ERROR;
    }
    

    SOL_SOCKET级别

    • SO_ACCEPTCONN:BOOL类型,检查套接字是否进入监听模式,如果套接字已进入此选项返回TRUE。SOCK_DGRAM类型的套接字不支持此选项
    • SO_BROADCAST:BOOL类型,设置套接字传输和接收广播消息,如果给定套接字已经被设置为接收或发送广播数据,查询此套接字选项将返回TRUE,此选项对不是SOCK_STREAM类型的套接字有效。
    • SO_CONNECT_TIME:int类型,这是一个仅Microsoft相关选项,他返回连接已建立的时间,它可以在客户端套接字句柄上调用,确定是否有连接,连接已建立多长时间,没有连接返回值为0Xffffffff。
    • SO_DONTROUTE:BOOL类型,SO_DONTROUTE选项告诉下层网络堆栈忽略路由表,直接发送数据到此套接字绑定的接口。
    • SO_REUSEADDR:BOOL类型,如果值为TRUE,套接字可以被绑定到一个已经被另一个套接字使用的本地地址,或者是绑定到一个处于TIME_WAIT状态的地址
    • SO_EXCLUSIVEADDRUSE:BOOL类型,如果值为TRUE,套接字绑定到的本地端口就不能被其他进程重用。这个选项是SO_REUSEADDR的补充,阻止其他进程在你的应用程序使用的地址上使用SO_REUSEADDR
    • SO_RCVBUF和SO_SNDTIMEO:int类型,获取或者设置套接字内部为接收(发送)操作分配缓冲区的大小,套接字床创建时,会被分配一个接收缓冲区和发送缓冲区
    • SO_RCVTIMEO和SO_SNDTIMEO:int类型,获取或设置套接字上接收(发送数据的超时)

    IPPROTO_IP级别

    • 在IPPROTO_IP级别上的套接字选项与IP协议属性相关,如修改IP头的特定域,添加一个套接字到IP多播组等。
    • IP_OPTIONS:char类型,获取设置IP头中的IP选项,这个标识允许你设置IP头中的IP选项域
    • IP_HDRINCL:BOOL类型,如果值为TRUE,IP头和数据会一块提交给Winsock发送调用。置IP_HDRINCL为TRUE导致发送函数在数据前包含ip头
    • IP_TTL:int类型,设置和获取IP头中的TTL参数,数据报设置TTL限制它能够经过路由器的数量

    IOCTL

    • 用来控制套接字上I/O行为,也可以用来获取套接字上未决的I/O信息,向套接字上发送ioctl命令的函数有两个,一个是Winsock1的ioctlsocket,另一个是Winsock2的WSAIoctl。
    int ioctlsocket(
    SOCKET s,//套接字句柄
    long cmd,//在套接字上要执行的命令
    u_long* argp//指向cmd的参数
    )
    
    • WSAsock2新引进的ioctl函数WSAIoctl添加了一些新的选项
    int WSAIoctl(
    SOCKET s,//套接字句柄
    DWORD dwIoControlCode,//在套接字上要执行的命令
    LPVOID lpvInBuffer,//指向输入缓冲区
    DWORD cbInBuffer,//输入缓冲区大小
    LPVOID lpvOutBuffer,//指向输出缓冲区
    DWORD cbOutBuffer,//输出缓冲区的大小
    LPDWORD lpcbBytesReturned,//用来返回实际返回的字节数
    LPWSAOVERLAPPED lpOverlapped,//指向一个WSAOVERLAPPED结构
    LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//指向自定义完成例程
    )
    
    • FIONBIO:将套接字置于非阻塞模式,这个命令启动或者关闭套接字s上的非阻塞模式。默认情况下,所有套接字在创建时都处于阻塞模式,如果要打开非阻塞模式,调用I/O控制函数时设置argp设置为非0,如果要关闭非阻塞模式,设置argp为0.
      • WSAAsyncSelect或者WSAEventSelect函数自动设置套接字为非阻塞模式,任何试图将套接字设置为阻塞模式的调用都将以WSAEINVAL错误失败,为了将套接字设置为阻塞模式,应用程序首先让IEVENT参数等于0调用WSAAsyncSelect无效或者通过使NetworkEvents参数等于0调用WSAEventSelect无效。
    • FIONREAD:返回在套接字上要读的数据的大小。
    • SIO_GET_EXTENSION_FUNCTION_POINTER:取得与特定下层提供者相关的函数指针。
    • SIO_RCVALL:接收网络上所有的封包,,套接字必须绑定要一个明确的接口不能绑定到INADDR_ANY。一旦套接字被绑定,这个ioctl被设置,对recv/WSARecv的调用将返回IP数据报。
    //设置SIO_RCVALL控制码,以便接收所有IP包
    DWORD dwValue=1;
    if(ioctlsocket(sRaw,SIO_RCBALL,&dwValue)!=0)
    return;
    

    广播通信

    • 利用广播可以发送给本地子网上的每个机器,为了进行广播通信,必须打开广播选项S0_BROADCAST,然后使用recvform、sendto等函数收发广播数据
    • 对于UDP,存在一个特定的广播地址255.255.255.255,广播数据都应该发送到这里。
    • 发送放程序创建套接字后使用setsockopt函数打开SO_BROADCAST选项,然后设置广播地址向4567不断发送广播数据
    SOCKET s=::socket(AF_INET,SOCK_DGRAM,0)
    BOOL bBroadcast=TRUE;
    ::setsockopt(s,SOL_SOCKET,SO_BROADCAST,(char*)&bBroadcast,sizeof(BOOL));
    SOCKADDR_IN bcast;
    bcast.sin_family=AF_INET;
    bcast.sin_addr.s_addr=INVADDR_BROADCASTl
    bcast.sin_port=htons(4567);
    char sz[]="This is just a test
    ";
    while(TRUE){
    ::sendto(s,sz,strlen(sz),0,(sockaddr*)&bcast,sizeof(bcast));
    ::Sleep(5000);
    }
    
    • 可以将广播通信的端口看作电台的频率,广播程序不断向端口号发送数据,电台播放节目一样,调用recvform函数即可接收到广播数据这和其他UDP程序没有什么不同。
    SOCKET s=::socket(AF_INET,SOCK_DGRAM,0)
    SOCKADDR_IN sin;
    sin.sin_famoly=AF_INET;
    sin.sin_addr.S_un.S_addr=INADDR_ANY;
    sin.sin_port=::ntohs(4567);
    if(::bind(s,(sockaddr*)&sin,sizeof(sin))==SOCKET_ERROR){
    print("bind() failed
    ");
    return ;
    }
    SOCKADDR_IN addrRemote;
    int nLen=sizeof(addrRemote);
    char sz[256];
    while(TRUE){
    int nRet=::recvfrom(s,sz,256,0,(sockaddr*)&addrRemote,&nLen);
    if(nRet>0){
    sz[nRet]='';
    printf(sz);
    }
    }
    

    IP多播

    • 使用广播封包可以发送到网络中的每个节点,多播封包仅被发送到网络节点的一个集合。

    多播地址

    • 为了发送IP多播数据,发送者需要确定一个合适的多播地址,这个地址代表一个组。IP多播采用D类地址确定多播的组。,地址范围是224.0.0.0~239.255.255.255。不过有许多多播地址保留为特殊目的使用。
      地址|用途
      ---|:-------:
      224.0.0.0|基地址
      224.0.0.1|本子网上的所有节点
      224.0.0.2|本子网上的所有路由器
      224.0.0.4|网段中所有的DVMRP路由器
      224.0.0.5|所有的OSPE路由器
      224.0.0.6|所有的OSPE指派路由器
      224.0.0.9|所有的RIPv2路由器
      224.0.0.13|所有的PIM路由器

    组管理协议

    • IGMP是IPV4引入的管理多播客户,和他们之间关系的协议。为了多播能正常工作,两个多播几点之间所有的路由器必须支持IGMP协议。一旦路由器有一个或者多个客户主机注册的多播组,他就时不时的接收到加入命令时在内部记录下所有主机地址发送“组询问”消息。仍然存活了多播用户,会用另一个消息来响应,以便路由器支持需要继续转发与那个地址相关的数据,如果客户主机不发送响应,路由器就会认为该客户离开了多播组,从此就不再为他转发数据了。
    • 加入和离开多播组可以使用setsockopt函数,也可以使用WSAJoinLeaf函数。
    加入和离开组
    • 有两个套接字选项控制组的加入和离开:IP_ADD_MEMBERSHIP和IP_DROP_MEMBERSHIP,套接字选项级别分别是IPPROTO_IP,输入参数是一个ip_mreq结构定义如下:
    typedef struct{
    struct in_addr IMR_MULTIADDRl//多播组的IP地址
    struct in_addr IMR_INTERFACE;//将要加入或者离开多播组的本地地址
    }ip_mreq;
    
    • 下面代码示例如何加入组,其中s是已经创建好的数据报套接字
    ip_mreq mcast;
    mcast.imr_interface.S_un.S_addr=INADDR_ANY;
    mcast.imr_multiaddr.S_un.S_addr=::inet_addr("234.5.6.7");
    int nRet=::setsockopt(s,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&mcast,sizeof(mcast));
    
    • 加入一个或者多个多播组之后,可以使用IP_DROP_MEMBERSHIP选项离开特定的组。
    ip_merq mcast;
    mcast.imr_interface.Sun.S_addr=dwInterFace;
    mcast.imr_multiaddr.Sum.S_addr=dwMultiAddr;
    int nRet=::setsockopt(s,IPPROTO_IP,IP_DROP_MEMBERSHIP,(char*)&mcast,sizeof(mcast));
    
    • 每个组关系和接口关联,如果使用默认的接口,讲imr_interface设为INADDR_ANY即可,也可指明本地地址。

    接收多播数据

    • 主机在接收多播数据之前,必须成为ip多播组的成员。和单播封包一样,到特定套接字的多播封包的发送也是基于目的端口号的。为了接收发送到特定端口的多播封包,有必要绑定到那个本地端口,而不是显示的指定本地地址。
    • 如果绑定套接字设置了SO_REUSEADDR选项,就有不止一个进程可以绑定到UDP端口。
    BOOL bReuse=TRUE;
    ::setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(char*)&bReuse,sizeof(BOOL));
    
    • 如此一来。每个来到这个共享端口的多播或广播UDP封包都会被发送给所有绑定到此端口的套接字。由于向前兼容的原因,这并不包括单播封包-单播封包永远不会发送到多个套接字。
    • 绑定到本地端口4567之后,便加入多播组234.5.6.7,循环调用recvfrom函数接收发送到多播组中的数据

    发送多播数据

    • 要想组发送数据,没有必要加入那个组以234.5.6.7为目的地址,4567为目的端口调用sendto函数,即可向多播组发送数据
    • 默认情况加发送的IP多播数据报的TTL等于1,这使得他们不能被发出子网。套接字选项IP_MULTICAST_TTL用来设置多播数据报TTL的值(范围0~255)
    BOOL SetTTL(SOCKET s,int nTTL){//自定义设置多播数据TTL的函数
    int nRet=::setsockopt(s,IPPROTO_IP,IP_MULTICAST_TTL,(char*)&nTTL,sizeof(nTTL));
    return nRet!=SOCKET_ERROR;
    }
    
    • TTL为0的多播组不会在任何子网上传输,但是如果发送放属于目的的组就能够在本地传输
      • 初始TTL为0的多播封包被限制在一台主机
      • 初始TTL为1的多播封包被限制在一个子网
      • 初始TTL为32的多播封包被限制在一个站点
      • 初始TTL为64的多播封包被限制在一个地区
      • 初始TTL为128的多播封包被限制在一个大陆
      • 初始TTL为255的多播封包没有限制
    • 许多多播路由器拒绝转发目的地址在224.0.0.0~224.0.0.255之间的任何多播数据报,不管他的TTL是多少,这个地址范围是为路由器和其他底层拓扑协议或者维护协议预留的。
    • 每个多播传输仅从一个网络接口出发,即便是主机有多个剥夺接口。系统管理者在安装过程中就指定了多播使用的默认接口,可以使用套接字选项IP_MULTICAST_IF改变默认的发送数据的接口
    struct in_addr addr;
    setsockopt(sock,IPPROTO_IP,IP_MULTICAST_IF,&addr,sizeof(addr));
    
    • addr是本地对外接口,设置为INADDR_ANY可以恢复使用默认接口,IP_MULTICAST_IF可以设置多播回环是否打开,如果值为真发送到多播地址的数据会回显到套接字的接收缓冲区。默认情况下,当发送IP多播数据时,如果发送方也是多播组的一个成员。数据讲回到发送套接字。如果设置为FALSE,任何发送的数据都不会被发送回来。

    带源地址的IP多播

    • 带源地址的IP多播允许加入组时候,指定要接收哪些成员的数据,这种情况下有两种方式加入组。第一种是“包含”方式,为套接字指定N个有效原地址,套接字仅接收这些源地址的有效数据。另一种是“排除”,为套接字指定N个源地址,套接字接收来自这些源地址之外的数据。
    • 要使用“包含”方式加入多播组,应该使用套接字选项IP_ADDR_SOURCE_MEMBERSHIP和IP_DROP_SOURCE_MEMBERSHIP。第一步是添加一个或者多个源地址。这两个套接字选项的输入输出参数都是一个ip_mreq_source
    struct ip_mreq_source{
    struct in_addr imr_multiaddr;//多播组的ip地址
    struct in_addr imr_sourceaddr,//指定的源ip地址
    struct in_addr imr_interface;//本地ip地址接口
    }
    
    • imr_sourceaddr域指定了源ip地址,套接字接收来自此IP地址的数据,如果有多个有效的源地址,IP_ADD_SOURCE_MEMBERSHIP就应该被调用多次
    SOCKET s=::socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
    
    //本地接口
    SOCKETADDR_IN localif;
    localif.sin_family=AF_INET;
    localif.sin_port=HTONS(5150);
    localif.SI_ADDR.S_ADDR=HTONL(inaddr_any);
    ::bind(s,(SOCKADDR*)&localif,sizeof(localif));
    
    //设置ip_mreq_source 结构
    struct ip_mreq_source mreqsrc;
    mreqsrc.imr_interface.s_addr=inet_addr("192.168.0.46");
    
    mreqsrc.imr_multiaddr.s_addr=inet_addr("234.5.6.7");
    
    //添加源地址
    mreqsrc.imr_sourceaddr.s_addr=inet_addr("218.12.255.113");
    ::setsockopt(s,IPPROTO_IP,IP_ADD_SOURCE_MEMBERSHIP,(char*)&mreqsrc.sizeof(mreqsrc));
    mreqsrc.imr_sourceaddr.s_addr=inet_addr("218.12.174.222");
    ::setsockopt(s,IPPROTO_IP,IP_ADD_SOURCE_MEMBERSHIP,(char*)&mreqsrc,sizeof(mreqsrc));
    
    • 为了从包含集合中移除源地址,要使用IP_DROP_SOURCE_MEMBERSHIP选项为他转递多播组、本地接口和要移除的源地址
    • 为了加入多播组,同时排除一个或者多个源地址,加入组时使用IP_ADD_MEMBERSHIP选项。使用IP_ADD_MEMBERSHIP加入组等价“排除“方式加入,但是源地址也没有被排除。加入组后可以使用IP_BLOCK_SOURCE选项指定要排除的源地址,输入参数也是ip_mreq_source结构。
    • 如果应用程序想从之前排除的地址接收数据,可以通过IP_UNBLOCK_SOURCE选项从排除集合中移除此地址,输入参数仍然是ip_mreq_source结构。
    #include<WinSock2.h>
    #include<stdio.h>
    
    
    #define IP_ADD_MEMBERSHIP 12
    typedef struct {
    
        struct in_addr imr_multiaddr;//多播组的ip地址
        struct in_addr imr_interface;//将要加入或者离开多播组的本地地址
    }ip_mreq;
    
    int main() {
        SOCKET s = ::socket(AF_INET, SOCK_DGRAM, 0);
        //允许其他进程使用绑定的地址
        BOOL bReuse = TRUE;
        ::setsockopt(s,SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL));
        
    
        //绑定到4567端口
        sockaddr_in si;
        si.sin_family = AF_INET;
        si.sin_port = ::ntohs(4567);
        si.sin_addr.S_un.S_addr = INADDR_ANY;
        ::bind(s, (sockaddr*)&si, sizeof(si));
    
        //加入多播组
        ip_mreq mcast;
        mcast.imr_interface.S_un.S_addr = INADDR_ANY;
        mcast.imr_multiaddr.S_un.S_addr = ::inet_addr("234.5.6.7");//多播地址
        ::setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
    
        //接收多播数据
        printf("开始接收多播组234.5.6.7上的数据");
        char buf[1280];
        int nAddrLen(sizeof(si));
        while (TRUE)
        {
            int nRet = ::recvfrom(s, buf, strlen(buf), 0, (sockaddr*)&si, &nAddrLen);
            if (nRet!=SOCKET_ERROR)
            {
                buf[nRet] = '';
                printf(buf);
            }
            else
            {
                int i = ::WSAGetLastError();
                break;
            }
        }
        return 0;
    }
    
  • 相关阅读:
    C语言之回调函数&模块化
    680. 验证回文字符串 Ⅱ
    C++指针数组和数组指针
    345. 反转字符串中的元音字母
    633.平方数之和
    IDM使用介绍篇
    路由器无线桥接WDS
    约数的个数
    密码翻译
    查找学生信息
  • 原文地址:https://www.cnblogs.com/binarysystemloophole/p/13508339.html
Copyright © 2011-2022 走看看