17 Star 59 Fork 24

Talkweb_OpenHarmony / Niobe

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
README.md 10.69 KB
一键复制 编辑 原始数据 按行查看 历史

Niobe开发板UDP联网演示

本案例程序将演示怎么在拓维Niobe WiFi IoT Core开发板上编写一个创建UDP服务器的业务程序,实现开发板联网与UDP客户端数据通信。

简述

UDP是用户数据包协议(UDP,User Datagram Protocol), 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。RFC 768 描述了 UDP。

Internet 的传输层有两个主要协议,互为补充。无连接的是 UDP,它除了给应用程序发送数据包功能并允许它们在所需的层次上架构自己的协议之外,几乎没有做什么特别的事情。面向连接的是 TCP,该协议几乎做了所有的事情

UDP服务端编程步骤

1、创建一个socket,用函数socket(); 2、设置socket属性,用函数setsockopt();* 可选 3、绑定IP地址、端口等信息到socket上,用函数bind(); 4、循环接收/发送数据,用函数recvfrom()/sendto(); 5、关闭网络连接;

特点

1、UDP是无连接的,即发送数据之前不需要建立连接; 2、UDP使用尽最大努力交付,即不保证可靠交付; 3、UDP是面向报文的; 4、UDP支持一对一、一对多、多对一和多对多的交互通信等。

结构体详解

struct sockaddr_in {
	sa_family_t sin_family;
	in_port_t sin_port;
	struct in_addr sin_addr;
	uint8_t sin_zero[8];
};

描述:

套接字地址,存放地址和端口信息

参数:

名字 描述
sin_family 指代协议族,在socket编程中只能是AF_INET
sin_port 存储端口号(使用网络字节顺序)
sin_addr 存储IP地址,使用in_addr这个数据结构
sin_zero 为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节

示例代码如下:

	//服务端地址信息
	struct sockaddr_in server_sock;
	bzero(&server_sock, sizeof(server_sock));
	server_sock.sin_family = AF_INET;
	//server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
	inet_pton(AF_INET, _SERVER_IP_, &server_sock.sin_addr); 
	server_sock.sin_port = htons(_PROT_);

typedef struct {
	unsigned long fds_bits[FD_SETSIZE / 8 / sizeof(long)];
} fd_set;

描述:

文件描述符集合

操作函数:

名字 描述
void FD_CLR(int fd, fd_set *set) 清除某一个被监视的文件描述符
int FD_ISSET(int fd, fd_set *set) 测试一个文件描述符是否是集合中的一员
void FD_SET(int fd, fd_set *set) 添加一个文件描述符,将set中的某一位设置成1
void FD_ZERO(fd_set *set) 清空集合中的文件描述符,将每一位都设置为0

示例代码如下:

	fd_set fds;
	FD_ZERO(&fds);
    FD_SET(sock_fd,&fds);//将sock_fd添加至fds

    if(FD_ISSET(sock_fd,&fds))//判断sock_fd是否在fds中
        ;//

struct sockaddr {
	sa_family_t sa_family;
	char sa_data[14];
};

描述:

通用的套接字地址,与sockaddr_in类似

参数

名字 描述
sa_family 地址族
sa_data 14字节,包含套接字中的目标地址和端口信息

函数详解

int WifiConnect(const char *ssid, const char *psk)

描述: 初始化网络,并连接指定wifi

参数:

名字 描述
ssid 指定wifi的账户名
psk 指定wifi的密码
int socket(int domain, int type, int protocol)

描述:

创建套接字

参数:

名字 描述
domain 地址族,也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6
type 数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM(数据报套接字/无连接的套接字)和SOCK_RAW(原始套接字)
protocol 传输协议,常用的有 IPPROTO_TCP 和 IPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议
int bind(int fd, const struct sockaddr *addr, socklen_t len)

描述:

把用于通信的地址和端口绑定到 socket 上

参数:

名字 描述
fd 需要绑定的socket
addr 存放服务端用于通信的地址和端口
len addr 结构体的大小
ssize_t recvfrom(int fd, void *restrict buf, size_t len, int flags, struct sockaddr *restrict addr, socklen_t *restrict alen)

描述:

接收数据

参数:

名字 描述
fd 服务端的套接字
buf UDP数据报缓存区(包含所接收的数据)
len 缓冲区长度
flags 调用操作方式(一般设置为0)
addr 指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换)
alen 指针,指向addr结构体长度值
ssize_t sendto(int fd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t alen)

描述:

发送数据

参数:

名字 描述
fd 套接字
buf UDP数据报缓存区(包含待发送数据)
len UDP数据报的长度
flags 调用操作方式(一般设置为0)
addr 指向接收数据的主机地址信息的结构体(sockaddr_in需类型转换)
alen addr所指结构体的长度
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

描述:

IO多路复用

参数:

名字 描述
nfds 表示集合中所有文件描述符的范围
readfds select监视的可读文件句柄集合
writefds select监视的可写文件句柄集合
exceptfds select监视的异常文件句柄集合
timeout 本次select()的超时结束时间,NULL表示永久等待

软件设计

主要代码分析

