第15章 RL-TCPnet之创建多个TCP连接
本章节为大家讲解RL-TCPnet的TCP多客户端实现,因为多客户端在实际项目中用到的地方还挺多,所以我们也专门开启一个章节做讲解。另外,学习本章节前,务必要优先学习第14章TCP客户端。学会创建一个TCP客户端了,创建多个客户端是一样的。
本章教程含STM32F407开发板和STM32F429开发板。
15.1 初学者重要提示
15.2 创建多个TCP客户端连接服务器
15.3 TCP配置说明(Net_Config.c)
15.4 TCP调试说明(Net_Debug.c)
15.5 TCP客户端的实现方法
15.6 网络调试助手创建TCP服务器的方法
15.7 实验例程说明(裸机)
15.8 实验例程说明(RTX)
15.9 总结
15.1 初学者重要提示
1、学习本章节前,务必保证已经学习了第12章的TCP基础知识和第14章的TCP客户端。
2、相比前面章节的TCP服务器,TCP客户端的测试要稍麻烦些,例子中默认访问的TCP服务器端IP地址是192.168.1.2,端口号1001。大家测试时要根据自己电脑的实际IP地址设置app_tcpnet_lib.c文件中远程IP和端口。具体测试方法详看本章节的15.6小节。
15.2 创建多个TCP客户端连接服务器
本章节为大家讲解一个多TCP客户端连接服务器的实例,因为实际项目中,这种情况还比较多,所以也作为一期教程进行专门的讲解。
有了上期教程的基础,本期教程也比较好实现,用户仅需多创建几个TCP客户端,并配置Net_Config.c中可以创建的TCP Socket个数即可。本章节配套的例子是创建了三个TCP客户端跟电脑端的服务器连接通信。
15.3 TCP配置说明(Net_Config.c)
(本章节配套例子的配置与本小节的说明相同)
RL-TCPnet的配置工作是通过配置文件Net_Config.c实现。在MDK工程中打开文件Net_Config.c,可以看到下图所示的工程配置向导:
RL-TCPnet要配置的选项非常多,我们这里把几个主要的配置选项简单介绍下。
System Definitions
(1)Local Host Name
局域网域名。
这里起名为armfly,使用局域网域名限制为15个字符。
(2)Memory Pool size
参数范围1536-262144字节。
内存池大小配置,单位字节。另外注意一点,配置向导这里显示的单位是字节,如果看原始定义,MDK会做一个自动的4字节倍数转换,比如我们这里配置的是8192字节,那么原始定义是#define MEM_SIZE 2048,也就是8192/4 = 2048。
(3)Tick Timer interval
可取10,20,25,40,50,100,200,单位ms。
系统滴答时钟间隔,也就是网络协议栈的系统时间基准,默认情况下,取值100ms。
Ethernet Network Interface
以太网接口配置,勾选了此选项就可以配置了,如果没有使能DHCP的话,将使用这里配置的固定IP。
(1)MAC Address
局域网内可以随意配置,只要不跟局域网内其它设备的MAC地址冲突即可。
(2)IP Address
IP地址。
(3)Subnet mask
子网掩码。
(4)Default Gateway
默认网关。
Ethernet Network Interface
以太网接口配置,这个配置里面还有如下两项比较重要的配需要说明。
(1)NetBIOS Name Service
NetBIOS局域网域名服务,这里打上对勾就使能了。这样我们就可以通过前面配置的Local Host Name局域网域名进行访问,而不需要通过IP地址访问了。
(2)Dynaminc Host Configuration
即DHCP,这里打上对勾就使能了。使能了DHCP后,RL-TCPnet就可以从外接的路由器上获得动态IP地址。
UDP Sockets
UDP Sockets配置,打上对勾就使能了此项功能
(1)Number of UDP Sockets
用于配置可创建的UDP Sockets数量。
范围1 – 20。
TCP Sockets
TCP Sockets配置,打上对勾就使能了此项功能
(1)Number of TCP Sockets
用于配置可创建的TCP Sockets数量,由于要创建三个TCP客户端,这里配置为4,可以创建4个Socket。
(2)Number of Retries
范围0-20。
用于配置重试次数,TCP数据传输时,如果在设置的重试时间内得不到应答,算一次重试失败,这里就是配置的最大重试次数。
(3)Retry Timeout in seconds
范围1-10,单位秒。
重试时间。如果发送的数据在重试时间内得不到应答,将重新发送数据。
(4)Default Connect Timeout in seconds
范围1-600,单位秒。
用于配置默认的保持连接时间,即我们常说的Keep Alive时间,如果时间到了将断开连接。常用于HTTP Server,Telnet Server等。
(5)Maximum Segment Size
范围536-1460,单位字节。
MSS定义了TCP数据包能够传输的最大数据分段。
(6)Receive Window Size
范围536-65535,单位字节。
TCP接收窗口大小。
15.4 TCP调试说明(Net_Debug.c)
(重要说明,RL-TCPnet的调试是通过串口打印出来的)
RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:
Print Time Stamp
勾选了此选项的话,打印消息时,前面会附带时间信息。
其它所有的选项
默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以Memory Management Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。
Off:表示关闭此选项的调试功能。
Errors only:表示仅在此选项出错时,将其错误打印出来。
Full debug:表示此选项的全功能调试。
具体测试,我们这里就不做了,大家可以按照第11章讲解的调试方法进行测试。
15.5 TCP客户端的实现方法
有了本章节15.3小节的配置后,剩下的问题就是TCP客户端的创建和TCP客户端数据收发的实现。
15.5.1 创建三个TCP客户端
TCP服务器的创建比较简单,调用函数tcp_get_socket即可(此函数的使用方法和注意事项在上一章的14.2.1小节有讲解),为了更好的管理这三个TCP客户端,专门为每个TCP客户端单独做一个C文件:
app_tcpclient1.c,app_tcpclient2.c和app_tcpclient3.c分别是TCP客户端1,2和3。下面以TCP客户端1为例说明,即app_tcpclient1.c文件:
/* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp1; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: 主要是TCP_EVT_CONREQ事件使用,返回数值1允许连接,返回数值0禁止连接。 ********************************************************************************************************* */ static U16 tcp_callback1 (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp1) { return (); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]); printf_debug1("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug1("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug1("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug1("TCP客户端1接收到数据长度 = %d\r\n", par); for(i = ; i < par; i++) { printf_debug1("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TcpConnect_Client1 * 功能说明: 创建一个TCP Client * 形 参: _remip 要访问的远程IP地址 * _remport 远程端口号 * _localport 本地端口号 * 返 回 值: 无 ********************************************************************************************************* */ void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport) { uint8_t res; /* 创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp1 = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, , , tcp_callback1); if(socket_tcp1 != ) { res = tcp_connect (socket_tcp1, _remip, _remport, _localport); printf_debug1("TCP Socket创建成功res = %d\r\n", res); } }
在这个文件中,专门对TCP客户端的创建进行了封装,这样仅需调用函数TcpConnect_Client1就可以创建TCP客户端了。此函数是在app_tcpnet_lib.c文件中被调用的,通过按下按键K1进行调用:
/* ********************************************************************************************************* * 外部变量和函数调用 ********************************************************************************************************* */ extern uint8_t socket_tcp1; extern void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp2; extern void TcpConnect_Client2(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp3; extern void TcpConnect_Client3(uint8_t *_remip, uint16_t _remport, uint16_t _localport); /* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { bsp_LedToggle(); /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t ucKeyCode; uint8_t TcpState; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,TCP客户端1建立连接 */ case KEY_DOWN_K1: TcpState = tcp_get_state(socket_tcp1); //--------------(1) /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) //--------------(2) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) //--------------(3) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* 其他的键值不处理 */ default: break; } } } }
- 优先检查TCP Socket的状态。
- TCP_STATE_CONNECT的状态定义在RTL.h文件:
/* TCP States */ #define TCP_STATE_FREE 0 /* Entry is free and unused */ #define TCP_STATE_CLOSED 1 /* Entry allocated, socket still closed */ #define TCP_STATE_LISTEN 2 /* Socket waiting for incoming connection */ #define TCP_STATE_SYN_REC 3 /* SYN frame received */ #define TCP_STATE_SYN_SENT 4 /* SYN packet sent to establish a connect. */ #define TCP_STATE_FINW1 5 /* Tcp_close started FIN packet was sent */ #define TCP_STATE_FINW2 6 /* Our FIN ack-ed, waiting for remote FIN */ #define TCP_STATE_CLOSING 7 /* Received FIN independently of our FIN */ #define TCP_STATE_LAST_ACK 8 /* Waiting for last ACK for our FIN */ #define TCP_STATE_TWAIT 9 /* Timed waiting for 2MSL */ #define TCP_STATE_CONNECT 10 /* TCP Connection established */
从上面的定义中可以看出TCP_STATE_CONNECT是定义在了最后一个状态。由于未建立TCP连接的话,函数tcp_get_state返回的数值是255,这11种状态中未定义,所以专门再定义一个状态,表示未创建。数值比TCP_STATE_CONNECT大1,用在我们自己定义的数组ReVal_Table中。
- 未建立连接的话,调用函数TcpConnect_Client1创建连接。
15.5.2 TCP数据发送
TCP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在上一章的14.2小节有讲解。下面的代码中对数据发送专门做了处理,支持任意字节大小的数据发送,仅需修改计数变量iCount的初始值即可,初始值是多少,就发送多少字节。下面的代码是裸机方式的,测试发送4096字节:
/* ********************************************************************************************************* * 外部变量和函数调用 ********************************************************************************************************* */ extern uint8_t socket_tcp1; extern void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp2; extern void TcpConnect_Client2(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp3; extern void TcpConnect_Client3(uint8_t *_remip, uint16_t _remport, uint16_t _localport); /* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { bsp_LedToggle(); /* 此函数坚决不可以放在中断里面跑 */ timer_tick ();//--------------(1) } main_TcpNet ();//--------------(2) } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t ucKeyCode; uint8_t TcpState; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,TCP客户端1建立连接 */ case KEY_DOWN_K1: TcpState = tcp_get_state(socket_tcp1); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* 摇杆上键按下,TCP客户端1向TCP服务器发送4096字节数据 */ case JOY_DOWN_U: TcpState = tcp_get_state(socket_tcp1); printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; //--------------(3) /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do //--------------(4) { tcpnet_poll(); if (tcp_check_send (socket_tcp1) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp1); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp1, sendbuf, maxlen); } }while(iCount > ); } break; /* 其他的键值不处理 */ default: break; } } } }
- 函数timer_tick用于实现网络时间基准,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms。
- 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
- 通过变量iCount设置要发送的字节数,这里是发送4096字节数据。
- do while语句中的流程很重要:
(1) 函数tcp_poll一定要实时调用着。
(2) 发送前务必要调用函数tcp_check_send查看发送是否就绪。
(3) 函数tcp_max_dsize,tcp_get_buf和tcp_send务必要依次调用,一个都不能少。
说完了裸机方式,下面说说RTOS方式的数据发送,这里我们以RTX操作系统为例进行说明(其它的uCOS-III和FreeRTOS的思路是一样的)。RTX操作系统与裸机方式的主要不同是为RL-TCPnet专门配套了两个任务,一个是RL-TCPnet主任务,另一个是网络系统时间基准更新任务。
网络系统时间更新任务:
/* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 创建任务 */ AppTaskCreate(); os_itv_set (); while() { os_itv_wait (); /* RL-TCPnet时间基准更新函数 */ timer_tick (); } }
特别注意,这里的网络时间基准函数timer_tick,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms,所以这里的延迟一定要匹配。
RL-TCPnet主任务,TCP数据收发在这个任务里面实现:
/* ********************************************************************************************************* * 函 数 名: AppTaskTCPMain * 功能说明: RL-TCPnet测试任务 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while () { TCPnetTest(); } }
函数TCPnetTest的具体实现如下:
/* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t TcpState; OS_RESULT xResult; const uint16_t usMaxBlockTime = ; /* 延迟周期 */ while () { /* RL-TCPnet处理函数 */ main_TcpNet();//--------------(1) /* 按键消息的处理 */ if(os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT) //--------------(2) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,TCP客户端1建立连接 */ case KEY1_BIT0: TcpState = tcp_get_state(socket_tcp1); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* 接收到摇杆上键按下,TCP客户端1向TCP服务器发送4096字节数据 */ case JOY_U_BIT3: TcpState = tcp_get_state(socket_tcp1); printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; //--------------(3) /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) //--------------(4) { do //--------------(5) { main_TcpNet(); if (tcp_check_send (socket_tcp1) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp1); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp1, sendbuf, maxlen); } }while(iCount > ); } break; /* 其他的键值不处理 */ default: break; } } } }
- 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
- 这里使用了事件标志组,溢出时间设置为了2毫秒。这样一方面保证了函数main_TcpNet的周期性执行,另一方面用来等待按键发送事件标志消息。
- 通过变量iCount设置要发送的字节数,这里是发送4096字节数据。
- 这里是为了保证已经建立连接了才可以发送数据。
- do while语句中的流程很重要:
(1) 函数main_TcpNet一定要实时调用着。
(2) 发送前务必要调用函数tcp_check_send查看发送是否就绪。
(3) 函数tcp_max_dsize,tcp_get_buf和tcp_send务必要以此调用,一个都不能少。
15.5.3 TCP数据接收
TCP数据接收主要是通过函数tcp_get_socket的回调函数实现(裸机,RTX,uCOS-III和FreeRTOS是一样的),下面以TCP客户端1的数据接收为例进行说明:
/* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: 主要是TCP_EVT_CONREQ事件使用,返回数值1允许连接,返回数值0禁止连接。 ********************************************************************************************************* */ static U16 tcp_callback1 (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp1) { return (); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]); printf_debug1("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug1("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug1("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: //--------------(1) printf_debug1("TCP客户端1接收到数据长度 = %d\r\n", par); for(i = ; i < par; i++) { printf_debug1("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); }
- TCP服务器的数据接收主要是通过回调函数的TCP_EVT_DATA消息实现,进入消息后,指针变量ptr是接收数据缓冲区首地址,变量par记录接收到的数据长度,单位字节。
15.6 网络调试助手和板子的调试操作步骤
我们这里使用下面这款调试助手,任何其它网络调试助手均可,不限制:
http://bbs.armfly.com/read.php?tid=1568 。
重要提示,操作的过程中务必要优先在电脑端创建TCP服务器并开启,然后再操作板子进行连接。因为本章节配套的实例在按键按下后调用函数tcp_connect只进行一次连接,如果在Net_Config.c文件中配置的重连次数范围内无法连接上,就不会再进行连接了,需要再次点击按键进行连接。
15.6.1 获取板子IP地址
(说明,对于TCP客户端实验,这步已经不需要了,不过大家还可以进行测试)
首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址。测试方法如下:
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)弹出的命令窗口中,输入ping armfly。
(3)输入ping armfly后,回车。
获得IP地址是192.168.1.6。
15.6.2 获取电脑的IP地址
获取电脑IP地址的方法很多,可以在网上邻居获取,也可以通过输入命令ipconfig获取,方法跟上面15.6.1小节中的方式一样:
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)弹出的命令窗口中,输入ipconfig。
(3)输入ipconfig后,回车。
获得电脑的IP地址是192.168.1.2。
15.6.3 在程序中配置要访问的远程IP地址和端口
据前面15.6.2小节获取的电脑端IP地址,需要大家配置程序中app_tcpnet_lib.c文件开头的宏定义,其中IP地址填前面获取的192.168.1.2,大家要根据电脑实际的IP地址填写。而端口号,我们这里随意配置一个即可,配置为1001,后面电脑端使用网络调试助手创建TCP服务器时,务必要跟这个端口号统一:
/* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001
15.6.4 网络调试助手创建TCP服务器
1、打开调试助手,点击左上角创建服务器:
2、弹出如下界面,指定IP设置为192.168.1.2,一定要跟15.6.2小节中获得的电脑IP地址一致,端口号1001,最后点击确定:
3、确定后的界面效果如下:
4、然后点击启动服务器:
如果开发板下载了TCP客户端的程序,并且开发板已经上电,按下板子上面的K1按键,可以看到客户端连接已经加入:
跟我们在程序中设置的设置的端口号,即app_tcpnet_lib.c文件开头的宏定义:
#define LocalPort_NUM1 1024 是一致的。IP地址也跟15.6.1小节中获取的IP地址也是一致的。同样的方法,按下K2按键,可以看到TCP客户端2也加入了连接,端口号是1025:
按下K3按键,可以看到TCP客户端3也加入了连接,端口号1026:
三个都连接上后,串口软件也会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):
15.6.5 TCP客户端发送数据
板子和网络调试助手建立连接后就可以互相收发数据了。对于发送数据,三个TCP客户端都可以给服务器发送数据。
(1)摇杆上键按下,TCP客户端1发送4096字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
(2)摇杆左键按下,TCP客户端2发送4096字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
(3)摇杆右键按下,TCP客户端3发送4096字节,每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
15.6.6 TCP客户端接收数据
TCP服务器接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送1到5,共5个字符。
(1)TCP客户端1数据接收测试。
点击发送后,可以看到串口软件打印出接收到的5个字符:
字符1对应的ASCII值就是49,其它字符数值依次增加。测试也是没问题的。
(2)TCP客户端2数据接收测试。
点击发送后,可以看到串口软件打印出接收到的5个字符:
字符1对应的ASCII值就是49,其它字符数值依次增加。测试也是没问题的。
(3)TCP客户端3数据接收测试。
点击发送后,可以看到串口软件打印出接收到的5个字符:
字符1对应的ASCII值就是49,其它字符数值依次增加。测试也是没问题的。
15.7 实验例程说明(裸机)
15.7.1 STM32F407开发板实验
配套例子:
V5-1016_RL-TCPnet实验_多个TCP客户端连接(裸机)
实验目的:
- 学习RL-TCPnet的多个TCP客户端创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 本例程可以创建三个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,三个TCP Client的端口号分别是1024,1025和1026。
- 用户可以在电脑端用网络调试软件创建TCP Server跟这三个客户端建立连接。执行下面5--10步的操作时,优先将电脑端的TCP Server建立起来!
- 按键K1按下,创建TCP客户端1,并跟电脑端的TCP服务器建立连接。
- 按键K2按下,创建TCP客户端2,并跟电脑端的TCP服务器建立连接。
- 按键K3按下,创建TCP客户端3,并跟电脑端的TCP服务器建立连接。
- 摇杆上键按下,TCP客户端1给电脑端的TCP服务器发送4096字节数据。
- 摇杆左键按下,TCP客户端2给电脑端的TCP服务器发送4096字节数据。
- 摇杆右键按下,TCP客户端3给电脑端的TCP服务器发送4096字节数据。
实验操作:
详见本章节15.6小节。
配置向导文件设置(Net_Config.c):
详见本章节15.3小节。
调试文件设置(Net_Debug.c):
详见本章节15.4小节。
程序设计:
主函数初始化
在main.c文件实现:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 进入RL-TCPnet测试函数 */ TCPnetTest(); }
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。 启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。 系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */ bsp_InitLed(); /* 初始LED指示灯端口 */ bsp_InitTimer(); /* 初始化滴答定时器 */ }
三个TCP客户端的实现
三个TCP客户端实现都是一样的,我们这里仅以客户端1为例进行说明:
#include "bsp.h" #include <RTL.h> #include <stdio.h> #include <Net_Config.h> /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug1 printf #else #define printf_debug1(...) #endif /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp1; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: 主要是TCP_EVT_CONREQ事件使用,返回数值1允许连接,返回数值0禁止连接。 ********************************************************************************************************* */ static U16 tcp_callback1 (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp1) { return (); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]); printf_debug1("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug1("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug1("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug1("TCP客户端1接收到数据长度 = %d\r\n", par); for(i = ; i < par; i++) { printf_debug1("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TcpConnect_Client1 * 功能说明: 创建一个TCP Client * 形 参: _remip 要访问的远程IP地址 * _remport 远程端口号 * _localport 本地端口号 * 返 回 值: 无 ********************************************************************************************************* */ void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport) { uint8_t res; /* 创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp1 = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, , , tcp_callback1); if(socket_tcp1 != ) { res = tcp_connect (socket_tcp1, _remip, _remport, _localport); printf_debug1("TCP Socket创建成功res = %d\r\n", res); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试。
#include "bsp.h" #include <RTL.h> #include <stdio.h> #include <Net_Config.h> /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 外部变量和函数调用 ********************************************************************************************************* */ extern uint8_t socket_tcp1; extern void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp2; extern void TcpConnect_Client2(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp3; extern void TcpConnect_Client3(uint8_t *_remip, uint16_t _remport, uint16_t _localport); /* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { bsp_LedToggle(); /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t ucKeyCode; uint8_t TcpState; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,TCP客户端1建立连接 */ case KEY_DOWN_K1: TcpState = tcp_get_state(socket_tcp1); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* K2键按下,TCP客户端2建立连接 */ case KEY_DOWN_K2: TcpState = tcp_get_state(socket_tcp2); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client2(Rem_IP, PORT_NUM, LocalPort_NUM2); } break; /* K3键按下,TCP客户端3建立连接 */ case KEY_DOWN_K3: TcpState = tcp_get_state(socket_tcp3); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client3(Rem_IP, PORT_NUM, LocalPort_NUM3); } break; /* 摇杆上键按下,TCP客户端1向TCP服务器发送4096字节数据 */ case JOY_DOWN_U: TcpState = tcp_get_state(socket_tcp1); printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { tcpnet_poll(); if (tcp_check_send (socket_tcp1) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp1); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp1, sendbuf, maxlen); } }while(iCount > ); } break; /* 摇杆左键按下,TCP客户端2向TCP服务器发送4096字节数据 */ case JOY_DOWN_L: TcpState = tcp_get_state(socket_tcp2); printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { tcpnet_poll(); if (tcp_check_send (socket_tcp2) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp2); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp2, sendbuf, maxlen); } }while(iCount > ); } break; /* 摇杆右键按下,TCP客户端3向TCP服务器发送4096字节数据 */ case JOY_DOWN_R: TcpState = tcp_get_state(socket_tcp3); printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { tcpnet_poll(); if (tcp_check_send (socket_tcp3) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp3); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp3, sendbuf, maxlen); } }while(iCount > ); } break; /* 其他的键值不处理 */ default: break; } } } }
15.7.2 STM32F429开发板实验
配套例子:
V6-1016_RL-TCPnet实验_多个TCP客户端连接(裸机)
实验目的:
- 学习RL-TCPnet的多个TCP客户端创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 本例程可以创建三个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,三个TCP Client的端口号分别是1024,1025和1026。
- 用户可以在电脑端用网络调试软件创建TCP Server跟这三个客户端建立连接。执行下面5--10步的操作时,优先将电脑端的TCP Server建立起来!
- 按键K1按下,创建TCP客户端1,并跟电脑端的TCP服务器建立连接。
- 按键K2按下,创建TCP客户端2,并跟电脑端的TCP服务器建立连接。
- 按键K3按下,创建TCP客户端3,并跟电脑端的TCP服务器建立连接。
- 摇杆上键按下,TCP客户端1给电脑端的TCP服务器发送4096字节数据。
- 摇杆左键按下,TCP客户端2给电脑端的TCP服务器发送4096字节数据。
- 摇杆右键按下,TCP客户端3给电脑端的TCP服务器发送4096字节数据。
实验操作:
详见本章节15.6小节。
配置向导文件设置(Net_Config.c):
详见本章节15.3小节。
调试文件设置(Net_Debug.c):
详见本章节15.4小节。
程序设计:
主函数初始化
在main.c文件实现:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 进入RL-TCPnet测试函数 */ TCPnetTest(); }
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。 启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。 系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */ bsp_InitLed(); /* 初始LED指示灯端口 */ bsp_InitTimer(); /* 初始化滴答定时器 */ }
三个TCP客户端的实现
三个TCP客户端实现都是一样的,我们这里仅以客户端1为例进行说明:
#include "bsp.h" #include <RTL.h> #include <stdio.h> #include <Net_Config.h> /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug1 printf #else #define printf_debug1(...) #endif /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp1; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: 主要是TCP_EVT_CONREQ事件使用,返回数值1允许连接,返回数值0禁止连接。 ********************************************************************************************************* */ static U16 tcp_callback1 (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp1) { return (); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]); printf_debug1("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug1("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug1("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug1("TCP客户端1接收到数据长度 = %d\r\n", par); for(i = ; i < par; i++) { printf_debug1("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TcpConnect_Client1 * 功能说明: 创建一个TCP Client * 形 参: _remip 要访问的远程IP地址 * _remport 远程端口号 * _localport 本地端口号 * 返 回 值: 无 ********************************************************************************************************* */ void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport) { uint8_t res; /* 创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp1 = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, , , tcp_callback1); if(socket_tcp1 != ) { res = tcp_connect (socket_tcp1, _remip, _remport, _localport); printf_debug1("TCP Socket创建成功res = %d\r\n", res); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试。
#include "bsp.h" #include <RTL.h> #include <stdio.h> #include <Net_Config.h> /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 外部变量和函数调用 ********************************************************************************************************* */ extern uint8_t socket_tcp1; extern void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp2; extern void TcpConnect_Client2(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp3; extern void TcpConnect_Client3(uint8_t *_remip, uint16_t _remport, uint16_t _localport); /* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { bsp_LedToggle(); /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t ucKeyCode; uint8_t TcpState; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,TCP客户端1建立连接 */ case KEY_DOWN_K1: TcpState = tcp_get_state(socket_tcp1); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* K2键按下,TCP客户端2建立连接 */ case KEY_DOWN_K2: TcpState = tcp_get_state(socket_tcp2); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client2(Rem_IP, PORT_NUM, LocalPort_NUM2); } break; /* K3键按下,TCP客户端3建立连接 */ case KEY_DOWN_K3: TcpState = tcp_get_state(socket_tcp3); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client3(Rem_IP, PORT_NUM, LocalPort_NUM3); } break; /* 摇杆上键按下,TCP客户端1向TCP服务器发送4096字节数据 */ case JOY_DOWN_U: TcpState = tcp_get_state(socket_tcp1); printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { tcpnet_poll(); if (tcp_check_send (socket_tcp1) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp1); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp1, sendbuf, maxlen); } }while(iCount > ); } break; /* 摇杆左键按下,TCP客户端2向TCP服务器发送4096字节数据 */ case JOY_DOWN_L: TcpState = tcp_get_state(socket_tcp2); printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { tcpnet_poll(); if (tcp_check_send (socket_tcp2) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp2); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp2, sendbuf, maxlen); } }while(iCount > ); } break; /* 摇杆右键按下,TCP客户端3向TCP服务器发送4096字节数据 */ case JOY_DOWN_R: TcpState = tcp_get_state(socket_tcp3); printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { tcpnet_poll(); if (tcp_check_send (socket_tcp3) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp3); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp3, sendbuf, maxlen); } }while(iCount > ); } break; /* 其他的键值不处理 */ default: break; } } } }
15.8 实验例程说明(RTX)
15.8.1 STM32F407开发板实验
配套例子:
V5-1017_RL-TCPnet实验_多个TCP客户端连接(RTX)
实验目的:
- 学习RL-TCPnet的多个TCP客户端创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 本例程可以创建三个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,三个TCP Client的端口号分别是1024,1025和1026。
- 用户可以在电脑端用网络调试软件创建TCP Server跟这三个客户端建立连接。执行下面5--10步的操作时,优先将电脑端的TCP Server建立起来!
- 按键K1按下,创建TCP客户端1,并跟电脑端的TCP服务器建立连接。
- 按键K2按下,创建TCP客户端2,并跟电脑端的TCP服务器建立连接。
- 按键K3按下,创建TCP客户端3,并跟电脑端的TCP服务器建立连接。
- 摇杆上键按下,TCP客户端1给电脑端的TCP服务器发送4096字节数据。
- 摇杆左键按下,TCP客户端2给电脑端的TCP服务器发送4096字节数据。
- 摇杆右键按下,TCP客户端3给电脑端的TCP服务器发送4096字节数据。
实验操作:
详见本章节15.6小节。
配置向导文件设置(Net_Config.c):
详见本章节15.3小节。
调试文件设置(Net_Debug.c):
详见本章节15.4小节。
RTX配置:
RTX配置向导详情如下:
Task Configuration
(1)Number of concurrent running tasks
允许创建6个任务,实际创建了如下5个任务:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :按键检测。
AppTaskTCPMain任务:RL-TCPnet测试任务。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。
(2)Number of tasks with user-provided stack
创建的5个任务都是采用自定义堆栈方式。
(3)Run in privileged mode
设置任务运行在非特权级模式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
static uint64_t AppTaskUserIFStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskLEDStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskMsgProStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */
static uint64_t AppTaskStartStk[1024/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 创建启动任务 */ os_sys_init_user (AppTaskStart, /* 任务函数 */ , /* 任务优先级 */ &AppTaskStartStk, /* 任务栈 */ sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */ while(); }
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。 启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。 系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); bsp_InitDWT(); /* 初始化DWT */ bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */ bsp_InitLed(); /* 初始LED指示灯端口 */ }
RTX任务创建:
/* ********************************************************************************************************* * 函 数 名: AppTaskCreate * 功能说明: 创建应用任务 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void AppTaskCreate (void) { HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */ , /* 任务优先级 */ &AppTaskUserIFStk, /* 任务栈 */ sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */ HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */ , /* 任务优先级 */ &AppTaskLEDStk, /* 任务栈 */ sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */ HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */ , /* 任务优先级 */ &AppTaskMsgProStk, /* 任务栈 */ sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */ HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain, /* 任务函数 */ , /* 任务优先级 */ &AppTaskTCPMainStk, /* 任务栈 */ sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */ }
五个RTX任务的实现:
/* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反) ********************************************************************************************************* */ __task void AppTaskUserIF(void) { uint8_t ucKeyCode; while() { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */ case KEY_DOWN_K1: printf("K1键按下,直接发送事件标志给任务AppTaskTCPMain,bit0被设置\r\n"); os_evt_set (KEY1_BIT0, HandleTaskTCPMain); break; /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */ case KEY_DOWN_K2: printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n"); os_evt_set (KEY2_BIT1, HandleTaskTCPMain); break; /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */ case KEY_DOWN_K3: printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n"); os_evt_set (KEY3_BIT2, HandleTaskTCPMain); break; /* 摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3 */ case JOY_DOWN_U: printf("摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3\r\n"); os_evt_set (JOY_U_BIT3, HandleTaskTCPMain); break; /* 摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit4 */ case JOY_DOWN_L: printf("摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit4\r\n"); os_evt_set (JOY_L_BIT4, HandleTaskTCPMain); break; /* 摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit5 */ case JOY_DOWN_R: printf("摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit5\r\n"); os_evt_set (JOY_R_BIT5, HandleTaskTCPMain); break; /* 其他的键值不处理 */ default: break; } } os_dly_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskLED * 功能说明: LED闪烁。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 2 ********************************************************************************************************* */ __task void AppTaskLED(void) { const uint16_t usFrequency = ; /* 延迟周期 */ /* 设置延迟周期 */ os_itv_set(usFrequency); while() { bsp_LedToggle(); /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/ os_itv_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 按键检测 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 3 ********************************************************************************************************* */ __task void AppTaskMsgPro(void) { while() { bsp_KeyScan(); os_dly_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskTCPMain * 功能说明: RL-TCPnet测试任务 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while () { TCPnetTest(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 创建任务 */ AppTaskCreate(); os_itv_set (); while() { os_itv_wait (); /* RL-TCPnet时间基准更新函数 */ timer_tick (); } }
三个TCP客户端的实现
三个TCP客户端实现都是一样的,我们这里仅以客户端1为例进行说明:
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug1 printf #else #define printf_debug1(...) #endif /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp1; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: 主要是TCP_EVT_CONREQ事件使用,返回数值1允许连接,返回数值0禁止连接。 ********************************************************************************************************* */ static U16 tcp_callback1 (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp1) { return (); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]); printf_debug1("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug1("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug1("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug1("TCP客户端1接收到数据长度 = %d\r\n", par); for(i = ; i < par; i++) { printf_debug1("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TcpConnect_Client1 * 功能说明: 创建一个TCP Client * 形 参: _remip 要访问的远程IP地址 * _remport 远程端口号 * _localport 本地端口号 * 返 回 值: 无 ********************************************************************************************************* */ void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport) { uint8_t res; /* 创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp1 = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, , , tcp_callback1); if(socket_tcp1 != ) { res = tcp_connect (socket_tcp1, _remip, _remport, _localport); printf_debug1("TCP Socket创建成功res = %d\r\n", res); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试。
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 外部变量和函数调用 ********************************************************************************************************* */ extern uint8_t socket_tcp1; extern void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp2; extern void TcpConnect_Client2(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp3; extern void TcpConnect_Client3(uint8_t *_remip, uint16_t _remport, uint16_t _localport); /* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t TcpState; OS_RESULT xResult; const uint16_t usMaxBlockTime = ; /* 延迟周期 */ while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 按键消息的处理 */ if(os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,TCP客户端1建立连接 */ case KEY1_BIT0: TcpState = tcp_get_state(socket_tcp1); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* 接收到K2键按下,TCP客户端2建立连接 */ case KEY2_BIT1: TcpState = tcp_get_state(socket_tcp2); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client2(Rem_IP, PORT_NUM, LocalPort_NUM2); } break; /* 接收到K3键按下,TCP客户端3建立连接 */ case KEY3_BIT2: TcpState = tcp_get_state(socket_tcp3); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client3(Rem_IP, PORT_NUM, LocalPort_NUM3); } break; /* 接收到摇杆上键按下,TCP客户端1向TCP服务器发送4096字节数据 */ case JOY_U_BIT3: TcpState = tcp_get_state(socket_tcp1); printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { main_TcpNet(); if (tcp_check_send (socket_tcp1) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp1); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp1, sendbuf, maxlen); } }while(iCount > ); } break; /* 接收到摇杆左键按下,TCP客户端2向TCP服务器发送4096字节数据 */ case JOY_L_BIT4: TcpState = tcp_get_state(socket_tcp2); printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { main_TcpNet(); if (tcp_check_send (socket_tcp2) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp2); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp2, sendbuf, maxlen); } }while(iCount > ); } break; /* 接收到摇杆右键按下,TCP客户端3向TCP服务器发送4096字节数据 */ case JOY_R_BIT5: TcpState = tcp_get_state(socket_tcp3); printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { main_TcpNet(); if (tcp_check_send (socket_tcp3) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp3); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp3, sendbuf, maxlen); } }while(iCount > ); } break; /* 其他的键值不处理 */ default: break; } } } }
15.8.2 STM32F429开发板实验
配套例子:
V6-1017_RL-TCPnet实验_多个TCP客户端连接(RTX)
实验目的:
- 学习RL-TCPnet的多个TCP客户端创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 客户端的例子相比服务器的例子稍麻烦些,因为客户端的例子需要用户知道电脑端IP和端口号。并根据实际情况设置IP和端口号的宏定义,这个配置在文件app_tcpnet_lib.c开头,测试的时候板子要连接这个IP和端口(下面是默认配置,一定要根据实际情况重新配置,如果不会配置,看本例程对应的教程即可):
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
- 本例程可以创建三个TCP Client,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,三个TCP Client的端口号分别是1024,1025和1026。
- 用户可以在电脑端用网络调试软件创建TCP Server跟这三个客户端建立连接。执行下面5--10步的操作时,优先将电脑端的TCP Server建立起来!
- 按键K1按下,创建TCP客户端1,并跟电脑端的TCP服务器建立连接。
- 按键K2按下,创建TCP客户端2,并跟电脑端的TCP服务器建立连接。
- 按键K3按下,创建TCP客户端3,并跟电脑端的TCP服务器建立连接。
- 摇杆上键按下,TCP客户端1给电脑端的TCP服务器发送4096字节数据。
- 摇杆左键按下,TCP客户端2给电脑端的TCP服务器发送4096字节数据。
- 摇杆右键按下,TCP客户端3给电脑端的TCP服务器发送4096字节数据。
实验操作:
详见本章节15.6小节。
配置向导文件设置(Net_Config.c):
详见本章节15.3小节。
调试文件设置(Net_Debug.c):
详见本章节15.4小节。
RTX配置:
RTX配置向导详情如下:
Task Configuration
(1)Number of concurrent running tasks
允许创建6个任务,实际创建了如下5个任务:
AppTaskUserIF任务 :按键消息处理。
AppTaskLED任务 :LED闪烁。
AppTaskMsgPro任务 :按键检测。
AppTaskTCPMain任务:RL-TCPnet测试任务。
AppTaskStart任务 :启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。
(2)Number of tasks with user-provided stack
创建的5个任务都是采用自定义堆栈方式。
(3)Run in privileged mode
设置任务运行在非特权级模式。
RTX任务调试信息:
程序设计:
任务栈大小分配:
static uint64_t AppTaskUserIFStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskLEDStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskMsgProStk[1024/8]; /* 任务栈 */
static uint64_t AppTaskTCPMainStk[2048/8]; /* 任务栈 */
static uint64_t AppTaskStartStk[1024/8]; /* 任务栈 */
将任务栈定义成uint64_t类型可以保证任务栈是8字节对齐的,8字节对齐的含义就是数组的首地址对8求余等于0。如果不做8字节对齐的话,部分C语言库函数、浮点运算和uint64_t类型数据运算会出问题。
系统栈大小分配:
RTX初始化:
/* ********************************************************************************************************* * 函 数 名: main * 功能说明: 标准c程序入口。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ int main (void) { /* 初始化外设 */ bsp_Init(); /* 创建启动任务 */ os_sys_init_user (AppTaskStart, /* 任务函数 */ , /* 任务优先级 */ &AppTaskStartStk, /* 任务栈 */ sizeof(AppTaskStartStk)); /* 任务栈大小,单位字节数 */ while(); }
硬件外设初始化
硬件外设的初始化是在 bsp.c 文件实现:
/* ********************************************************************************************************* * 函 数 名: bsp_Init * 功能说明: 初始化所有的硬件设备。该函数配置CPU寄存器和外设的寄存器并初始化一些全局变量。只需要调用一次 * 形 参:无 * 返 回 值: 无 ********************************************************************************************************* */ void bsp_Init(void) { /* 由于ST固件库的启动文件已经执行了CPU系统时钟的初始化,所以不必再次重复配置系统时钟。 启动文件配置了CPU主时钟频率、内部Flash访问速度和可选的外部SRAM FSMC初始化。 系统时钟缺省配置为168MHz,如果需要更改,可以修改 system_stm32f4xx.c 文件 */ /* 优先级分组设置为4,可配置0-15级抢占式优先级,0级子优先级,即不存在子优先级。*/ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); SystemCoreClockUpdate(); /* 根据PLL配置更新系统时钟频率变量 SystemCoreClock */ bsp_InitDWT(); /* 初始化DWT */ bsp_InitUart(); /* 初始化串口 */ bsp_InitKey(); /* 初始化按键变量(必须在 bsp_InitTimer() 之前调用) */ bsp_InitExtIO(); /* FMC总线上扩展了32位输出IO, 操作LED等外设必须初始化 */ bsp_InitLed(); /* 初始LED指示灯端口 */ }
RTX任务创建:
/* ********************************************************************************************************* * 函 数 名: AppTaskCreate * 功能说明: 创建应用任务 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ static void AppTaskCreate (void) { HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF, /* 任务函数 */ , /* 任务优先级 */ &AppTaskUserIFStk, /* 任务栈 */ sizeof(AppTaskUserIFStk)); /* 任务栈大小,单位字节数 */ HandleTaskLED = os_tsk_create_user(AppTaskLED, /* 任务函数 */ , /* 任务优先级 */ &AppTaskLEDStk, /* 任务栈 */ sizeof(AppTaskLEDStk)); /* 任务栈大小,单位字节数 */ HandleTaskMsgPro = os_tsk_create_user(AppTaskMsgPro, /* 任务函数 */ , /* 任务优先级 */ &AppTaskMsgProStk, /* 任务栈 */ sizeof(AppTaskMsgProStk)); /* 任务栈大小,单位字节数 */ HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain, /* 任务函数 */ , /* 任务优先级 */ &AppTaskTCPMainStk, /* 任务栈 */ sizeof(AppTaskTCPMainStk)); /* 任务栈大小,单位字节数 */ }
五个RTX任务的实现:
/* ********************************************************************************************************* * 函 数 名: AppTaskUserIF * 功能说明: 按键消息处理 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 1 (数值越小优先级越低,这个跟uCOS相反) ********************************************************************************************************* */ __task void AppTaskUserIF(void) { uint8_t ucKeyCode; while() { ucKeyCode = bsp_GetKey(); if (ucKeyCode != KEY_NONE) { switch (ucKeyCode) { /* K1键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit0 */ case KEY_DOWN_K1: printf("K1键按下,直接发送事件标志给任务AppTaskTCPMain,bit0被设置\r\n"); os_evt_set (KEY1_BIT0, HandleTaskTCPMain); break; /* K2键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit1 */ case KEY_DOWN_K2: printf("K2键按下,直接发送事件标志给任务AppTaskTCPMain,bit1被设置\r\n"); os_evt_set (KEY2_BIT1, HandleTaskTCPMain); break; /* K3键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit2 */ case KEY_DOWN_K3: printf("K3键按下,直接发送事件标志给任务AppTaskTCPMain,bit2被设置\r\n"); os_evt_set (KEY3_BIT2, HandleTaskTCPMain); break; /* 摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3 */ case JOY_DOWN_U: printf("摇杆上键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit3\r\n"); os_evt_set (JOY_U_BIT3, HandleTaskTCPMain); break; /* 摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit4 */ case JOY_DOWN_L: printf("摇杆左键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit4\r\n"); os_evt_set (JOY_L_BIT4, HandleTaskTCPMain); break; /* 摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit5 */ case JOY_DOWN_R: printf("摇杆右键按下,直接发送事件标志给任务AppTaskTCPMain,设置bit5\r\n"); os_evt_set (JOY_R_BIT5, HandleTaskTCPMain); break; /* 其他的键值不处理 */ default: break; } } os_dly_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskLED * 功能说明: LED闪烁。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 2 ********************************************************************************************************* */ __task void AppTaskLED(void) { const uint16_t usFrequency = ; /* 延迟周期 */ /* 设置延迟周期 */ os_itv_set(usFrequency); while() { bsp_LedToggle(); /* os_itv_wait是绝对延迟,os_dly_wait是相对延迟。*/ os_itv_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskMsgPro * 功能说明: 按键检测 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 3 ********************************************************************************************************* */ __task void AppTaskMsgPro(void) { while() { bsp_KeyScan(); os_dly_wait(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskTCPMain * 功能说明: RL-TCPnet测试任务 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 4 ********************************************************************************************************* */ __task void AppTaskTCPMain(void) { while () { TCPnetTest(); } } /* ********************************************************************************************************* * 函 数 名: AppTaskStart * 功能说明: 启动任务,也是最高优先级任务,这里实现RL-TCPnet的时间基准更新。 * 形 参: 无 * 返 回 值: 无 * 优 先 级: 5 ********************************************************************************************************* */ __task void AppTaskStart(void) { /* 初始化RL-TCPnet */ init_TcpNet (); /* 创建任务 */ AppTaskCreate(); os_itv_set (); while() { os_itv_wait (); /* RL-TCPnet时间基准更新函数 */ timer_tick (); } }
三个TCP客户端的实现
三个TCP客户端实现都是一样的,我们这里仅以客户端1为例进行说明:
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug1 printf #else #define printf_debug1(...) #endif /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp1; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: 主要是TCP_EVT_CONREQ事件使用,返回数值1允许连接,返回数值0禁止连接。 ********************************************************************************************************* */ static U16 tcp_callback1 (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp1) { return (); } switch (evt) { /* 远程客户端连接消息 1、数组ptr存储远程设备的IP地址,par中存储端口号。 2、返回数值1允许连接,返回数值0禁止连接。 */ case TCP_EVT_CONREQ: sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[], ptr[], ptr[], ptr[]); printf_debug1("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug1("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug1("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug1("TCP客户端1接收到数据长度 = %d\r\n", par); for(i = ; i < par; i++) { printf_debug1("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TcpConnect_Client1 * 功能说明: 创建一个TCP Client * 形 参: _remip 要访问的远程IP地址 * _remport 远程端口号 * _localport 本地端口号 * 返 回 值: 无 ********************************************************************************************************* */ void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport) { uint8_t res; /* 创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp1 = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, , , tcp_callback1); if(socket_tcp1 != ) { res = tcp_connect (socket_tcp1, _remip, _remport, _localport); printf_debug1("TCP Socket创建成功res = %d\r\n", res); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试。
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 外部变量和函数调用 ********************************************************************************************************* */ extern uint8_t socket_tcp1; extern void TcpConnect_Client1(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp2; extern void TcpConnect_Client2(uint8_t *_remip, uint16_t _remport, uint16_t _localport); extern uint8_t socket_tcp3; extern void TcpConnect_Client3(uint8_t *_remip, uint16_t _remport, uint16_t _localport); /* ********************************************************************************************************* * 宏定义,远程服务器的IP和端口 ********************************************************************************************************* */ /* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */ #define IP1 192 #define IP2 168 #define IP3 1 #define IP4 2 #define PORT_NUM 1001 /* 这个是本地端口 */ #define LocalPort_NUM1 1024 #define LocalPort_NUM2 1025 #define LocalPort_NUM3 1026 /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t Rem_IP[] = {IP1,IP2,IP3,IP4}; /* TCP状态的返回值 */ const char * ReVal_Table[]= { "0: TCP_STATE_FREE", "1: TCP_STATE_CLOSED", "2: TCP_STATE_LISTEN", "3: TCP_STATE_SYN_REC", "4: TCP_STATE_SYN_SENT", "5: TCP_STATE_FINW1", "6: TCP_STATE_FINW2", "7: TCP_STATE_CLOSING", "8: TCP_STATE_LAST_ACK", "9: TCP_STATE_TWAIT", "10: TCP_STATE_CONNECT", "11: 连接还未创建", }; /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint16_t maxlen; uint8_t TcpState; OS_RESULT xResult; const uint16_t usMaxBlockTime = ; /* 延迟周期 */ while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 按键消息的处理 */ if(os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,TCP客户端1建立连接 */ case KEY1_BIT0: TcpState = tcp_get_state(socket_tcp1); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client1(Rem_IP, PORT_NUM, LocalPort_NUM1); } break; /* 接收到K2键按下,TCP客户端2建立连接 */ case KEY2_BIT1: TcpState = tcp_get_state(socket_tcp2); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client2(Rem_IP, PORT_NUM, LocalPort_NUM2); } break; /* 接收到K3键按下,TCP客户端3建立连接 */ case KEY3_BIT2: TcpState = tcp_get_state(socket_tcp3); /* TCP_STATE_CONNECT是最后一个状态,未创建连接的话,会返回数值255,这里就是专门处理 这种状态,将其设置下一个TCP_STATE_CONNECT+1,即我们这里自定义了一个未创建连接的 状态。 */ if(TcpState > TCP_STATE_CONNECT) { TcpState = TCP_STATE_CONNECT + ; } printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); if(TcpState != TCP_STATE_CONNECT) { TcpConnect_Client3(Rem_IP, PORT_NUM, LocalPort_NUM3); } break; /* 接收到摇杆上键按下,TCP客户端1向TCP服务器发送4096字节数据 */ case JOY_U_BIT3: TcpState = tcp_get_state(socket_tcp1); printf_debug("TCP客户端1状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { main_TcpNet(); if (tcp_check_send (socket_tcp1) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp1); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp1, sendbuf, maxlen); } }while(iCount > ); } break; /* 接收到摇杆左键按下,TCP客户端2向TCP服务器发送4096字节数据 */ case JOY_L_BIT4: TcpState = tcp_get_state(socket_tcp2); printf_debug("TCP客户端2状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { main_TcpNet(); if (tcp_check_send (socket_tcp2) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp2); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp2, sendbuf, maxlen); } }while(iCount > ); } break; /* 接收到摇杆右键按下,TCP客户端3向TCP服务器发送4096字节数据 */ case JOY_R_BIT5: TcpState = tcp_get_state(socket_tcp3); printf_debug("TCP客户端3状态 = %s\r\n", ReVal_Table[TcpState]); iCount = ; /* 保证已经建立连接了才可以发送数据 */ if(TcpState == TCP_STATE_CONNECT) { do { main_TcpNet(); if (tcp_check_send (socket_tcp3) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp3); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } /* 这里仅初始化了每次所发送数据包的前8个字节 */ sendbuf = tcp_get_buf(maxlen); sendbuf[] = 'a'; sendbuf[] = 'b'; sendbuf[] = 'c'; sendbuf[] = 'd'; sendbuf[] = 'e'; sendbuf[] = 'f'; sendbuf[] = 'g'; sendbuf[] = 'h'; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp3, sendbuf, maxlen); } }while(iCount > ); } break; /* 其他的键值不处理 */ default: break; } } } }
15.9 总结
本章节就为大家讲解这么多,希望大家多做测试,再多创建几个客户端进行测试。