• Linux网络编程系列之UDP组播


    Linux网络编程系列  (够吃,管饱)

            1、Linux网络编程系列之网络编程基础

            2、Linux网络编程系列之TCP协议编程

            3、Linux网络编程系列之UDP协议编程

            4、Linux网络编程系列之UDP广播

            5、Linux网络编程系列之UDP组播

            6、Linux网络编程系列之服务器编程——阻塞IO模型

            7、Linux网络编程系列之服务器编程——非阻塞IO模型

            8、Linux网络编程系列之服务器编程——多路复用模型

            9、Linux网络编程系列之服务器编程——信号驱动模型

    一、什么是UDP组播

            UDP组播是指使用用户数据报协议(UDP)实现的组播方式。组播是一种数据传输方式,允许单一数据包同时传输到多个接收者。在UDP组播中,一个数据包可以被多个接收者同时接收,这样可以降低网络传输的负载和提高数据传输效率。

    二、特性

            1、支持单向的多对多通信:UDP组播可以同时将一个数据包传输给多个接收者,使多个接收者能够同时获取到相同的数据。

            2、不可靠性:跟普通的UDP一样,UDP组播只提供不可靠的数据传输服务。如果某个接收者没有接收到数据包,发送者不会得到任何提示或反馈信息。

            3、可扩展性:UDP组播支持动态加入和退出组播组,能够自适应地处理组播成员的加入和离开(聊天群里的进群和退群操作)。

            4、低延迟:UDP组播传输的数据包不需要在接收方重新组装,可以直接进行处理,因此具有很低的传输延迟。

            5、高效:UDP组播传输的数据包只需要经过一次发送操作,就可以同时传输到多个接收者,可以有效地降低网络传输的负载。

            6、简单易用:UDP组播不需要复杂的配置和管理,使用简单,能够快速搭建起基于组播的多媒体通信系统。

    三、使用场景

            1、多媒体流媒体:UDP组播可以在局域网或广域网上传输音视频流,能够快速地向多个接收者发送相同的视频和音频数据,避免了建立多个点对点的连接。

            2、 分布式应用的数据分发:UDP组播可以实现高效的数据分发,例如在大型集群环境下,可以将某些服务的状态信息广播给所有节点,使得所有节点都能够及时了解到最新的信息。

            3、网络游戏:UDP组播可以用于多人联机游戏,使得多个玩家能够同时收到相同的游戏状态和动作,提高游戏体验。

            4、 网络广播:UDP组播可以用于向多个设备广播事件和消息,例如路由器可以向所有连接的设备发送网络配置信息、DHCP服务器可以向所有设备广播IP地址信息等。

            5、实时数据更新:UDP组播可以用于实时的数据更新,例如在金融行业,可以订阅某些财经数据的实时更新,以便及时响应市场变化。

            可以把UDP组播简单理解为群聊。

    四、UDP组播通信流程

            1、发送方

            (1)、建立套接字。使用socket()

            (2)、设置端口复用。使用setsockopt()(可选,推荐)

            (3)、绑定自己的IP地址和端口号。使用bind()(可以省略)

            (4)、发送数据,接收方IP地址要填写为组播地址。使用sendto()

            (5)、关闭套接字。使用close()

            2、接收方

            (1)、建立套接字。使用socket()

            (2)、定义并初始化一个组播结构体。使用struct  ip_mreq;

            (3)、给套接字加入组播属性。使用setsockopt()

            (4)、绑定自己的IP地址和端口号。使用bind(),不可以省略

            (5)、接收数据。使用recvfrom()

            (6)、关闭套接字。使用close()

    五、相关函数API

              1、建立套接字

    1. // 建立套接字
    2. int socket(int domain, int type, int protocol);
    3. // 接口说明
    4. 返回值:成功返回一个套接字文件描述符,失败返回-1
    5. 参数domain:用来指定使用何种地址类型,有很多,具体看别的资源
    6. 1)PF_INET 或者 AF_INET 使用IPV4网络协议
    7. 2)其他很多的,看别的资源
    8. 参数type:通信状态类型选择,有很多,具体看别的资源
    9. 1)SOCK_STREAM 提供双向连续且可信赖的数据流,即TCP
    10. 2)SOCK_DGRAM 使用不连续不可信赖的数据包连接,即UDP
    11. 参数protocol:用来指定socket所使用的传输协议编号,通常不用管,一般设为0

               2、设置端口状态

    1. // 设置端口的状态
    2. int setsockopt(int sockfd,
    3. int level,
    4. int optname,
    5. const void *optval,
    6. socklen_t optlen);
    7. // 接口说明
    8. 返回值:成功返回0,失败返回-1
    9. 参数sockfd:待设置的套接字
    10. 参数level: 待设置的网络层,一般设成为SOL_SOCKET以存取socket层
    11. 参数optname:待设置的选项,有很多种,具体看别的资源,这里讲常用的
    12. 1)、SO_REUSEADDR 允许在bind()过程中本地地址可复用,即端口复用
    13. 2)、SO_BROADCAST 使用广播的方式发送,通常用于UDP广播
    14. 3)、SO_SNDBUF 设置发送的暂存区大小
    15. 4)、SO_RCVBUF 设置接收的暂存区大小
    16. 5)、IP_ADD_MEMBERSHIP 设置为组播
    17. 参数optval:待设置的值
    18. 参数optlen:参数optval的大小,即sizeof(optval)
    19. // 组播结构体
    20. struct ip_mreq
    21. {
    22. struct in_addr imr_multiaddr; // 多播组的IP地址,就是组播的IP地址
    23. struct in_addr imr_interface; // 需要加入到组的IP地址,就是自己的IP地址
    24. };

             3、绑定IP地址和端口号

    1. // 绑定自己的IP地址和端口号
    2. int bind(int sockfd,
    3. const struct sockaddr *addr,
    4. socklen_t addrlen);
    5. // 接口说明
    6. 返回值:
    7. 参数sockfd:待绑定的套接字
    8. 参数addrlen:参数addr的大小,即sizeof(addr)
    9. 参数addr:IP地址和端口的结构体,通用的结构体,根据sockfd的类型有不同的定义
    10. 当sockfd的domain参数指定为IPV4时,结构体定义为
    11. struct sockaddr_in
    12. {
    13. unsigned short int sin_family; // 需与sockfd的domain参数一致
    14. uint16_t sin_port; // 端口号
    15. struct in_addr sin_addr; // IP地址
    16. unsigned char sin_zero[8]; // 保留的,未使用
    17. };
    18. struct in_addr
    19. {
    20. uin32_t s_addr;
    21. }
    22. // 注意:网络通信时,采用大端字节序,所以端口号和IP地址需要调用专门的函数转换成网络字节序

             4、字节序转换接口 

    1. // 第一组接口
    2. // 主机转网络IP地址,输入主机IP地址
    3. uint32_t htonl(uint32_t hostlong);
    4. // 主机转网络端口,输入主机端口号
    5. uint16_t htons(uint16_t hostshort); // 常用
    6. // 网络转主机IP,输入网络IP地址
    7. uint32_t ntohl(uint32_t netlong);
    8. // 网络转主机端口,输入网络端口
    9. uint16_t ntohs(uint16_t netshort);
    10. // 第二组接口,只能用于IPV4转换,IP地址
    11. // 主机转网络
    12. int inet_aton(const char *cp, struct in_addr *inp);
    13. // 主机转网络
    14. in_addr_t inet_addr(const char *cp); // 常用
    15. // 网络转主机
    16. int_addr_t inet_network(const char *cp);
    17. // 网络转主机
    18. char *inet_ntoa(struct in_addr in); // 常用
    19. // 将本地IP地址转为网络IP地址
    20. int inet_pton(int af, const char *src, void *dst);
    21. // 参数说明:
    22. 参数af:选择是哪一种协议族,IPV4还是IPV6
    23. 参数src:本地IP地址
    24. 参数dst:将本地IP地址转为网络IP地址存储到这里

               5、发送数据

    1. // UDP协议发送数据
    2. ssize_t sendto(int sockfd,
    3. const void *buf,
    4. size_t len,
    5. int flags,
    6. const struct sockaddr *dest_addr,
    7. socklen_t addrlen);
    8. // 接口说明
    9. 返回值:成功返回成功发送的字节数,失败返回-1
    10. 参数sockfd:发送者的套接字
    11. 参数buf:发送的数据缓冲区
    12. 参数len:发送的长度
    13. 参数flags:一般设置为0,还有其他数值,具体查询别的资源
    14. 参数dest_addr:接收者的网络地址
    15. 参数addrlen:接收者的网络地址大小,即sizeof(dest_addr)

             6、接收数据

    1. // UDP协议接收数据
    2. ssize_t recvfrom(int sockfd,
    3. void *buf,
    4. size_t len,
    5. int flags,
    6. struct sockaddr *src_addr,
    7. socklen_t *addrlen);
    8. // 接口说明:
    9. 返回值:成功返回成功接收的字节数,失败返回-1
    10. 参数sockfd:接收者的套接字
    11. 参数buf:接收数据缓的冲区
    12. 参数len:接收的最大长度
    13. 参数flags:一般设置为0,还有其他数值,具体查询别的资源
    14. 参数src_addr:发送者的网络地址,可以设置为NULL
    15. 参数addrlen: 发送者的网络地址大小,即sizeof(src_addr)

              7、关闭套接字

    1. // 关闭套接字
    2. int close(int fd);
    3. // 接口说明
    4. 返回值:成功返回0,失败返回-1
    5. 参数fd:套接字文件描述符

    六、案例

           实现UDP组播的演示

            发送端GroupSend.c

    1. // UDP组播发送方的案例
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define SEND_IP "192.168.64.128" // 记得改为自己IP
    11. #define SEND_PORT 10000 // 不能超过65535,也不要低于1000,防止端口误用
    12. int main(int argc, char *argv[])
    13. {
    14. // 1、建立套接字,使用IPV4网络地址,UDP协议
    15. int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    16. if(sockfd == -1)
    17. {
    18. perror("socket fail");
    19. return -1;
    20. }
    21. // 2、设置端口复用(推荐)
    22. int optval = 1; // 这里设置为端口复用,所以随便写一个值
    23. int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    24. if(ret == -1)
    25. {
    26. perror("setsockopt fail");
    27. close(sockfd);
    28. return -1;
    29. }
    30. // 3、绑定自己的IP地址和端口号(可以省略)
    31. struct sockaddr_in send_addr = {0};
    32. socklen_t addr_len = sizeof(struct sockaddr);
    33. send_addr.sin_family = AF_INET; // 指定协议为IPV4地址协议
    34. send_addr.sin_port = htons(SEND_PORT); // 端口号
    35. send_addr.sin_addr.s_addr = inet_addr(SEND_IP); // IP地址
    36. ret = bind(sockfd, (struct sockaddr*)&send_addr, addr_len);
    37. if(ret == -1)
    38. {
    39. perror("bind fail");
    40. close(sockfd);
    41. return -1;
    42. }
    43. // 4、发送数据,往组播地址
    44. uint16_t port = 0; // 端口号
    45. char ip[20] = {0}; // IP地址
    46. struct sockaddr_in recv_addr = {0};
    47. char msg[128] = {0}; // 数据缓冲区
    48. // 注意输入组播地址,范围是D类网络地址,224.0.0.1~239.255.255.254
    49. printf("please input receiver IP and port\n");
    50. scanf("%s %hd", ip, &port);
    51. printf("IP = %s, port = %hd\n", ip, port);
    52. recv_addr.sin_family = AF_INET; // 指定用IPV4地址
    53. recv_addr.sin_port = htons(port); // 接收者的端口号
    54. recv_addr.sin_addr.s_addr = inet_addr(ip); // 接收者的IP地址
    55. while(getchar() != '\n'); // 清空多余的换行符
    56. while(1)
    57. {
    58. printf("please input data:\n");
    59. fgets(msg, sizeof(msg)/sizeof(msg[0]), stdin);
    60. // 发送数据,注意要填写接收者的地址
    61. ret = sendto(sockfd, msg, strlen(msg), 0,
    62. (struct sockaddr*)&recv_addr, addr_len);
    63. if(ret > 0)
    64. {
    65. printf("success: send %d bytes\n", ret);
    66. }
    67. }
    68. // 5、关闭套接字
    69. close(sockfd);
    70. return 0;
    71. }

            接收端GroupRecv.c

      

    1. // UDP组播接收方的案例
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define RECV_IP "192.168.64.128" // 记得改为自己的地址
    11. #define GROUP_IP "224.0.0.10" // 组播地址
    12. #define GROUP_PORT 20000 // 不能超过65535,也不要低于1000,防止端口误用
    13. int main(int argc, char *argv[])
    14. {
    15. // 1、建立套接字,使用IPV4网络地址,UDP协议
    16. int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    17. if(sockfd == -1)
    18. {
    19. perror("socket fail");
    20. return -1;
    21. }
    22. // 2、定义并初始化一个组播结构体,设置组播IP
    23. struct ip_mreq vmreq;
    24. inet_pton(AF_INET, GROUP_IP, &vmreq.imr_multiaddr); // 初始化组播地址
    25. inet_pton(AF_INET, RECV_IP, &vmreq.imr_interface); // 把自己的地址加入到组中
    26. // 3、给套接字加入组播属性
    27. int ret = setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &vmreq, sizeof(vmreq));
    28. if(ret == -1)
    29. {
    30. perror("setsockopt fail");
    31. close(sockfd);
    32. return -1;
    33. }
    34. // 4、绑定自己的IP地址和端口号(不可以省略)
    35. struct sockaddr_in recv_addr = {0};
    36. socklen_t addr_len = sizeof(struct sockaddr);
    37. recv_addr.sin_family = AF_INET; // 指定协议为IPV4地址协议
    38. recv_addr.sin_port = htons(GROUP_PORT); // 端口号,注意绑定为组播的端口号
    39. // recv_addr.sin_addr.s_addr = inet_addr(RECV_IP); // IP地址. 写下面的更好
    40. recv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机内所有的IP地址
    41. ret = bind(sockfd, (struct sockaddr*)&recv_addr, addr_len);
    42. if(ret == -1)
    43. {
    44. perror("bind fail");
    45. close(sockfd);
    46. return -1;
    47. }
    48. // 4、接收数据
    49. uint16_t port = 0; // 端口号
    50. char ip[20] = {0}; // IP地址
    51. struct sockaddr_in send_addr = {0};
    52. char msg[128] = {0}; // 数据缓冲区
    53. while(1)
    54. {
    55. // 接收数据,注意使用发送者的地址来接收
    56. ret = recvfrom(sockfd, msg, sizeof(msg)/sizeof(msg[0]), 0,
    57. (struct sockaddr*)&send_addr, &addr_len);
    58. if(ret > 0)
    59. {
    60. memset(ip, 0, sizeof(ip)); // 先清空IP
    61. strcpy(ip, inet_ntoa(send_addr.sin_addr)); // 网络IP转主机IP
    62. port = ntohs(send_addr.sin_port); // 网络端口号转主机端口号
    63. printf("[%s:%d] send data: %s\n", ip, port, msg);
    64. memset(msg, 0, sizeof(msg)); // 清空数据区
    65. }
    66. }
    67. // 5、关闭套接字
    68. close(sockfd);
    69. return 0;
    70. }

          通信演示 

            注:第一幅图只有一台主机,不好演示;第二幅图有两台主机,一台本机,另外一台用ssh连接,实现了组播。

    七、总结

           组播是一种数据传输方式,允许单一数据包同时传输到多个接收者。在UDP组播中,一个数据包可以被多个接收者同时接收,这样可以降低网络传输的负载和提高数据传输效率。组播主要应用以群聊的场景。UDP组播的通信流程,跟UDP的广播的通信流程大致相同,但是要注意组播接收方要定义一个组播结构体,然后把自己的IP地址加入到组播中。可以结合案例加深对组播的理解。

  • 相关阅读:
    Windows安装Docker
    mkv视频文件损坏如何修复?很简单方法
    网络安全攻防:软件逆向之反汇编
    航运船公司人工智能AI产品成熟化标准化规模应用,全球港航人工智能/集装箱人工智能领军者CIMC中集飞瞳,打造国际航运智能化标杆
    Linux驱动之INPUT子系统框架
    互联网职场人写周报的正确姿势
    离线 notepad++ 添加到右键菜单
    【探索Linux世界|中秋特辑】--- 倒计时和进度条的实现与演示
    Java入门刷题篇 基础语法>>运算符>>JAVA3四舍五入
    【Linux】进程
  • 原文地址:https://blog.csdn.net/AABond/article/details/133589579