static void UDPServerTaskWithSelect(void)
{
	//服务端地址信息
	struct sockaddr_in server_sock;
	
	int sin_size = sizeof(struct sockaddr_in);
	//连接Wifi
	if(WifiConnect(WIFI_SSID,WIFI_PASSWORD)!=0)
	{
		perror("Wifi connect error!");
		exit(1);
	}
	//in_addr_t localIpaddr = GetLocalIpaddr();
	uint32_t localIpaddr = htonl(GetLocalIpaddr());
	char strr[IP_LEN];
	inet_ntop(AF_INET, &server_sock.sin_addr, strr, sizeof(strr));
	printf("GetLocalIpaddr is %s \n",strr);

	//创建socket
	printf("socket begin\n");
	int sock_fd=-1;
	if ((sock_fd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
	{
		perror("socket is error\r\n");
		exit(1);
	}
	printf("sock_fd=%d\n",sock_fd);

	bzero(&server_sock, sizeof(server_sock));
	server_sock.sin_family = AF_INET;
	//server_sock.sin_addr.s_addr = htonl(INADDR_ANY);
	inet_pton(AF_INET, _SERVER_IP_, &server_sock.sin_addr); //inet_addr("127.0.0.1");
	server_sock.sin_port = htons(_PROT_);
	char str[IP_LEN];
	inet_ntop(AF_INET, &server_sock.sin_addr, str, sizeof(str));
	printf("udp server IP_addr: %s at PORT %d\n",str,ntohs(server_sock.sin_port));
	
	//调用bind函数绑定socket和地址
	if (bind(sock_fd, (struct sockaddr *)&server_sock, sizeof(struct sockaddr)) == -1)
	{
		perror("bind is error\r\n");
		exit(1);
	}
	
	//printf("start select\r\n");
	fd_set fds;
	FD_ZERO(&fds);	

	while(true)
	{		
		printf("select wait...\n");
		FD_SET(sock_fd,&fds);
        int ret = select(sock_fd + 1, &fds, NULL, NULL, NULL);
		if(ret<=0)
		{
			//超时等
			printf("[DISCOVERY]ret:%d\n", ret);
			continue;
		}
		//printf("select\n");
		if(FD_ISSET(sock_fd,&fds))
		{
			//客户端地址信息
			struct sockaddr_in repaddr;
			memset(recvbuf,0,sizeof(recvbuf));
			memset(&repaddr, 0, sizeof(struct sockaddr));
			int res = recvfrom(sock_fd, recvbuf, sizeof(recvbuf)-1, 0, (struct sockaddr *)&repaddr, (socklen_t *)&sin_size);
			//"exit"特殊字符串,表示客户端申请断开链接
			if(strlen(recvbuf)==4 && memcmp(recvbuf,"exit",5)==0)
			{
				printf("remote client close,msg is %s\n",recvbuf);
				sendto(sock_fd, "exit", strlen("exit") + 1, 0, (struct sockaddr *)&repaddr, (socklen_t)sin_size);
			}
			else
			{
				printf("recv msg :%s\n",recvbuf);
				if (sendto(sock_fd, udpSendBuf, strlen(udpSendBuf) + 1, 0, (struct sockaddr *)&repaddr, (socklen_t)sin_size) == -1)
				{
					perror("send error \r\n");
				}
				else{ printf("send msg %s \n",udpSendBuf);}
			}
			osDelay(10);
			FD_ZERO(&fds);
		}
	}	
	close(sock_fd);
	FD_CLR(sock_fd,&fds);
	sock_fd=-1;
	return ;
}

编译调试

修改对接热点的账号密码

修改udp_server.c第34行和35行的WiFi热点SSID和密码,改成自己环境中的WiFi热点。

// 默认WiFi名和密码
#define WIFI_SSID "aaa"
#define WIFI_PASSWORD "talkweb1996"

修改服务端的ip地址和端口号

修改udp_server.c第28行和30行的端口号和ip地址,改成自己链接上wifi后的ip地址,此处应该从wifi的sdk中获取本身的ip地址,但还未找到相应api。

// 默认服务端的ip地址和端口号
#define _PROT_ 8800
#define _SERVER_IP_ "192.168.1.114"

修改 BUILD.gn 文件

修改 applications/app/BUILD.gn 路径中的 BUILD.gn 文件,指定 network_udpserver_demo 参与编译。

        # "TW303_Network_mqttclient:network_mqttclient_example",
        # "TW402_APP_oled_u8g2:app_oled_u8g2_example",
        # "TW304_Network_tcpserver:network_tcpserver_demo",
        # "TW304_Network_tcpclient:network_tcpclient_demo",
        # "TW305_Network_udpclient:network_udpclient_demo",
		 "TW305_Network_udpserver:network_udpserver_demo",

运行结果

示例代码编译烧录代码后,按下开发板的RESET按键,通过串口助手查看日志,会打印连接到的Wifi热点信息,以及开启udp服务端,等待客户端连接

链接wifi成功,并打印IP信息

callback function for wifi connect

WaitConnectResult:wait success[1]s
WiFi connect succeed!
begain to dhcp<-- DHCP state:Inprogress -->

<-- DHCP state:OK -->
server :
	server_id : 192.168.1.1
	mask : 255.255.255.0, 1
	gw : 192.168.1.1
	T0 : 7200
	T1 : 3600
	T2 : 6300
clients <1> :
	mac_idx mac             addr            state   lease   tries   rto     
	0       849dc22100d4    192.168.1.112   10      0       1       4     

开启tcp服务端,并进行监听等待

tcp server IP_addr: 192.168.1.112 at PORT 8800
select wait...

有客户端接入并进行通信

recv msg :0000
send msg Hello! I'm Talkweb UDP Server! 

select wait...

recv msg :1111
send msg Hello! I'm Talkweb UDP Server! 

select wait...

recv msg :2222
send msg Hello! I'm Talkweb UDP Server! 

select wait...

remote client close,msg is exit

select wait...
1
https://gitee.com/talkweb_oh/niobe.git
git@gitee.com:talkweb_oh/niobe.git
talkweb_oh
niobe
Niobe
master

搜索帮助