前言
本文研究如何使用UDP进行数据通信,模块连上路由后,绑定2000端口,监听数据,收到数据后,原路发送回去。
一、 理论基础
1.理解UDP
UDP工作原理可以用信件来说明,寄信之前需要在信封上写上寄信人和收信人的地址,之后贴上邮票放进信箱即可,使用信件方式,我们无法确认对方是否收到,寄信过程中也可能发生丢失信件情况,总而言之,信件是一种不可靠的传输方式,UDP也是类似,提供不可靠的通信服务。
UDP和TCP的区别?
(1)TCP是基于连接的服务,UDP是基于无连接的服务
(2)UDP程序结构较简单
(3)TCP对系统资源要求较高,UDP较小
(4)TCP是基于流模式,UDP基于数据包模式,是两者间最大区别
(5)TCP保证数据正确性,UDP可能丢包
(6)TCP保证数据顺序,UDP不保证
2.要点说明
模块上电首先连接路由器,连接相关的操作和前文完全一致,需要做的就是在连接路由器之后处理UDP通信,UDP通信代码中常用的也是最核心的是sendto和recvfrom两个函数。
int sendto(int s,const void * dataptr,size_t size,int flags,const struct sockaddr * to,socklen_t tolen )
- 参数
- s套接字描述符
- dataptr发送的数据指针
- size发送的数据长度
- flags标志,一般为0
- to目标地址结构体指针
- tolen目标地址结构体长度
- 返回
- 大于0 成功,返回发送的数据的长度;小于等于0 失败。
int recvfrom(int s,void * mem,size_t len,int flags,struct sockaddr * from,socklen_t * fromlen )
- 参数
- s套接字描述符mem接收的数据指针len接收的数据长度
- flags标志,一般为0
- from接收地址结构体指针
- fromlen接收地址结构体长度
- 返回
- 大于0 成功,返回接收的数据的长度;等于0 接收地址已传输完并关闭连接;小于0 失败。
除了这两个函数还有一个bind函数也很重要,这里将重点说下bind函数,UDP程序中调用sendto函数传输数据前应完成对套接字的地址分配工作,而bind函数的作用就在此,bind在TCP程序中也出现过,但是bind函数不区分TCP和UDP,都可以使用。另外如果调用sendto函数时候发现尚未分配地址信息,则在首次调用sendto函数时候给响应套接字自动分配IP和端口,因此,UDP客户端程序中通常无需额外的地址分配过程。
二、使用实例
1.程序
/* * Copyright (c) 2019 Winner Microelectronics Co., Ltd. * * SPDX-License-Identifier: Apache-2.0 * * Change Logs: * Date Author Notes * 2019-02-13 tyx first implementation */#include #include #include //使用BSD socket需要包含此头文件#define UDP_LOCAL_PORT 2000static rt_sem_t wait_sem = NULL;char *send_data = "hello UDP object!";static void wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter){ rt_kprintf("%s", __FUNCTION__); rt_sem_release(wait_sem);}static void wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter){ rt_kprintf("%s", __FUNCTION__); if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) { rt_kprintf("ssid : %s", ((struct rt_wlan_info *)buff->data)->ssid.val); }}static void wifi_connect_fail_callback(int event, struct rt_wlan_buff *buff, void *parameter){ rt_kprintf("%s", __FUNCTION__); if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) { rt_kprintf("ssid : %s", ((struct rt_wlan_info *)buff->data)->ssid.val); }}static void udp_thread_entry(void *args){ int ret = 0; int fd = -1; struct sockaddr_in addr, remote_addr; socklen_t addrLen = sizeof(addr); struct timeval t; fd_set readfds; char buf[512] = { 0x00 }; int len = 0;reconnect: fd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == fd) { rt_kprintf("create socket error!!!"); goto exit; } addr.sin_family = AF_INET; addr.sin_port = htons(UDP_LOCAL_PORT); addr.sin_addr.s_addr = INADDR_ANY; rt_memset(&addr.sin_zero, 0x00, sizeof(addr.sin_zero)); ret = bind( fd, (struct sockaddr *)&addr, sizeof(addr)); if (0 != ret) { rt_kprintf("bind addr error!!!"); } while (1) { len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr *)&addr, &addrLen); if (len > 0) { buf[len] = 0x00; rt_kprintf("receive data:%s, from %s:%d", buf, inet_ntoa(addr.sin_addr), addr.sin_port); sendto(fd, buf, len, 0, (struct sockaddr *)&addr, sizeof(struct sockaddr_in) ); }else { rt_kprintf("receive data from tcp server error!"); goto label_try_reconnect; } }label_try_reconnect: if (-1 != fd) { closesocket(fd); } rt_thread_mdelay(1000); goto reconnect;exit: if (-1 != fd) { closesocket(fd); } rt_kprintf("thread udp exit!");}int main(void){ rt_err_t ret = RT_EOK; char str[] = "hello world!"; // 创建一个动态信号量,初始值为0 wait_sem = rt_sem_create("sem_conn", 0, RT_IPC_FLAG_FIFO); /* Start automatic connection */ rt_wlan_config_autoreconnect(RT_TRUE); // register event ret = rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED, wifi_connect_callback, RT_NULL); if (0 != ret) { rt_kprintf("register event handler error!"); } ret = rt_wlan_register_event_handler(RT_WLAN_EVT_STA_DISCONNECTED, wifi_disconnect_callback, RT_NULL); if (0 != ret) { rt_kprintf("register event handler error!"); } ret = rt_wlan_register_event_handler(RT_WLAN_EVT_STA_CONNECTED_FAIL, wifi_connect_fail_callback, RT_NULL); if (0 != ret) { rt_kprintf("register event handler error!"); } // connect to router rt_wlan_set_mode(RT_WLAN_DEVICE_STA_NAME, RT_WLAN_STATION); rt_wlan_connect("HUAWEI-6ZCHWJ", "123456789a"); rt_kprintf("start to connect ap ..."); // wait until module connect to ap success ret = rt_sem_take(wait_sem, RT_WAITING_FOREVER); if (0 != ret) { rt_kprintf("wait_sem error!"); } rt_kprintf("connect to AP success!"); //create udp thread rt_thread_t client_thread = rt_thread_create("udp", udp_thread_entry, RT_NULL, 4*1024, 25, 10); if (client_thread != NULL) { rt_thread_startup(client_thread); }else { ret = RT_ERROR; rt_kprintf("create tcp client error!!!"); }exit: rt_sem_delete(wait_sem); return ret;}
2.配置
在applications目录下新建一个文件夹:6-udp_unicast,然后同理需要修改aplications/SConscript脚本。
Import('RTT_ROOT')Import('rtconfig')from building import *cwd = GetCurrentDir()src = Glob('6-udp_unicast/main.c')CPPPATH = [cwd]group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)Return('group')
三、下载运行
在ENV控制台,输入scons命令,在build/Bin目录下生成rtthread_1M.FLS,
烧录运行后,电脑连接模块起来的热点,然后打开电脑网络调试助手,开启一个UDP服务,IP地址是电脑的IP地址,端口为8089(可自行定义),模块启动UDP服务之后,通过网络助手发送hello world,模块收到数据后,返回给网络助手。
网络助手界面如下:

块调试串口信息如下:

四、结语
1.总结:
本节完,实际操作过程中需要注意的地方有如下几点:
(1) 解决hardfault问题

程序运行过程中,偶先hardfault问题,从提示的任务状态信息可以看到tcpip thread使用了100%,发生溢出,由此可知,需要增大tcpip线程堆栈大小。
修改地方在bsp/w60x/rt_config.h文件中,具体位置如下图所示:

将1024增大为4096即可。
本文由小驿原创,欢迎关注,带你一起长知识!
寄语:人的成熟是从认识到自己的不完美开始!