[C++项目] Web Server(7):UDP通信,广播和多播

[C++项目] Web Server(7):UDP通信

UDP通信

数据传输

#include <sys/types.h>
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
参数:
  - sockfd : 通信的fd
  - buf : 要发送的数据
  - len : 发送数据的长度
  - flags: 0
  - dest_addr : 通信的另外一端的地址信息
  - addrlen : 地址的内存大小

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen) ;
参数:
  - sockfd : 通信的fd
  - buf : 接收数据的数组
  - len : 数组的大小
  - flags : 0
  - src_addr : 传出参数,用来保存另外一端的地址信息,可以用于判断是否是指定的IP传来的信息,不需要可以指定为NULL
  - addrlen : 地址的内存大小

客户端代码

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //创建UDP套接字
    int fd = socket(PF_INET,SOCK_DGRAM,0);

    struct sockaddr_in saddr;
    saddr.sin_port = htons(8888);
    inet_pton(AF_INET,"127.0.0.1",&saddr.sin_addr.s_addr);
    saddr.sin_family = AF_INET;
    int len = sizeof(saddr);
    int i = 0;
    while (1) {
        char buf[1024] = {0}, rebuf[1024] = {0};
        sprintf(buf,"this is %d",i++);
        // 发送数据
        sendto(fd,buf,strlen(buf)+1,0,(struct sockaddr*)&saddr,len);

        //接受数据
        recvfrom(fd,rebuf,sizeof(rebuf),0,(struct sockaddr*)&saddr, &len);
        printf("服务器数据:%s\n",rebuf);
        sleep(1);
    }

    close(fd);
}

服务器端代码

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //创建UDP套接字
    int fd = socket(PF_INET,SOCK_DGRAM,0);

    struct sockaddr_in saddr;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_family = AF_INET;

    bind(fd, (struct sockaddr*)&saddr, sizeof(saddr));

    while (1) {
        char buf[1024] = {0};
        struct sockaddr_in caddr;
        int len = sizeof(caddr);

        // 接受数据
        recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&caddr, &len);

        char address[20];
        printf("客户端IP:%s, 端口:%d\n",inet_ntop(AF_INET, &caddr.sin_addr.s_addr,address,sizeof(address)),ntohs(caddr.sin_port));
        printf("接受数据:%s\n",buf);

        // 发送数据
        sendto(fd,buf,strlen(buf)+1,0,(struct sockaddr*)&caddr,len);

    }

    close(fd);
}

注意 UDP通信不需要额外的操作 比如多线程或者epoll也可以接受多个客户端

广播

广播是一种将消息发送给网络中的所有主机的通信方式。它通常使用 UDP(用户数据报协议)来实现,因为 UDP 是一种无连接的协议,可以将数据包发送到广播地址,从而达到广播的效果。

在 TCP(传输控制协议)通信中,没有直接支持广播的机制。TCP 是一种面向连接的协议,它通过建立可靠的点对点连接来进行通信。每个 TCP 连接都是一个独立的双向通信通道,只有发送方和接收方之间可以进行通信。这种连接的特性使得 TCP 无法直接实现广播。

如果需要在 TCP 网络中实现广播功能,可以使用其他的机制,例如在应用层上实现自定义的广播协议,或者使用多播(Multicast)来实现类似的功能。多播是一种将数据包发送给一组特定主机的通信方式,可以在 UDP 或 IP 层级上使用。

在UDP通信中,可以通过设置套接字属性来使用广播。

//设置广播属性的函数
int setsockopt(int sockfd, int level, int optname, const void *optva1, socklen_t optlen) ;
  - sockfd : 文件描述符
  - level : SOL_SOCKET
  - optname : SO_BROADCAST
  - optval : int类型的值,为1表示允许广播
  - optlen : optval的大小

更详细的对socket属性的设置可以参照[C++项目] Web Server(4):TCP 通信流程与端口复用中的说明

广播的客户端的代码

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //创建UDP套接字
    int fd = socket(PF_INET,SOCK_DGRAM,0);

    struct sockaddr_in saddr;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_family = AF_INET;

    bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));

    int len = sizeof(saddr);
    while (1) {
        char rebuf[1024] = {0};

        //接受数据
        recvfrom(fd,rebuf,sizeof(rebuf), 0, NULL, NULL);
        printf("服务器数据:%s\n",rebuf);
    }

    close(fd);
}

