第13章 RL-TCPnet之TCP服务器
本章节为大家讲解RL-TCPnet的TCP服务器实现,学习本章节前,务必要优先学习第12章TCP传输控制协议基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
本章教程含STM32F407开发板和STM32F429开发板。
13.1 初学者重要提示
13.2 TCP服务器API函数
13.3 TCP配置说明(Net_Config.c)
13.4 TCP调试说明(Net_Debug.c)
13.5 TCP服务器的实现方法
13.6 网络调试助手和板子的调试操作步骤
13.7 实验例程说明(裸机)
13.8 实验例程说明(RTX)
13.9 总结
13.1 初学者重要提示
1、学习本章节前,务必保证已经学习了第12章的基础知识。
2、本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取达到熟练使用。
3、socket和监听的关系:
(1)创建的一个socket只能创建一个监听。
(2)创建的一个socket不能够监听多个 。
(3)创建多个socket可以创建多个监听。
(4)创建多个socket可以仅创建一个监听。
13.2 TCP服务器API函数
使用如下12个函数可以实现RL-TCPnet的TCP通信:
(1) tcp_get_socket
(2) tcp_connect
(3) tcp_listen
(4) tcp_close
(5) tcp_abort
(6)tcp_release_socket
(7) tcp_get_buf
(8) tcp_max_dsize
(9) tcp_send
(10) tcp_get_state
(11) tcp_check_send
(12) tcp_reset_window
关于这12个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:
这里我们重点的说以下 7个函数,因为本章节配套的例子使用的是这 7 个函数:
(1)tcp_get_socket
(2)tcp_listen
(3)tcp_check_send
(4)tcp_max_dsize
(5)tcp_get_buf
(6)tcp_send
(7)tcp_get_state
关于这些函数注意以下两点:
1、 这些函数都不支持重入,也就是不支持多任务调用。
2、 TCP接口函数通过TCP Socket做数据传输,主要用于数据安全作为首选的场合。TCP Socket发送完数据后会等待应答,任何数据包失败都会重传。
13.2.1 函数tcp_get_socket
函数原型:
U8 tcp_get_socket ( U8 type, /* TCP Socket类型 */ U8 tos, /* TCP服务类型 */ U16 tout, /* 断开连接前的空闲溢出时间 */ U16 (*listener)( /* 回调函数 */ U8 socket, /* Socket句柄 */ U8 event, /* TCP事件 */ U8* ptr, /* 记录接收到的数据或者远程机器的IP地址 */ U16 par )); /* 记录接收到的数据长度或者远程机器的端口号 */
函数描述:
函数tcp_get_socket用于获取一个TCP Socket。
(1)第1个参数是TCP Socket的类型。
(3)第3个参数用于设置空闲溢出时间,单位秒。Keep alive定时器用于监控TCP连接,如果连接的空闲时间(也就是长时间没有数据通信)超出了,那么会断开连接。如果设置了TCP_TYPE_KEEP_ALIVE属性,会通过发送keep alive数据包来保持连接。
(4)第4个参数是回调函数,用于事件监听。
a. 回调函数第1个参数,TCP Socket的句柄,也就是函数tcp_get_socket的返回值。
b. 回调函数第2个参数,事件类型。
c. 回调函数第3个参数,事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址。
d. 回调函数第4个参数,记录接收到的数据个数,其余事件记录端口号。
(5)返回值,如果获取成功,返回TCP Socket句柄,如果获取失败,返回0。
使用这个函数要注意以下问题:
- 调用TCP Socket任何其它函数前,务必要调用此函数tcp_get_socket。
- 使用函数tcp_get_socket,第四个参数的监听回调函数务必要设置。
- 如果需要长时间连接,需要设置属性TCP_TYPE_KEEP_ALIVE。
使用举例:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* 省略 */ } }
13.2.2 函数tcp_listen
函数原型:
BOOL tcp_listen ( U8 socket, /* TCP socket 句柄 */ U16 locport ); /* 监听的端口号 */
函数描述:
函数tcp_listen用于设置TCP服务器的监听端口。
(1)第1个参数是要设置监听的TCP Socket句柄。
(2)第2个参数是监听端口号。
(3)返回值,创建监听成功返回__TRUE,创建失败返回__FALSE。
使用这个函数要注意以下问题:
- RL-TCPnet服务器类应用,比如Telnet Server,HTTP Server,务必要打开一个TCP Socket用于监听。
- 只有创建的TCP服务器才可以使用此函数,也就是调用函数tcp_get_socket的第一个形参必须得是TCP_TYPE_SERVER 或者 TCP_TYPE_CLIENT_SERVER。
使用举例:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* 省略 */ } }
13.2.3 函数tcp_check_send
函数原型:
BOOL tcp_check_send ( U8 socket ); /* TCP socket 句柄 */
函数描述:
函数tcp_check_send用于检测是否可以发送数据。此函数通过检测TCP连接是否建立以及上次发送的数据是否接收到远程机器的应答来判断是否可以发送数据。
(1)第1个参数是TCP Socket句柄。
(2)返回值,可以发送数据,返回__TRUE;不可以发送数据,返回__FALSE。
使用举例:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.2.4 函数tcp_max_dsize
函数原型:
U16 tcp_max_dsize ( U8 socket ); /* TCP socket 句柄 */
函数描述:
函数tcp_max_dsize用于获得当前可以发送的最大报文长度(MSS,Maximum Segment Size)。在配置向导中,默认配置的MSS是1460字节,然而在实际建立连接后,此值会被动态调整,但一定是小于等于1460字节的。
(1)第1个参数是TCP Socket句柄。
(2)返回值,返回本次可以发送的最大报文长度,单位字节。
使用举例:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.2.5 函数tcp_get_buf
函数原型:
U8* tcp_get_buf ( U16 size ); /* 申请的缓冲区大小 */
函数描述:
函数tcp_get_buf用于获取TCP发送缓冲区,用户将要发送的数据存到这个缓冲区中,然后通过函数tcp_send发送。发送完毕后要等待远程主机的应答,收到应答后,会在函数tcp_send中释放申请的发送缓冲区。
(1)第1个参数是要申请的缓冲区大小。
(2)返回值,返回获取的缓冲区地址。
使用这个函数要注意以下问题:
- 每次发送都需要调用此函数获取发送缓冲区地址。
- 申请的发送缓冲区大小不可超过最大报文长度(MSS,Maximum Segment Size),即1460字节。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小。
使用举例:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.2.6 函数tcp_send
函数原型:
BOOL tcp_send ( U8 socket, /* TCP socket 句柄 */ U8* buf, /* 数据缓冲区地址 */ U16 dlen ); /* 要发送的数据个数,单位字节 */
函数描述:
函数tcp_send用于数据包发送。
(1)第1个参数是TCP Socket句柄。
(2)第2个参数是函数tcp_get_buf获取的缓冲区地址。
(3)第3个参数是发送数据个数,单位字节。
(4)返回值,发送成功返回__TRUE,发送失败返回__FALSE。
使用这个函数要注意以下问题:
- 不管函数tcp_send发送成功还是失败,都会释放通过函数tcp_get_buf获取的缓冲区。
- 以下两种情况不可使用函数tcp_send发送数据包:
(1) TCP连接还未建立。
(2) 发送给远程机器的数据包还未收到应答。
- 调用函数tcp_send前务必要调用函数tcp_get_buf获得缓冲区。
- 申请的发送缓冲区大小不可超过最大报文长度(MSS,Maximum Segment Size),即1460字节。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小。
使用举例:
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.2.7 函数tcp_get_state
函数原型:
U8 tcp_get_state ( U8 socket ); /* TCP socket 句柄 */
函数描述:
函数tcp_get_state用于获取TCP Socket的当前状态。用户应用程序可以通过此函数监控TCP Socket的连接、断开等状态。最有用的状态值是TCP_STATE_CLOSED,TCP_STATE_LISTEN和TCP_STATE_CONNECT。
(1)第1个参数是TCP Socket句柄。
(2)返回值,返回以下几种状态值:
使用举例:
/* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); }
13.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数量。
(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接收窗口大小。
13.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章讲解的调试方法进行测试。
13.5 TCP服务器的实现方法
有了本章节13.3小节的配置后,剩下的问题就是TCP服务器的创建和TCP服务器数据收发的实现。
13.5.1 创建TCP服务器
TCP服务器的创建比较简单,调用函数tcp_get_socket即可,此函数的使用和注意事项在本章的13.2.1小节有讲解:
/* ********************************************************************************************************* * 宏定义 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { uint8_t res; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } /* 省略 */ }
13.5.2 TCP数据发送
TCP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在本章节的13.2小节有讲解。下面的代码中对数据发送专门做了处理,支持任意字节大小的数据发送,仅需修改计数变量iCount的初始值即可,初始值是多少,就是发送多少字节。下面的代码是裸机方式的,测试发送8字节,1024字节和5MB:
/* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); //--------------(1) } main_TcpNet (); //--------------(2) } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; uint8_t ucKeyCode; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if ((ucKeyCode != KEY_NONE)&&(tcp_status == __TRUE)) { switch (ucKeyCode) { /* K1键按下,给远程TCP客户端发送8字节数据 */ case KEY_DOWN_K1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; //--------------(3) do //--------------(4) { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY_DOWN_K2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; //--------------(5) do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* K3键按下,给远程TCP客户端发送5MB数据 */ case KEY_DOWN_K3: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = **; //--------------(6) do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
- 函数timer_tick用于实现网络时间基准,必须要周期性调用,周期大小是由配置向导文件中参数Tick Timer interval决定的。默认情况下,我们都取100ms。
- 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
- 通过变量iCount设置要发送的字节数,这里是发送8字节数据。
- do while语句中的流程很重要:
(1) 函数tcp_poll一定要实时调用着。
(2) 发送前务必要调用函数tcp_check_send查看发送是否就绪。
(3) 函数tcp_max_dsize,tcp_get_buf和tcp_send务必要依次调用,一个都不能少。
- 通过变量iCount设置要发送的字节数,这里是发送1024字节数据。
- 通过变量iCount设置要发送的字节数,这里是发送5MB数据。
说完了裸机方式,下面说说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的具体实现如下:
/* ********************************************************************************************************* * 宏定义 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; OS_RESULT xResult; const uint16_t usMaxBlockTime = ; /* 延迟周期 */ /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); //--------------(1) /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) //------(2) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; //--------------(3) do { main_TcpNet();//--------------(4) if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY2_BIT1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; //--------------(5) do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */ case KEY3_BIT2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = **; //--------------(6) do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
- 函数main_TcpNet必须要一直调用着,协议栈的执行,主要靠它。
- 这里使用了事件标志组,溢出时间设置为2毫秒。这样一方面保证了函数main_TcpNet的周期性执行,另一方面用来等待按键发送事件标志消息。
- 通过变量iCount设置要发送的字节数,这里是发送8字节数据。
- do while语句中的流程很重要:
(1) 函数main_TcpNet一定要实时调用着。
(2) 发送前务必要调用函数tcp_check_send查看发送是否就绪。
(3) 函数tcp_max_dsize,tcp_get_buf和tcp_send务必要依次调用,一个都不能少。
- 通过变量iCount设置要发送的字节数,这里是发送1024字节数据。
- 通过变量iCount设置要发送的字节数,这里是发送5MB数据。
13.5.3 TCP数据接收
TCP数据接收主要是通过函数tcp_get_socket的回调函数实现(裸机,RTX,uCOS-III和FreeRTOS是一样的):
/* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: //--------------(1) printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); }
- TCP服务器的数据接收主要是通过回调函数的TCP_EVT_DATA消息实现,进入消息后,指针变量ptr是接收数据缓冲区首地址,变量par记录接收到的数据长度,单位字节。
13.6 网络调试助手和板子的调试操作步骤
我们这里使用下面这款调试助手,任何其它网络调试助手均可,不限制:http://bbs.armfly.com/read.php?tid=1568 。
13.6.1 获取板子IP地址
首先,强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址,而且在前面的配置中使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获取板子的IP地址。测试方法如下:
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)弹出的命令窗口中,输入ping armfly。
(3)输入ping armfly后,回车。
获得IP地址是192.168.1.5。
13.6.2 网络调试助手创建TCP客户端
1、打开调试助手,点击左上角创建连接:
2、弹出如下界面,类型选择TCP,目标IP设置为192.168.1.5,端口号1001,最后点击创建:
特别说明,我们这里直接填局域网域名armfly也是没有问题的,即下面这样:
3、创建后的界面效果如下:
4、点击连接,连接后的界面效果如下:
连接上后,串口软件也会打印出如下信息(波特率115200,数据位8,奇偶校验位无,停止位1):
13.6.3 TCP服务器发送数据
板子和网络调试助手建立连接后就可以相互收发数据了。对于发送数据。程序中创建了三种大小的数据发送测试。
(1)K1按键按下,发送了8个字符,从1到8。
(2)K2按键按下,发送1024字节,每次发送数据包的前8个字节设置了字符a到字符h,后面未做设置。
(3)K3按键按下,发送5*1024*1024 = 5242880字节,即5MB。每次发送数据包的前8个字节设置了字符a到字符h,后面都未做设置。
13.6.4 TCP服务器接收数据
TCP服务器接收数据的测试也比较方便,我们这里通过网络调试助手给板子发送0到9,共10个字符:
点击发送后,可以看到串口软件打印出接收到的10个字符:
字符0对应的ASCII值就是48,其它字符数值依次增加。测试也是没问题的。
13.7 实验例程说明(裸机)
13.7.1 STM32F407开发板实验
配套例子:
V5-1008_RL-TCPnet实验_TCP服务器(裸机)
实验目的:
- 学习RL-TCPnet的TCP服务器创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 创建了一个TCP Server,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,端口号1001。
- 用户可以在电脑端用网络调试软件创建TCP Client连接此服务器端。
- 按键K1按下,发送8字节的数据给TCP Client。
- 按键K2按下,发送1024字节的数据给TCP Client。
- 按键K3按下,发送5MB字节的数据给TCP Client。
实验操作:
详见本章节13.6小节。
配置向导文件设置(Net_Config.c):
详见本章节13.3小节。
调试文件设置(Net_Debug.c):
详见本章节13.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(); /* 初始化滴答定时器 */ }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); } /* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t ulCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; uint8_t ucKeyCode; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if ((ucKeyCode != KEY_NONE)&&(tcp_status == __TRUE)) { switch (ucKeyCode) { /* K1键按下,给远程TCP客户端发送8字节数据 */ case KEY_DOWN_K1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); ulCount = ; do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); ulCount -= maxlen; if(ulCount < ) { /* 这么计算没问题的 */ maxlen = ulCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(ulCount > ); break; /* K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY_DOWN_K2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); ulCount = ; do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); ulCount -= maxlen; if(ulCount < ) { /* 这么计算没问题的 */ maxlen = ulCount + 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_tcp, sendbuf, maxlen); } }while(ulCount > ); break; /* K3键按下,给远程TCP客户端发送5MB数据 */ case KEY_DOWN_K3: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); ulCount = **; do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); ulCount -= maxlen; if(ulCount < ) { /* 这么计算没问题的 */ maxlen = ulCount + 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_tcp, sendbuf, maxlen); } }while(ulCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.7.2 STM32F429开发板实验
配套例子:
V6-1008_RL-TCPnet实验_TCP服务器(裸机)
实验目的:
- 学习RL-TCPnet的TCP服务器创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 创建了一个TCP Server,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,端口号1001。
- 用户可以在电脑端用网络调试软件创建TCP Client连接此服务器端。
- 按键K1按下,发送8字节的数据给TCP Client。
- 按键K2按下,发送1024字节的数据给TCP Client。
- 按键K3按下,发送5MB字节的数据给TCP Client。
实验操作:
详见本章节13.6小节。
配置向导文件设置(Net_Config.c):
详见本章节13.3小节。
调试文件设置(Net_Debug.c):
详见本章节13.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(); /* 初始化滴答定时器 */ }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。
/* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); } /* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t ulCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; uint8_t ucKeyCode; /* 初始化网络协议栈 */ init_TcpNet (); /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ ucKeyCode = bsp_GetKey(); if ((ucKeyCode != KEY_NONE)&&(tcp_status == __TRUE)) { switch (ucKeyCode) { /* K1键按下,给远程TCP客户端发送8字节数据 */ case KEY_DOWN_K1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); ulCount = ; do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); ulCount -= maxlen; if(ulCount < ) { /* 这么计算没问题的 */ maxlen = ulCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(ulCount > ); break; /* K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY_DOWN_K2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); ulCount = ; do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); ulCount -= maxlen; if(ulCount < ) { /* 这么计算没问题的 */ maxlen = ulCount + 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_tcp, sendbuf, maxlen); } }while(ulCount > ); break; /* K3键按下,给远程TCP客户端发送5MB数据 */ case KEY_DOWN_K3: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); ulCount = **; do { tcpnet_poll(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); ulCount -= maxlen; if(ulCount < ) { /* 这么计算没问题的 */ maxlen = ulCount + 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_tcp, sendbuf, maxlen); } }while(ulCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.8 实验例程说明(RTX)
13.8.1 STM32F407开发板实验
配套例子:
V5-1009_RL-TCPnet实验_TCP服务器(RTX)
实验目的:
- 学习RL-TCPnet的TCP服务器创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 创建了一个TCP Server,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,端口号1001。
- 用户可以在电脑端用网络调试软件创建TCP Client连接此服务器端。
- 按键K1按下,发送8字节的数据给TCP Client。
- 按键K2按下,发送1024字节的数据给TCP Client。
- 按键K3按下,发送5MB字节的数据给TCP Client。
实验操作:
详见本章节13.6小节。
配置向导文件设置(Net_Config.c):
详见本章节13.3小节。
调试文件设置(Net_Debug.c):
详见本章节13.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; /* 其他的键值不处理 */ 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 (); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; OS_RESULT xResult; const uint16_t usMaxBlockTime = ; /* 延迟周期 */ /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY2_BIT1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */ case KEY3_BIT2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = **; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.8.2 STM32F429开发板实验
配套例子:
V6-1009_RL-TCPnet实验_TCP服务器(RTX)
实验目的:
- 学习RL-TCPnet的TCP服务器创建和数据收发。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 创建了一个TCP Server,而且使能了局域网域名NetBIOS,用户只需在电脑端ping armfly就可以获得板子的IP地址,端口号1001。
- 用户可以在电脑端用网络调试软件创建TCP Client连接此服务器端。
- 按键K1按下,发送8字节的数据给TCP Client。
- 按键K2按下,发送1024字节的数据给TCP Client。
- 按键K3按下,发送5MB字节的数据给TCP Client。
实验操作:
详见本章节13.6小节。
配置向导文件设置(Net_Config.c):
详见本章节13.3小节。
调试文件设置(Net_Debug.c):
详见本章节13.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; /* 其他的键值不处理 */ 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 (); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,主要功能是创建了一个TCP Server。
#include "includes.h" /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #if 1 #define printf_debug printf #else #define printf_debug(...) #endif /* ********************************************************************************************************* * 用于本文件的调试 ********************************************************************************************************* */ #define PORT_NUM 1001 /* TCP服务器监听端口号 */ /* ********************************************************************************************************* * 变量 ********************************************************************************************************* */ uint8_t socket_tcp; /* ********************************************************************************************************* * 函 数 名: tcp_callback * 功能说明: TCP Socket的回调函数 * 形 参: soc TCP Socket类型 * evt 事件类型 * ptr 事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址 * par 事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号 * 返 回 值: ********************************************************************************************************* */ U16 tcp_callback (U8 soc, U8 evt, U8 *ptr, U16 par) { char buf[]; uint16_t i; /* 确保是socket_tcp的回调 */ if (soc != socket_tcp) { 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_debug("IP:%s port:%d\r\n", buf, par); return (); /* 连接终止 */ case TCP_EVT_ABORT: break; /* Socket远程连接已经建立 */ case TCP_EVT_CONNECT: printf_debug("Socket is connected to remote peer\r\n"); break; /* 连接断开 */ case TCP_EVT_CLOSE: printf_debug("Connection has been closed\r\n"); break; /* 发送的数据收到远程设备应答 */ case TCP_EVT_ACK: break; /* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */ case TCP_EVT_DATA: printf_debug("Data length = %d\r\n", par); for(i = ; i < par; i++) { printf_debug("ptr[%d] = %d\r\n", i, ptr[i]); } break; } return (); } /* ********************************************************************************************************* * 函 数 名: TCP_StatusCheck * 功能说明: 检测TCP的连接状态,主要用于网线插拔的判断 * 形 参: 无 * 返 回 值: __TRUE 连接 * __FALSE 断开 ********************************************************************************************************* */ uint8_t TCP_StatusCheck(void) { uint8_t res; switch (tcp_get_state(socket_tcp)) { case TCP_STATE_FREE: case TCP_STATE_CLOSED: res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); break; case TCP_STATE_LISTEN: break; case TCP_STATE_CONNECT: return (__TRUE); default: break; } return (__FALSE); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { int32_t iCount; uint8_t *sendbuf; uint8_t tcp_status; uint16_t maxlen; uint8_t res; OS_RESULT xResult; const uint16_t usMaxBlockTime = ; /* 延迟周期 */ /* 创建TCP Socket并创建监听,客户端连接服务器后,10秒内无数据通信将断开连接。 但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。 */ socket_tcp = tcp_get_socket (TCP_TYPE_SERVER|TCP_TYPE_KEEP_ALIVE, , , tcp_callback); if(socket_tcp != ) { res = tcp_listen (socket_tcp, PORT_NUM); printf_debug("tcp listen res = %d\r\n", res); } while () { /* RL-TCPnet处理函数 */ main_TcpNet(); /* 用于网线插拔的处理 */ tcp_status = TCP_StatusCheck(); /* 按键消息的处理 */ if((os_evt_wait_or(0xFFFF, usMaxBlockTime) == OS_R_EVT)&&(tcp_status == __TRUE)) { xResult = os_evt_get (); switch (xResult) { /* 接收到K1键按下,给远程TCP客户端发送8字节数据 */ case KEY1_BIT0: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); iCount -= maxlen; if(iCount < ) { /* 这么计算没问题的 */ maxlen = iCount + maxlen; } sendbuf = tcp_get_buf(maxlen); sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; sendbuf[] = ''; /* 测试发现只能使用获取的内存 */ tcp_send (socket_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 接收到K2键按下,给远程TCP客户端发送1024字节的数据 */ case KEY2_BIT1: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = ; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 接收到K3键按下,给远程TCP客户端发送5MB数据 */ case KEY3_BIT2: printf_debug("tcp_get_state(socket_tcp) = %d\r\n", tcp_get_state(socket_tcp)); iCount = **; do { main_TcpNet(); if (tcp_check_send (socket_tcp) == __TRUE) { maxlen = tcp_max_dsize (socket_tcp); 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_tcp, sendbuf, maxlen); } }while(iCount > ); break; /* 其他的键值不处理 */ default: break; } } } }
13.9 总结
本章节就为大家讲解这么多,希望大家多做测试,争取可以熟练掌握这些API函数的使用。