第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的类型。
(2)第2个参数用于指定服务类型,默认取零即可。
(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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
while (1)
{
/* 省略 */
}
}
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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
while (1)
{
/* 省略 */
}
}
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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
while (1)
{
/* 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
", tcp_get_state(socket_tcp));
iCount = 8;
do
{
main_TcpNet();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
while (1)
{
/* 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
", tcp_get_state(socket_tcp));
iCount = 8;
do
{
main_TcpNet();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
while (1)
{
/* 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
", tcp_get_state(socket_tcp));
iCount = 8;
do
{
main_TcpNet();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
while (1)
{
/* 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
", tcp_get_state(socket_tcp));
iCount = 8;
do
{
main_TcpNet();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
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
", 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[50];
uint16_t i;
/* 确保是socket_tcp的回调 */
if (soc != socket_tcp)
{
return (0);
}
switch (evt)
{
/*
远程客户端连接消息
1、数组ptr存储远程设备的IP地址,par中存储端口号。
2、返回数值1允许连接,返回数值0禁止连接。
*/
case TCP_EVT_CONREQ:
sprintf(buf, "远程客户端请求连接IP: %d.%d.%d.%d", ptr[0], ptr[1], ptr[2], ptr[3]);
printf_debug("IP:%s port:%d
", buf, par);
return (1);
/* 连接终止 */
case TCP_EVT_ABORT:
break;
/* Socket远程连接已经建立 */
case TCP_EVT_CONNECT:
printf_debug("Socket is connected to remote peer
");
break;
/* 连接断开 */
case TCP_EVT_CLOSE:
printf_debug("Connection has been closed
");
break;
/* 发送的数据收到远程设备应答 */
case TCP_EVT_ACK:
break;
/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
case TCP_EVT_DATA:
printf_debug("Data length = %d
", par);
for(i = 0; i < par; i++)
{
printf_debug("ptr[%d] = %d
", i, ptr[i]);
}
break;
}
return (0);
}
/*
*********************************************************************************************************
* 函 数 名: 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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
/* 省略 */
}
13.5.2 TCP数据发送
TCP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在本章节的13.2小节有讲解。下面的代码中对数据发送专门做了处理,支持任意字节大小的数据发送,仅需修改计数变量iCount的初始值即可,初始值是多少,就是发送多少字节。下面的代码是裸机方式的,测试发送8字节,1024字节和5MB:
/*
*********************************************************************************************************
* 函 数 名: tcpnet_poll
* 功能说明: 使用TCPnet必须要一直调用的函数
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void tcpnet_poll(void)
{
if(bsp_CheckTimer(0))
{
/* 此函数坚决不可以放在中断里面跑 */
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, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_listen (socket_tcp, PORT_NUM);
printf_debug("tcp listen res = %d
", res);
}
/* 创建一个周期是100ms的软定时器 */
bsp_StartAutoTimer(0, 100);
while (1)
{
/* 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
", tcp_get_state(socket_tcp));
iCount = 8; //--------------(3)
do //--------------(4)
{
tcpnet_poll();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = '1';
sendbuf[1] = '2';
sendbuf[2] = '3';
sendbuf[3] = '4';
sendbuf[4] = '5';
sendbuf[5] = '6';
sendbuf[6] = '7';
sendbuf[7] = '8';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
break;
/* K2键按下,给远程TCP客户端发送1024字节的数据 */
case KEY_DOWN_K2:
printf_debug("tcp_get_state(socket_tcp) = %d
", tcp_get_state(socket_tcp));
iCount = 1024; //--------------(5)
do
{
tcpnet_poll();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
/* 这里仅初始化了每次所发送数据包的前8个字节 */
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = 'a';
sendbuf[1] = 'b';
sendbuf[2] = 'c';
sendbuf[3] = 'd';
sendbuf[4] = 'e';
sendbuf[5] = 'f';
sendbuf[6] = 'g';
sendbuf[7] = 'h';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
break;
/* K3键按下,给远程TCP客户端发送5MB数据 */
case KEY_DOWN_K3:
printf_debug("tcp_get_state(socket_tcp) = %d
", tcp_get_state(socket_tcp));
iCount = 5*1024*1024; //--------------(6)
do
{
tcpnet_poll();
if (tcp_check_send (socket_tcp) == __TRUE)
{
maxlen = tcp_max_dsize (socket_tcp);
iCount -= maxlen;
if(iCount < 0)
{
/* 这么计算没问题的 */
maxlen = iCount + maxlen;
}
/* 这里仅初始化了每次所发送数据包的前8个字节 */
sendbuf = tcp_get_buf(maxlen);
sendbuf[0] = 'a';
sendbuf[1] = 'b';
sendbuf[2] = 'c';
sendbuf[3] = 'd';
sendbuf[4] = 'e';
sendbuf[5] = 'f';
sendbuf[6] = 'g';
sendbuf[7] = 'h';
/* 测试发现只能使用获取的内存 */
tcp_send (socket_tcp, sendbuf, maxlen);
}
}while(iCount > 0);
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 (100);
while(1)
{
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 (1)
{
TCPnetTest();
}
}
函数TCPnetTest的具体实现如下:
/*
*********************************************************************************************************
* 宏定义
*********************************************************************************************************
*/
#define PORT_NUM 1001 /* TCP服务器监听端口号 */
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
/*
*********************************************************************************************************
* 函 数 名: TCPnetTest
* 功能说明: TCPnet应用
* 形 参: 无
* 返 回 值: 无
*********************************************************************************************************
*/
void TCPnetTest(void)
{
int32_t iCount;
uint8_t *s