广播的服务器端的代码

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //创建UDP套接字
    int fd = socket(PF_INET,SOCK_DGRAM,0);

    // 设置广播属性
    int pval = 1;
    setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&pval,sizeof(pval));

    struct sockaddr_in addr;
    addr.sin_port = htons(8888);
    addr.sin_family = AF_INET;
    inet_pton(AF_INET,"192.168.112.255",&addr.sin_addr.s_addr);

    // bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    int len = sizeof(addr);
    int i = 1;
    while (1) {
        char buf[1024]= {0};
        sprintf(buf,"this is %d",i++);
        // 发送数据
        sendto(fd,buf,strlen(buf)+1,0,(struct sockaddr*)&addr,len);
    }

    close(fd);
}

多播(组播)

单播地址标识单个IP接口,广播地址标识某个子网的所有IP接口,多播地址标识一组IP接口。

单播和广播是寻址方案的两个极端(要么单个要么全部),多播则意在两者之间提供一种折中方案。

多播数据报只应该由对它感兴趣的接口接收,也就是说由运行相应多播会话应用系统的主机上的接口接收。

另外,广播一般局限于局域网内使用,而多播则既可以用于局域网,也可以跨广域网使用。

客户端需要加入多播组,才能收到多播的数据。

多播地址

IP多播通信必须依赖于IP多播地址,在IPv4中它的范围从224.0.0.0 到239.255.255.255,并被划分为三类:

  • 局部链接多播地址
  • 预留多播地址
  • 管理权限多播地址

设置多播

int setsockopt(int sockfd, int level,int optname, const void *optval, socklen_t optlen) ; 
  //服务器设置多播的信息,外出接口
  - leve1 : IPPROTO_IP
  - optname : IP_MULTICAST_IF
  - optval : struct in_ddr
  //客户端加入到多播组:
  - leve1 : IPPROTO_IP
  - optname : IP_ADD_MEMBERSHIP
  - optval : struct ip_mreq


struct ip_mreq
{
  //组播组的IP地址
  struct in_addr imr_multiaddr;

  //本地某一网络设备接口的IP地址
  struct in_addr imr_address;

  int imr_ifindex; // 网卡编号
};

typedef uint32_t in_addr_t;
struct in_addr
{
  in_addr_t s_addr;
};

多播客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main(){
    //创建UDP套接字
    int fd = socket(PF_INET,SOCK_DGRAM,0);

    struct sockaddr_in saddr;
    saddr.sin_port = htons(8888);
    saddr.sin_addr.s_addr = INADDR_ANY;
    saddr.sin_family = AF_INET;

    bind(fd,(struct sockaddr*)&saddr,sizeof(saddr));

    // 加入多播组
    struct ip_mreq pval;
    inet_pton(AF_INET,"239.0.0.10", &pval.imr_multiaddr.s_addr);
    pval.imr_interface.s_addr = INADDR_ANY

    setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&pval,sizeof(pval));

    int len = sizeof(saddr);
    while (1) {
        char rebuf[1024] = {0};

        //接受数据
        recvfrom(fd,rebuf,sizeof(rebuf), 0, NULL, NULL);
        printf("服务器数据:%s\n",rebuf);
    }

    close(fd);
}

多播服务器代码

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

int main(){
    //创建UDP套接字
    int fd = socket(PF_INET,SOCK_DGRAM,0);

    // 设置多播属性
    struct in_addr pval;
    inet_pton(AF_INET,"239.0.0.10",&pval.s_addr);
    setsockopt(fd,IPPROTO_IP,IP_MULTICAST_IF,&pval,sizeof(pval));
    
    // 初始化客户端地址
    struct sockaddr_in addr;
    addr.sin_port = htons(8888);
    addr.sin_family = AF_INET;
    inet_pton(AF_INET,"239.0.0.10",&addr.sin_addr.s_addr);

    // bind(fd, (struct sockaddr*)&addr, sizeof(addr));
    int len = sizeof(addr);
    int i = 1;
    while (1) {
        char buf[1024]= {0};
        sprintf(buf,"多播:this is %d",i++);
        // 发送数据
        sendto(fd,buf,strlen(buf)+1,0,(struct sockaddr*)&addr,len);
        sleep(1);
    }

    close(fd);
}