
本文共 7647 字,大约阅读时间需要 25 分钟。
前言
本文研究如何在STA模式下进行TCP Client通信,STA模式是说模块直接连接AP(手机热点或者路由器),进入局域网中和其他无线设备通信,局域网中其他设备作为服务端,WIFI模块作为客户端。
一、理论基础
本节主要要处理的有一个问题:如何利用RT_Thread连接一个已知的AP,连上AP后,TCP Client程序和网络篇1中内容完全一致。
1.模块连接AP
路由器名字:HUAWEI-6ZCHWJ,密码:123456789a:。
rt_wlan_set_mode(RT_WLAN_DEVICE_AP_NAME, RT_WLAN_AP);rt_wlan_start_ap("sand", "12345678");
2.Socket套接字实现TCP Client
为了编写一个可用的TCP Client demo,需要先了解套接字使用流程

3.要点说明
由于本篇和网络篇1很相似,本以为如前言中所说的,直接把模块起softAP,换成连网的就OK了,几分钟的事情,实际代码写起来加上调试的时间大大超出了预期,下面我说下,写STA模式下的TCP Client demo遇到的具体问题。
首先是如文章开始所说的,把模块起来softAP的API换成连接路由器的API,这个核心功能实现起来还是轻松无压力,1-2分钟就搞定了,但是替换了API后,总感觉不是一个实际项目该有的操作,于是开始考虑,是不是需要等到模块连网成功后才创建TCP Client Thread,进行和局域网下服务器的通信,细想来,的确需要,于是开始搜索API,有没有检测到系统网络状态的函数,发现也是在wlan_mgnt.h有个时间管理的接口:
rt_err_t rt_wlan_register_event_handler(rt_wlan_event_t event, rt_wlan_event_handler handler, void *parameter);
有了注册函数,接下里就好办了,在连接路由器之前,首先注册回调函数,有连网成功回调、连网失败回调、断开连接回调,如下:

