第14章 RL-TCPnet之TCP客户端
本章节为大家讲解RL-TCPnet的TCP客户端实现,学习本章节前,务必要优先学习第12章TCP传输控制协议基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
本章教程含STM32F407开发板和STM32F429开发板。
14.1 初学者重要提示
14.2 TCP客户端API函数
14.3 TCP配置说明(Net_Config.c)
14.4 TCP调试说明(Net_Debug.c)
14.5 TCP客户端的实现方法
14.6 网络调试助手和板子的调试操作步骤
14.7 实验例程说明(裸机)
14.8 实验例程说明(RTX)
14.9 总结
14.1 初学者重要提示
1、学习本章节前,务必保证已经学习了第12章的基础知识。
2、
相比上一个章节的TCP服务器,TCP客户端的测试要稍麻烦些,例子中默认访问的TCP服务器端IP地址是192.168.1.2,端口号1001。大家测试时要根据自己电脑的实际IP地址设置app_tcpnet_lib.c文件中远程IP和端口。具体测试方法详看本章节的14.6小节。
3、本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取能够熟练使用。
14.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_connect
(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发送完毕数据后会等待应答,任何数据包失败都会重传。
14.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。
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
uint8_t Rem_IP[4] = {IP1,IP2,IP3,IP4};
/*
*********************************************************************************************************
* 函 数 名: 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_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功res = %d
", res);
}
while (1)
{
/* 省略 */
}
}
14.2.2 函数tcp_connect
函数原型:
BOOL tcp_connect (
U8 socket, /* Socket 句柄 */
U8* remip, /* 远程IP地址 */
U16 remport, /* 远程端口号 */
U16 locport ); /* 本地端口号 */
函数描述:
函数tcp_connect用于连接远程TCP服务器。
(1)第1个参数是要设置监听的TCP Socket句柄。
(2)第2个参数是远程服务器IP。
(3)第3个参数是远程服务器端口号。
(4)第4个参数是本地端口号。如果此参数设置为0,RL-TCPnet自动为其分配未使用的TCP端口号。
(5)返回值,成功开启连接返回__TRUE(注意,并不是已经建立了连接),否则返回__FALSE。
使用这个函数要注意以下问题:
- 只有创建的TCP客户端才可以使用此函数,也就是调用函数tcp_get_socket的第一个形参必须得是TCP_TYPE_CLIENT 或者 TCP_TYPE_CLIENT_SERVER。
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
uint8_t Rem_IP[4] = {IP1,IP2,IP3,IP4};
/*
*********************************************************************************************************
* 函 数 名: 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_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功res = %d
", res);
}
while (1)
{
/* 省略 */
}
}
14.2.3 函数tcp_check_send
函数原型:
BOOL tcp_check_send (
U8 socket ); /* TCP socket 句柄 */
函数描述:
函数tcp_check_send用于检测是否可以发送数据。此函数通过检测TCP连接是否建立以及上次发送的数据是否接收到远程机器的应答来判断是否可以发送数据。
(1)第1个参数是TCP Socket句柄。
(2)返回值,可以发送数据,返回__TRUE;不可以发送数据,返回__FALSE。
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
uint8_t Rem_IP[4] = {IP1,IP2,IP3,IP4};
/*
*********************************************************************************************************
* 函 数 名: 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)
{
int32_t iCount;
uint8_t *sendbuf;
uint8_t tcp_status;
uint16_t maxlen;
uint8_t res;
OS_RESULT xResult;
const uint16_t usMaxBlockTime = 2;·
/*
创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。
但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。
*/
socket_tcp = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功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;
}
}
}
}
14.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)返回值,返回本次可以发送的最大报文长度,单位字节。
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
uint8_t Rem_IP[4] = {IP1,IP2,IP3,IP4};
/*
*********************************************************************************************************
* 函 数 名: 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)
{
int32_t iCount;
uint8_t *sendbuf;
uint8_t tcp_status;
uint16_t maxlen;
uint8_t res;
OS_RESULT xResult;
const uint16_t usMaxBlockTime = 2;·
/*
创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。
但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。
*/
socket_tcp = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功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;
}
}
}
}
14.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字节。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小。
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
uint8_t Rem_IP[4] = {IP1,IP2,IP3,IP4};
/*
*********************************************************************************************************
* 函 数 名: 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)
{
int32_t iCount;
uint8_t *sendbuf;
uint8_t tcp_status;
uint16_t maxlen;
uint8_t res;
OS_RESULT xResult;
const uint16_t usMaxBlockTime = 2;·
/*
创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。
但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。
*/
socket_tcp = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功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;
}
}
}
}
14.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字节。
- 操作缓冲区的时候,切不可超过申请的缓冲区大小。
使用举例:
/*
*********************************************************************************************************
* 宏定义,远程服务器的IP和端口
*********************************************************************************************************
*/
/* 要访问的远程服务器IP和端口配置,也就是电脑端调试助手设置的IP和端口号 */
#define IP1 192
#define IP2 168
#define IP3 1
#define IP4 2
#define PORT_NUM 1001
/* 这个是本地端口 */
#define LocalPort_NUM 1024
/*
*********************************************************************************************************
* 变量
*********************************************************************************************************
*/
uint8_t socket_tcp;
uint8_t Rem_IP[4] = {IP1,IP2,IP3,IP4};
/*
*********************************************************************************************************
* 函 数 名: 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)
{
int32_t iCount;
uint8_t *sendbuf;
uint8_t tcp_status;
uint16_t maxlen;
uint8_t res;
OS_RESULT xResult;
const uint16_t usMaxBlockTime = 2;·
/*
创建TCP Socket并连接,客户端连接服务器后,10秒内无数据通信将断开连接。
但是由于这里使能了TCP_TYPE_KEEP_ALIVE,会一直保持连接,不受10秒的时间限制。
*/
socket_tcp = tcp_get_socket (TCP_TYPE_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功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;
}
}
}
}
14.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)
{
switch (tcp_get_state(socket_tcp))
{
case TCP_STATE_FREE:
case TCP_STATE_CLOSED:
{
/* 这里默认不开启调试,否则未跟电脑端服务器连接前会一直发 */
#if 0
uint8_t res;
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf_debug("tcp connect res = %d
", res);
#else
tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
#endif
}
break;
case TCP_STATE_LISTEN:
break;
case TCP_STATE_CONNECT:
return (__TRUE);
default:
break;
}
return (__FALSE);
}
14.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接收窗口大小。
14.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章讲解的调试方法进行测试。
14.5 TCP客户端的实现方法
有了本章节14.3小节的配置后,剩下的问题就是TCP客户端的创建和TCP客户端数据收发的实现。
14.5.1 创建TCP客户端
TCP客户端的创建比较简单,调用函数tcp_get_socket即可,此函数的使用和注意事项在本章的14.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_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功res = %d
", res);
}
/* 省略 */
}
14.5.2 TCP数据发送
TCP Socket的数据发送一定要注意各个函数调用顺序和使用方法,非常重要!否则,数据发送很容易失败。数据发送所用到函数的使用方法和注意事项在本章节的14.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_CLIENT | TCP_TYPE_KEEP_ALIVE, 0, 10, tcp_callback);
if(socket_tcp != 0)
{
res = tcp_connect (socket_tcp, Rem_IP, PORT_NUM, LocalPort_NUM);
printf("TCP Socket创建成功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务必要依次调用,一个都不