网络编程之UDP多播通信

UDP通信分类

基于UDP(面向无连接)的socket编程 这篇文章中,给出了UDP服务端和客户端编码流程。根据不同的场景需要,我们可以将UDP编程设置为三种通信模式,分别如下:

  • 单播
  • 广播
  • 组播(也叫多播)

不同的通信模式是通过setsockopt系统接口来完成,默认是UDP是单播模式,组播和广播需要setsockopt来配合完成。

以下是对三种通信方式概念说明

  • 单播,一对一的通信方式,一个客户端和一个服务端之间的消息通信
  • 广播,一对多的通信方式,会将消息分发给整个局域网内的所有主机,
  • 组播,一对多的通信方式,将网络上的主机进行逻辑上的分组,通信只会在同一个分组上面进行收发消息。

适用场景说明:

单播

下图是单播场景的网络拓扑图,常见于多个client向服务端上报各自消息,用于信息收集,逻辑上是一对一的,因为上报的消息是不同的。此时,客户端的sockaddr_in中的IP地址信息需要设置为具体的服务器地址
在这里插入图片描述
组播

下图是组播的网络拓扑图,主机被逻辑上归类为两组,后续消息收发都在各自分组上进行,互不影响。此时,服务端需要设置加入组播组和退出组播组的设置即可
在这里插入图片描述
广播

下图是广播场景的网络拓扑图,常见于一个客户端向多个服务端广播消息,执行同一个动作或者初始化。此时,需要对客户端的socket设置广播标志以及sockaddr_in中的IP地址设置"255.255.255.255"或者使用htonl(INADDR_BROADCAST)
在这里插入图片描述
组播通信设置

为了完成UDP上的组播通信,需要使用setsocketopt接口完成网络组播设置,其设置主要如下:

//加入多播组
struct ip_mreq stMreq;
//IP multicast address of group
stMreq.imr_multiaddr.s_addr = inet_addr("239.1.100.1");
//Local IP address of interface
stMreq.imr_interface.s_addr=htonl(INADDR_ANY);
setsockopt(RecvSocket,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq)); 

//结束后退出组播地址
setsockopt(RecvSocket,IPPROTO_IP,IP_DROP_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq));

组播通信源码

服务端:

#include "stdafx.h"
#include <WinSock2.h>
#include <stdlib.h>
#include <Ws2tcpip.h>
#ragma comment(lib, "ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    int iResult = 0;
    const int BufLen = 1024;
    char RecvBuf[BufLen];

    WSADATA wsaData;
    SOCKET RecvSocket;

    sockaddr_in SenderAddr;
    int SenderAddrSize = sizeof (SenderAddr);

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != NO_ERROR) 
    {
        printf("WSAStartup failed with error %d\n", iResult);
        return 1;
    }
    // Create a receiver socket to receive datagrams
    RecvSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (RecvSocket == INVALID_SOCKET) 
    {
        printf("socket failed with error %d\n", WSAGetLastError());
        return 1;
    }

    // Bind the socket to any address and the specified port.
    sockaddr_in RecvAddr;
    RecvAddr.sin_family = AF_INET;
    RecvAddr.sin_port = htons(6000);
    RecvAddr.sin_addr.s_addr = htonl(INADDR_ANY);

    iResult = bind(RecvSocket, (sockaddr *) & RecvAddr, sizeof (RecvAddr));
    if (iResult != 0) 
    {
        printf("bind failed with error %d\n", WSAGetLastError());
        return 1;
    }

    //加入多播组
    struct ip_mreq stMreq;
    //IP multicast address of group
    stMreq.imr_multiaddr.s_addr = inet_addr("239.1.100.1");
    //Local IP address of interface
    stMreq.imr_interface.s_addr=htonl(INADDR_ANY);
    setsockopt(RecvSocket,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq)); 

    // Call the recvfrom function to receive datagrams
    // on the bound socket.
    char szMsgData[128] = {};

    while (1)
    {
        printf("waiting get msg ....\n");
        memset(RecvBuf,0, sizeof(RecvBuf));

        iResult = recvfrom(RecvSocket,RecvBuf, BufLen, 0, (sockaddr*)&SenderAddr, &SenderAddrSize);
        if (iResult == SOCKET_ERROR) 
        {
            printf("recvfrom failed with error %d\n", WSAGetLastError());
        }
        else
        {
            printf("client->server:%s\n",RecvBuf);
            int nSendSize = sendto(RecvSocket,"i received",strlen("i received"),0,(sockaddr*)&SenderAddr,SenderAddrSize);
            if (nSendSize <= 0)
            {
                printf("send data error...\n");
            }
        }

         if (strlen(RecvBuf) == 1 && 'q' == RecvBuf[0])
         {
             break;
         }
    }

    // Close the socket when finished receiving datagrams
    printf("Finished receiving. Closing socket.\n");

    //结束后退出组播地址
    setsockopt(RecvSocket,IPPROTO_IP,IP_DROP_MEMBERSHIP,(char *)&stMreq,sizeof(stMreq)); 
    if (closesocket(RecvSocket) == SOCKET_ERROR) 
    {
        printf("closesocket failed with error %d\n", WSAGetLastError());
        return 1;
    }

    // Clean up and exit.
    WSACleanup();
    return 0;
}

客户端:

客户端代码和单播的场景是一样的,不需要进行特殊的设置。

#include "stdafx.h"
#include <Windows.h>
#pragma  comment(lib, "ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    WORD        wVersionRequested;
    WSADATA     wsaData;
    wVersionRequested = MAKEWORD(2, 2);
    int err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) 
    {
        /* Tell the user that we could not find a usable */
        printf("WSAStartup failed with error: %d\n", err);
        return 1;
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        printf("Could not find a usable version of Winsock.dll\n");
        WSACleanup();
        return 1;
    }
    
    //创建socket
    SOCKET socketClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (socketClient == INVALID_SOCKET) 
    {
        printf("socket failed with error %d\n", WSAGetLastError());
        return 1;
    }

    //填充服务端通信端口以及通信地址
    SOCKADDR_IN addrServer;
    addrServer.sin_family   = AF_INET;
    addrServer.sin_port     = htons(6000);
    addrServer.sin_addr.S_un.S_addr = inet_addr("239.1.100.1");
    
    int nLength = sizeof(addrServer);
    char szSendMsg[100]= {0};
    char szRecvBuf[100] = {0};

    while (1)
    {
        //发送数据
        printf("pls input msg...:\n");
        memset(szSendMsg,0, sizeof(szSendMsg));
        gets_s(szSendMsg, sizeof(szSendMsg));

        int nSendSize = sendto(socketClient,szSendMsg,strlen(szSendMsg),0,(SOCKADDR*)&addrServer,nLength);
        if (nSendSize > 0)
        {
            printf("client->server:%s\n",szSendMsg);
        }
        else
        {
            printf("send data error...\n");
        }

        //接收数据
        memset(szRecvBuf,0, sizeof(szRecvBuf));
        int nRecvSize = recvfrom(socketClient,szRecvBuf,100,0,(SOCKADDR*)&addrServer,&nLength);
        if (nRecvSize > 0)
        {
            printf("server->client:%s\n",szRecvBuf);
        }
    }

    closesocket(socketClient);
    WSACleanup();
    return 0;
}

更多的多播参数设置可参考如下资料:

https://www.tldp.org/HOWTO/Multicast-HOWTO-6.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值