static void wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter){ rt_kprintf("%s\n", __FUNCTION__); rt_sem_release(wait_sem);}
心里想着做到这一步,应该差不多了吧,于是乎,代码用scons编译后,跑一波,我天,模块跑着跑着突然断线了然后可恶的是它竟然断线后不重连了,于是接着找API,发现有个自动重连的函数没有设置,果断加上,如下:
rt_wlan_config_autoreconnect(RT_TRUE);
由此可见一个稳定的项目,往往是大量测试后优化的结晶。
二、使用实例
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 TCP_SERVER_ADDR "192.168.1.13"#define TCP_SERVER_PORT 8089static rt_sem_t wait_sem = NULL;static void wifi_connect_callback(int event, struct rt_wlan_buff *buff, void *parameter){ rt_kprintf("%s\n", __FUNCTION__); rt_sem_release(wait_sem);}static void wifi_disconnect_callback(int event, struct rt_wlan_buff *buff, void *parameter){ rt_kprintf("%s\n", __FUNCTION__); if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) { rt_kprintf("ssid : %s\r\n", ((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\n", __FUNCTION__); if ((buff != RT_NULL) && (buff->len == sizeof(struct rt_wlan_info))) { rt_kprintf("ssid : %s\r\n", ((struct rt_wlan_info *)buff->data)->ssid.val); }}static void tcp_client_thread_entry(void *args){ int ret = 0; int fd = -1; struct sockaddr_in server_addr; struct timeval t; fd_set readfds; char buf[512] = { 0x00 }; int len = 0;reconnect: fd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == fd) { rt_kprintf("create socket error!!!\r\n"); goto exit; } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(TCP_SERVER_PORT); server_addr.sin_addr.s_addr = inet_addr( TCP_SERVER_ADDR ); rt_memset(&server_addr.sin_zero, 0x00, sizeof(server_addr.sin_zero)); ret = connect(fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr )); if (0 == ret) { rt_kprintf("connect success\r\n"); }else { rt_kprintf("connect error!!!\r\n"); goto label_try_reconnect; } t.tv_sec = 2; t.tv_usec = 0; while (1) { FD_ZERO(&readfds); FD_SET(fd, &readfds); ret = select(fd + 1, &readfds, 0, 0, &t); if (-1 == ret) { rt_kprintf("select() error!\r\n"); goto label_try_reconnect; } // else if(0 == ret) // { // rt_kprintf("select() timeout!\r\n"); // } else if(ret > 0) { if (FD_ISSET(fd, &readfds)); { len = recv(fd, buf, sizeof(buf), 0); if (len > 0) { buf[len] = 0x00; rt_kprintf("receive data:%s\r\n", buf); }else { rt_kprintf("receive data from tcp server error!\r\n"); goto label_try_reconnect; } if (-1 != fd) { rt_sprintf(buf, "%s\r\n", buf); ret = send(fd, buf, strlen(buf), 0); if (ret < 0) { rt_kprintf("send error, closee socket"); 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 tcp_client exit!\r\n");}int main(void){ rt_err_t ret = RT_EOK; char str[] = "hello world!\r\n"; // 创建一个动态信号量,初始值为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!\r\n"); } 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!\r\n"); } 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!\r\n"); } // 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 ...\n"); // wait until module connect to ap success ret = rt_sem_take(wait_sem, RT_WAITING_FOREVER); if (0 != ret) { rt_kprintf("wait_sem error!\r\n"); } rt_kprintf("connect to ap success!\r\n"); //create client rt_thread_t client_thread = rt_thread_create("tcp_client", tcp_client_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目录下新建一个文件夹:5-sta_tcp_client,直接输入scons会提示编译错误,此时需要修改aplications/SConscript脚本。
Import('RTT_ROOT')Import('rtconfig')from building import *cwd = GetCurrentDir()src = Glob('5-sta_tcp_client/main.c')CPPPATH = [cwd]group = DefineGroup('Applications', src, depend = [''], CPPPATH = CPPPATH)Return('group')
三、下载运行
在ENV控制台,输入scons命令,在build/Bin目录下生成rtthread_1M.FLS,
烧录运行后,电脑连接模块起来的热点,然后打开电脑网络调试助手,开启一个TCP Server,IP地址是电脑的IP地址,端口为8089,注意如果填错,网络助手是无法开启TCP Server的,设备连接到TCP Server后,通过网络助手发送hello world,模块收到数据后,会加上"\r\n",然后返回给网络助手。
网络助手界面如下:


四、结语
1.总结:
本节完,实际操作过程中需要注意的地方有如下几点:
(1) 注意电脑连接模块后会得到一个IP地址,可以通过在cmd控制台中输入ipconfig

(2) 延时纠错
TCP Client重连的时候,rt_err_t rt_thread_sleep(rt_tick_t tick)的单位是tick不是秒,本代码已修改为rt_thread_mdelay(1000);
(3) 网络状态
本篇中没有针对网络状态做过多处理,实际项目使用中,需要根据注册的事件回调函数更新网络状态,一般断网的时候应该指示灯熄灭,连接成功的时候常亮,配网和连网过程中闪烁,关于配网在之后的文章中会有;再次提醒下,千万不要在int main(void)之后调用解除注册函数,释放连网前注册的函数,笔者之前不小心犯过这个错误,导致连网后网络状态变化,模块实际未同步给板卡,导致客户MCU控制的板卡还以为是正常在线的。解除注册接口如下:rt_err_t rt_wlan_unregister_event_handler(rt_wlan_event_t event);对于其他资源需要酌情考虑是否需要删除,本篇中的连网等待信号量可以删除,回收系统资源:rt_sem_delete(wait_sem)。
2.资料获取
如您在使用过程中有任何问题,请加QQ群进一步交流。
QQ交流群:906015840 (备注:物联网项目交流)
公众号:物联网客栈,扫码关注,回复w600即可。

发表评论
最新留言
关于作者
