先说下结论,
WIFI 单播与组播的对比
802.11 只有 unicast 和 broadcast。UDP 广播以及组播在实际传输上,根据设置的不同,可以被操作系统自动转换成 unicast 和 broadcast。转成 unicast 的好处很明显:
1. 可以使用 multi stream。即多个空间流传输,能够达到无线的标称速率比如:433Mbps/866Mbps
2. 可靠性比 broadcast 高,因为每一个包对会有物理层的 ACK。
3. 可以使用 802.11n 之后的高级特性,比如 A-MPDU,A-MSDU 等。
缺点也很明显,有多个 client 的时候,每个都要这样传输一遍,很占空域时间。实际上 OpenWrt 等路由器的默认广播 /组播就是用 unicast 发送的,要想修改可以参考如下: https://wiki.openwrt.org/doc/howto/udp_multicast。
使用 multcast 发送的好处如下:
最近再做一个项目,要求WIFI AP实时的向客户端发送UDP数据,有时候可能会存在很多这样的客户端,而发送的数据一样, 这不就是要发送广播包吗?毫不犹豫的使用了UDP广播包发送,但是测试过程中发现个问题,会不定时丢包,每500ms发1包,1分钟120包,总会丢那么三五包, 如果距离离的远的话,会丢的更多,即使客户端就放在AP旁边也会丢几包。
于是就开始了漫长的查问题。
1,模拟测试环境,直接使用电脑与路由器来测试,发现如果用UDP广播 发送的话,一样会丢包, 看来是UDP广播的特性,不保证成功率, 如果使用UDP点到点传输的话,会好很多。 看来使用广播发送更加剧了不确定性。
2. 虽然UDP广播包会丢包,但是ping非常稳定,不会超时,不会丢包,如下图,连续ping了半个小时,1包没有丢。
3. 上面2者对比,我就很困惑, UDP广播为什么会丢呢? 而同样发送的ping包却1包不丢, 就是wifi底层协议的问题? 一时也解决不了了问题,好在不耽误应用,
ping是没问题的
下面可以看出丢了4E这包数据, 但此时网络是通的。
下面是周期性发送UDP广播包的的程序,在我的嵌入式linux上已经正常运行。
这个程序调试过程花费了我很多时间,也是没有想到的,主要有2点
1. 新建socket绑定在INADDR_ANY也就是0.0.0.0时, 是不能发送到255.255.255.255的广播包的,只能发送到网段的广播包,如192.168.111.255
2. 要想发送255.255.255.255的广播包,必须绑定一个IP地址,我猜其实是绑定一个本地网卡,
(结论: 一个socket,即不知道源地址,也不知道目的地址, 是发送不成功的,因为系统不知道这包数据应该从哪个网卡发出去。我当时调用sendto(),一直返回-1)
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
/* According to POSIX.1-2001, POSIX.1-2008 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <stdbool.h>
#include <time.h>
#define MYPORT1 3060
void main()
{
struct sockaddr_in locaddr, servaddr;
socklen_t nRecLen;
int sock1;
int i;
uint8_t data=0;
int so_broadcast=1;
int ret;
char recv_buf[2048]; // 接收缓冲区
char snd_buf[128];
//int nRecLen; // 客户端地址长度!!!!
if ((sock1 = socket(PF_INET, SOCK_DGRAM, 0)) < 0)
printf("socket()\n");
printf("sock1: %d creat OK\n", sock1);
ret = setsockopt(sock1, SOL_SOCKET, SO_BROADCAST, &so_broadcast, sizeof(so_broadcast));
printf("setsockopt() %d\r\n", ret);
memset(&locaddr, 0, sizeof(locaddr));
locaddr.sin_family = AF_INET;
locaddr.sin_port = htons(MYPORT1);
locaddr.sin_addr.s_addr = inet_addr("192.168.111.1"); //htonl(INADDR_ANY);
if (bind(sock1, (struct sockaddr *)&locaddr, sizeof(locaddr)) < 0)
{
printf("blind error\r\n");
}
else{
printf("listening %d port\r\n",MYPORT1);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(3062);
servaddr.sin_addr.s_addr = inet_addr("255.255.255.255");
while(1)
{
sleep(1);
ret = sendto(sock1, snd_buf, sizeof(snd_buf), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
memset(snd_buf, data, sizeof(snd_buf));
data++;
printf("sendto %02X return %d\r\n", data, ret);
}
}
就是在这货身上调试的。