第32章 RL-TCPnet之Telnet服务器
本章节为大家讲解RL-TCPnet的Telnet应用,学习本章节前,务必要优先学习第31章的Telnet基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
本章教程含STM32F407开发板和STM32F429开发板。
32.1 初学者重要提示
32.2 Telnet函数
32.3 Telnet配置说明(Net_Config.c)
32.4 Telnet调试说明(Net_Debug.c)
32.5 Telnet访问方法和板子的操作步骤
32.6 实验例程说明(裸机)
32.7 实验例程说明(RTX)
32.8 总结
32.1 初学者重要提示
- 学习本章节前,务必保证已经学习了第31章的基础知识。
- 对于初学者,务必要看本章节32.5小节的测试方法。
32.2 Telnet函数
使用如下9个函数可以实现RL-TCPnet的Telnet:
- tnet_check_account
- tnet_accept_host
- tnet_cbfunc
- tnet_ccmp
- tnet_get_info
- tnet_get_user_id
- tnet_process_cmd
- tnet_set_delay
- tnet_msg_poll
关于这9个函数的讲解及其使用方法可以看教程第 3 章 3.4 小节里面说的参考资料 rlarm.chm 文件:
这里我们重点的说以下4个函数,因为本章节配套的例子使用的是这4个函数:
- tnet_cbfunc
- tnet_process_cmd
- tnet_ccmp
- tnet_get_info
关于这些函数注意以下一点:
- Telnet的所有函数都不支持重入,也就是不支持多任务调用。
32.2.1 函数tnet_cbfunc
函数原型:
U16 tnet_cbfunc ( U8 code, /* 消息类型 */ U8* buf, /* 输出缓冲区地址 */ U16 buflen ); /* 输出缓冲区大小 */
函数描述:
函数tnet_cbfunc是Telnet服务器的回调函数,主要用于设置Telnet服务器给Telnet客户端发送连接和登录消息(为了更好理解此函数,实际操作一遍本章节配套的例子就明白了)。
- 第1个参数是消息类型,具体支持的消息类型如下:
- 第2个参数是输出缓冲区地址,用于存储服务器返回给客户端的消息。
- 第3个参数是输出缓冲区大小,单位字节。
- 返回值,返回使用的输出缓冲区大小,单位字节。
使用这个函数要注意以下问题:
- 输出缓冲区的大小是由TCP Socket的MSS最大报文段大小决定的,局域网中一般是1400字节左右,但是也可减小到500字节,甚至更小。
- 写到输出缓冲区的数据,不可以超过第三个参数buflen的大小,否则会造成内存指针链表的损坏,从而很容易造成系统崩溃。
- Telnet服务器不会自动扩展回车符,所以每个消息结束后务必自行加上回车和换行符。
使用举例:
/*---------------------------------------------------------------------------- * Telnet CallBack Functions *---------------------------------------------------------------------------*/ /*--------------------------- tnet_cbfunc -----------------------------------*/ U16 tnet_cbfunc (U8 code, U8 *buf, U16 buflen) { /* This function is called by the Telnet Client to get formated system */ /* messages for different code values. */ /* Values for 'code': */ /* 0 - initial header */ /* 1 - prompt string */ /* 2 - header for login only if authorization is enabled */ /* 3 - string 'Username' for login */ /* 4 - string 'Password' for login */ /* 5 - message 'Login incorrect' */ /* 6 - message 'Login timeout' */ /* 7 - Unsolicited messages from Server (ie. Basic Interpreter) */ U16 len = ; /* Make a reference to disable compiler warning. */ buflen = buflen; switch (code) { case : /* Write initial header after login. */ len = str_copy (buf, (U8 *)&tnet_header); break; case : /* Write a prompt string. */ len = str_copy (buf, "\r\narmfly> "); break; case : /* Write Login header. */ len = str_copy (buf, CLS "\r\nRL-TCPnet之Telnet服务器," " 请登录...\r\n"); break; case : /* Write 'username' prompt. */ len = str_copy (buf, "\r\n用户名: "); break; case : /* Write 'Password' prompt. */ len = str_copy (buf, "\r\n密 码: "); break; case : /* Write 'Login incorrect'.message. */ len = str_copy (buf, "\r\n登录失败"); break; case : /* Write 'Login Timeout' message. */ len = str_copy (buf, "\r\n120秒无操作,退出登录\r\n"); break; } return (len); }
32.2.2 函数tnet_process_cmd
函数原型:
U16 tnet_process_cmd ( U8* cmd, /* 指针变量,指向从Telnet客户端接收到的命令字符 */ U8* buf, /* 输出缓冲区地址 */ U16 buflen, /* 输出缓冲区大小,单位字节 */ U32* pvar ); /* 指针变量,指向一个不会被改变的变量 */
函数描述:
函数tnet_process_cmd用于处理Telnet客户端请求的命令。Telnet服务器从Telnet客户端收到连续的回车(CR)和换行(LF)字符序列时,会调用tnet_process_cmd函数(通常由用户在telnet客户端终端上按Enter键)。
- 第1个参数指向从Telnet客户端接收到的命令字符。
- 第2个参数是输出缓冲区地址,用于函数tnet_process_cmd执行过程中存储要返回给Telnet客户端的消息。
- 第3个参数是输出缓冲区的大小,单位字节。
- 第4个参数指向不会被Telnet服务器更改的变量。用户可以将其作为重复计数器,或者简单地区分tnet_process_cmd函数的不同调用,亦或者任何其它应用均可。
- 返回值,返回写入到输出缓冲区的字节数。返回值是U16类型的,其中bit15和bit14还可以作为其它用途,而剩余的bit0-bit13表示的最大值是16383,足够表示TCP Socket的MSS最大报文段大小的1460字节。
- bit14作为函数tnet_process_cmd是否重复调用的标志,如果此位设置为1,表示退出函数后,依然保持第1个参数cmd和第4个参数*pvar的数值,并再次调用函数tnet_process_cmd。如果此位设置为0,将不再重复调用。
- bit15是断开连接标志,如果此位设置为1,Telnet服务器将断开客户端会话。如果此位是0,表示继续保证连接。
使用这个函数要注意以下问题:
- 输出缓冲区的大小是由TCP Socket的MSS最大报文段大小决定的,局域网中一般是1400字节左右,但是也可减小到500字节,甚至更小。
- 写到输出缓冲区的数据,不可以超过第三个参数buflen的大小,否则会造成内存指针链表的损坏,从而很容易造成系统崩溃。
- Telnet服务器不会自动扩展回车符,所以每个消息结束后务必自行加上回车和换行符。
- 对于每个Telnet会话,*pvar(注意,这里是指的指针变量pvar所指向的存储单元)变量都是独立的,也就是说新创建一个会话,都会有一个独立的*pvar变量。另外,每个会话首次调用函数tnet_process_cmd之前都会将变量*pvar清零(注意,这里是指的指针变量pvar所指向的存储单元清零)。
使用举例:
/*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.2.3 函数tnet_ccmp
函数原型:
BOOL tnet_ccmp ( U8* buf, /* 字符串地址 */ U8* cmd ); /* 要比较的字符串地址 */
函数描述:
函数tnet_ccmp用于查询字符串中是否有要找的字符。
- 第1个参数是字符串地址。
- 第2个参数是要比较的字符串,即判断第1个参数的字符串里面是否有要查找的字符。
- 返回值,如果字符串中含有要查找的字符,返回__TRUE。如果没有,返回__FALSE。
使用这个函数要注意以下问题:
- 此函数类似C标准库函数strcmp,区别是tnet_ccmp仅比较第一个字符串,即在第1个参数buf中,该字符串由NULL字符终止,或后跟空格字符。
- 字符串cmd中的所有字符必须大写,因为参数buf中的字符串也只有大写字母。这是因为在调用tnet_ccmp函数之前进行了内部转换。
使用举例:
/*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.2.4 函数tnet_get_info
函数原型:
void tnet_get_info ( REMOTEM* info ); /* 结构体变量地址 */
函数描述:
函数tnet_get_info用于返回远程客户端的IP地址和MAC地址。
- 第1个参数填写结构体变量地址,用于存储远程客户端的IP和MAC地址。
使用这个函数要注意以下问题:
- 用户可以使用此函数设置远程客户端的操作权限,即通过此函数获取客户端地址,然后对这个客户端做操作限制。
- 对于SLIP或者PPP连接方式,MAC地址是00-00-00-00-00-00。
使用举例:
/*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.3 Telnet配置说明(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数量,这里配置了5个。
范围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接收窗口大小。
Telnet Server
Telnet配置,打上对勾就使能了此项功能
(1)Number of Telnet Connections
范围1-10。
服务器可以同时连接的客户端数。
(2) Port Number
范围1-65535。
监听的端口号,Telnet的端口号一般就是23。
(3) Idle Connection Timeout in seconds
范围0-3600秒。
空闲连接溢出时间,如果连接后,这段时间内无操作,Telnet服务器将断开客户端的连接。
配置为数值0,将禁止超时断开连接,即一直保持连接。
(4) Disable Echo
禁止回显。如果此选项打上对勾的话,服务器将不会回显接收到的字符。即用户在客户端输入的字符,不会在客户端上面显示出来,比如输入用户名,就不会将其显示出来。
(5) Enable User Authentication
用户认证,如果此选项打上对勾的话,将使能用户认证。
Authentication Username 是用户名。
Authentication Password 是用户密码。
32.4 Telnet调试说明(Net_Debug.c)
(重要说明,RL-TCPnet的调试是通过串口打印出来的)
RL-TCPnet的调试功能是通过配置文件Net_Debug.c实现。在MDK工程中打开文件Net_Debug.c,可以看到下图所示的工程配置向导:
Print Time Stamp
勾选了此选项的话,打印消息时,前面会附带时间信息。
其它所有的选项
默认情况下,所有的调试选项都关闭了,每个选项有三个调试级别可选择,这里我们以Telnet Server Debug为例,点击下拉列表,可以看到里面有Off,Errors only和Full debug三个调试级别可供选择,每个调试选项里面都是这三个级别。
Off:表示关闭此选项的调试功能。
Errors only:表示仅在此选项出错时,将其错误打印出来。
Full debug:表示此选项的全功能调试。
具体测试,我们这里就不做了,大家可以按照第11章讲解的调试方法进行测试。
32.5 Telnet的访问方法和板子的操作步骤
我们这里使用电脑端自带的Telnet客户端功能访问开发板上面建立的Telnet服务器功能。
32.5.1 使能电脑端Telnet客户端
这里以WIN7 64bit为例进行说明:
- 点击电脑左下角开始按钮,选择控制面板:
- 打开后,点击“程序”:
- 弹出的界面里面选择“打开或关闭Windows功能”。
- 点击后,就会弹出Windows功能窗口:
弹出后要稍等一会,界面会变成如下这个样子:
将Telnet客户端前面单选框打上对勾,点击确定。这样就使能了电脑上的Telnet客户端。
32.5.2 登陆Telnet服务器并进行操作
登陆开发板的Telnet方法如下:
- WIN+R组合键打开“运行”窗口,输入cmd。
- 弹出的命令窗口中,输入telnet armfly,当然,也可以将armfly换成板子实际的IP地址:
- 输入telnet armfly后,回车。
- 用户名填admin,点击回车,密码填写123456:
最后再点击回车,就进入到开发板上的Telnet服务器。
输入命令字符“?”或者“help”就可以打印出Telnet支持的命令,输入后点击回车,可以看到支持的命令如下:
我们这里简单的测试下命令“lendon”和“rinfo”,输出命令后记得点击回车:
另外注意,如果120秒内无操作,服务器将自动断开客户端的连接,同时弹出如下消息:
还需要操作的话,重新登录即可。
32.6 实验例程说明(裸机)
32.6.1 STM32F407开发板实验
配套例子:
V5-1044_RL-TCPnet实验_Telnet应用(裸机)
实验目的:
- 学习RL-TCPnet的Telnet应用。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 程序中实现了一个Telnet服务器,用户名是admin,密码123456。
实验操作:
详见本章节32.5小节。
配置向导文件设置(Net_Config.c):
详见本章节32.3小节。
调试文件设置(Net_Debug.c):
详见本章节32.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功能的测试,此文件主要实现网络主函数main_TcpNet的调用。
/* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { bsp_LedToggle(); /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { /* 初始化网络协议栈 */ init_TcpNet (); /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); } }
Telnet用户接口文件的实现
KEIL官网有提供Telnet的接口文件,名为Telnet_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
/* Net_Config.c */ extern struct tcp_cfg tcp_config; extern struct tnet_cfg tnet_config; #define tcp_NumSocks tcp_config.NumSocks #define tcp_socket tcp_config.Scb #define tnet_EnAuth tnet_config.EnAuth #define tnet_auth_passw tnet_config.Passw /* ANSI ESC Sequences for terminal control. */ #define CLS "\033[2J" #define TBLUE "\033[37;44m" #define TNORM "\033[0m" /* My structure of a Telnet U32 storage variable. This variable is private */ /* for each Telnet Session and is not altered by Telnet Server. It is only */ /* set to zero when tnet_process_cmd() is called for the first time. */ typedef struct { U8 id; U8 nmax; U8 idx; } MY_BUF; #define MYBUF(p) ((MY_BUF *)p) /* Local variables */ static U8 const tnet_header[] = { CLS "\r\n" " " TBLUE "*=============================================================*\r\n" TNORM " " TBLUE "* RL-TCPnet之Telnet服务器实验 *\r\n" TNORM " " TBLUE "*=============================================================*\r\n" TNORM }; static U8 const tnet_help1[] = { "\r\n\r\n" " 当前支持的命令:\r\n" " ----------------------------\r\n" " ledon - 打开LED4\r\n" " ledoff - 关闭LED4\r\n" " rinfo - 显示远程设备的IP地址和MAC\r\n" }; static U8 const tnet_help2[] = { " help, ? - 显示帮助\r\n" " bye - 断开连接\r\n\r\n" " <ESC>,<^C> - ESC按键,断开连接\r\n" " <BS> - 回车函数,删除左侧字符\r\n" " <UP><DOWN> - 按键UP和DOWM,浏览历史命令\r\n" }; /*---------------------------------------------------------------------------- * Telnet CallBack Functions *---------------------------------------------------------------------------*/ /*--------------------------- tnet_cbfunc -----------------------------------*/ U16 tnet_cbfunc (U8 code, U8 *buf, U16 buflen) { /* This function is called by the Telnet Client to get formated system */ /* messages for different code values. */ /* Values for 'code': */ /* 0 - initial header */ /* 1 - prompt string */ /* 2 - header for login only if authorization is enabled */ /* 3 - string 'Username' for login */ /* 4 - string 'Password' for login */ /* 5 - message 'Login incorrect' */ /* 6 - message 'Login timeout' */ /* 7 - Unsolicited messages from Server (ie. Basic Interpreter) */ U16 len = ; /* Make a reference to disable compiler warning. */ buflen = buflen; switch (code) { case : /* Write initial header after login. */ len = str_copy (buf, (U8 *)&tnet_header); break; case : /* Write a prompt string. */ len = str_copy (buf, "\r\narmfly> "); break; case : /* Write Login header. */ len = str_copy (buf, CLS "\r\nRL-TCPnet之Telnet服务器," " 请登录...\r\n"); break; case : /* Write 'username' prompt. */ len = str_copy (buf, "\r\n用户名: "); break; case : /* Write 'Password' prompt. */ len = str_copy (buf, "\r\n密 码: "); break; case : /* Write 'Login incorrect'.message. */ len = str_copy (buf, "\r\n登录失败"); break; case : /* Write 'Login Timeout' message. */ len = str_copy (buf, "\r\n120秒无操作,退出登录\r\n"); break; } return (len); } /*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.6.2 STM32F429开发板实验
配套例子:
V5-1044_RL-TCPnet实验_Telnet应用(裸机)
实验目的:
- 学习RL-TCPnet的Telnet应用。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 程序中实现了一个Telnet服务器,用户名是admin,密码123456。
实验操作:
详见本章节32.5小节。
配置向导文件设置(Net_Config.c):
详见本章节32.3小节。
调试文件设置(Net_Debug.c):
详见本章节32.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功能的测试,此文件主要实现网络主函数main_TcpNet的调用。
/* ********************************************************************************************************* * 函 数 名: tcpnet_poll * 功能说明: 使用TCPnet必须要一直调用的函数 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void tcpnet_poll(void) { if(bsp_CheckTimer()) { bsp_LedToggle(); /* 此函数坚决不可以放在中断里面跑 */ timer_tick (); } main_TcpNet (); } /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { /* 初始化网络协议栈 */ init_TcpNet (); /* 创建一个周期是100ms的软定时器 */ bsp_StartAutoTimer(, ); while () { /* TCP轮询 */ tcpnet_poll(); } }
Telnet用户接口文件的实现
KEIL官网有提供Telnet的接口文件,名为Telnet_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
/* Net_Config.c */ extern struct tcp_cfg tcp_config; extern struct tnet_cfg tnet_config; #define tcp_NumSocks tcp_config.NumSocks #define tcp_socket tcp_config.Scb #define tnet_EnAuth tnet_config.EnAuth #define tnet_auth_passw tnet_config.Passw /* ANSI ESC Sequences for terminal control. */ #define CLS "\033[2J" #define TBLUE "\033[37;44m" #define TNORM "\033[0m" /* My structure of a Telnet U32 storage variable. This variable is private */ /* for each Telnet Session and is not altered by Telnet Server. It is only */ /* set to zero when tnet_process_cmd() is called for the first time. */ typedef struct { U8 id; U8 nmax; U8 idx; } MY_BUF; #define MYBUF(p) ((MY_BUF *)p) /* Local variables */ static U8 const tnet_header[] = { CLS "\r\n" " " TBLUE "*=============================================================*\r\n" TNORM " " TBLUE "* RL-TCPnet之Telnet服务器实验 *\r\n" TNORM " " TBLUE "*=============================================================*\r\n" TNORM }; static U8 const tnet_help1[] = { "\r\n\r\n" " 当前支持的命令:\r\n" " ----------------------------\r\n" " ledon - 打开LED4\r\n" " ledoff - 关闭LED4\r\n" " rinfo - 显示远程设备的IP地址和MAC\r\n" }; static U8 const tnet_help2[] = { " help, ? - 显示帮助\r\n" " bye - 断开连接\r\n\r\n" " <ESC>,<^C> - ESC按键,断开连接\r\n" " <BS> - 回车函数,删除左侧字符\r\n" " <UP><DOWN> - 按键UP和DOWM,浏览历史命令\r\n" }; /*---------------------------------------------------------------------------- * Telnet CallBack Functions *---------------------------------------------------------------------------*/ /*--------------------------- tnet_cbfunc -----------------------------------*/ U16 tnet_cbfunc (U8 code, U8 *buf, U16 buflen) { /* This function is called by the Telnet Client to get formated system */ /* messages for different code values. */ /* Values for 'code': */ /* 0 - initial header */ /* 1 - prompt string */ /* 2 - header for login only if authorization is enabled */ /* 3 - string 'Username' for login */ /* 4 - string 'Password' for login */ /* 5 - message 'Login incorrect' */ /* 6 - message 'Login timeout' */ /* 7 - Unsolicited messages from Server (ie. Basic Interpreter) */ U16 len = ; /* Make a reference to disable compiler warning. */ buflen = buflen; switch (code) { case : /* Write initial header after login. */ len = str_copy (buf, (U8 *)&tnet_header); break; case : /* Write a prompt string. */ len = str_copy (buf, "\r\narmfly> "); break; case : /* Write Login header. */ len = str_copy (buf, CLS "\r\nRL-TCPnet之Telnet服务器," " 请登录...\r\n"); break; case : /* Write 'username' prompt. */ len = str_copy (buf, "\r\n用户名: "); break; case : /* Write 'Password' prompt. */ len = str_copy (buf, "\r\n密 码: "); break; case : /* Write 'Login incorrect'.message. */ len = str_copy (buf, "\r\n登录失败"); break; case : /* Write 'Login Timeout' message. */ len = str_copy (buf, "\r\n120秒无操作,退出登录\r\n"); break; } return (len); } /*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.7 实验例程说明(RTX)
32.7.1 STM32F407开发板实验
配套例子:
V5-1045_RL-TCPnet实验_Telnet应用(RTX)
实验目的:
- 学习RL-TCPnet的Telnet应用。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 程序中实现了一个Telnet服务器,用户名是admin,密码123456。
实验操作:
详见本章节32.5小节。
配置向导文件设置(Net_Config.c):
详见本章节32.3小节。
调试文件设置(Net_Debug.c):
详见本章节32.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键按下 */ case KEY_DOWN_K1: printf("K1键按下 \r\n"); break; /* K2键按下 */ case KEY_DOWN_K2: printf("K2键按下 \r\n"); break; /* K3键按下 */ case KEY_DOWN_K3: printf("K3键按下 \r\n"); 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 (); os_evt_set(0x0001, HandleTaskTCPMain); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现网络主函数main_TcpNet的调用。
#include "includes.h" /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { while () { os_evt_wait_and(0x0001, 0xFFFF); while (main_TcpNet() == __TRUE); } }
Telnet用户接口文件的实现
KEIL官网有提供Telnet的接口文件,名为Telnet_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
/* Net_Config.c */ extern struct tcp_cfg tcp_config; extern struct tnet_cfg tnet_config; #define tcp_NumSocks tcp_config.NumSocks #define tcp_socket tcp_config.Scb #define tnet_EnAuth tnet_config.EnAuth #define tnet_auth_passw tnet_config.Passw /* ANSI ESC Sequences for terminal control. */ #define CLS "\033[2J" #define TBLUE "\033[37;44m" #define TNORM "\033[0m" /* My structure of a Telnet U32 storage variable. This variable is private */ /* for each Telnet Session and is not altered by Telnet Server. It is only */ /* set to zero when tnet_process_cmd() is called for the first time. */ typedef struct { U8 id; U8 nmax; U8 idx; } MY_BUF; #define MYBUF(p) ((MY_BUF *)p) /* Local variables */ static U8 const tnet_header[] = { CLS "\r\n" " " TBLUE "*=============================================================*\r\n" TNORM " " TBLUE "* RL-TCPnet之Telnet服务器实验 *\r\n" TNORM " " TBLUE "*=============================================================*\r\n" TNORM }; static U8 const tnet_help1[] = { "\r\n\r\n" " 当前支持的命令:\r\n" " ----------------------------\r\n" " ledon - 打开LED4\r\n" " ledoff - 关闭LED4\r\n" " rinfo - 显示远程设备的IP地址和MAC\r\n" }; static U8 const tnet_help2[] = { " help, ? - 显示帮助\r\n" " bye - 断开连接\r\n\r\n" " <ESC>,<^C> - ESC按键,断开连接\r\n" " <BS> - 回车函数,删除左侧字符\r\n" " <UP><DOWN> - 按键UP和DOWM,浏览历史命令\r\n" }; /*---------------------------------------------------------------------------- * Telnet CallBack Functions *---------------------------------------------------------------------------*/ /*--------------------------- tnet_cbfunc -----------------------------------*/ U16 tnet_cbfunc (U8 code, U8 *buf, U16 buflen) { /* This function is called by the Telnet Client to get formated system */ /* messages for different code values. */ /* Values for 'code': */ /* 0 - initial header */ /* 1 - prompt string */ /* 2 - header for login only if authorization is enabled */ /* 3 - string 'Username' for login */ /* 4 - string 'Password' for login */ /* 5 - message 'Login incorrect' */ /* 6 - message 'Login timeout' */ /* 7 - Unsolicited messages from Server (ie. Basic Interpreter) */ U16 len = ; /* Make a reference to disable compiler warning. */ buflen = buflen; switch (code) { case : /* Write initial header after login. */ len = str_copy (buf, (U8 *)&tnet_header); break; case : /* Write a prompt string. */ len = str_copy (buf, "\r\narmfly> "); break; case : /* Write Login header. */ len = str_copy (buf, CLS "\r\nRL-TCPnet之Telnet服务器," " 请登录...\r\n"); break; case : /* Write 'username' prompt. */ len = str_copy (buf, "\r\n用户名: "); break; case : /* Write 'Password' prompt. */ len = str_copy (buf, "\r\n密 码: "); break; case : /* Write 'Login incorrect'.message. */ len = str_copy (buf, "\r\n登录失败"); break; case : /* Write 'Login Timeout' message. */ len = str_copy (buf, "\r\n120秒无操作,退出登录\r\n"); break; } return (len); } /*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.7.2 STM32F429开发板实验
配套例子:
V6-1045_RL-TCPnet实验_Telnet应用(RTX)
实验目的:
- 学习RL-TCPnet的Telnet应用。
实验内容:
- 强烈推荐将网线接到路由器或者交换机上面测试,因为已经使能了DHCP,可以自动获取IP地址。
- 程序中实现了一个Telnet服务器,用户名是admin,密码123456。
实验操作:
详见本章节32.5小节。
配置向导文件设置(Net_Config.c):
详见本章节32.3小节。
调试文件设置(Net_Debug.c):
详见本章节32.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键按下 */ case KEY_DOWN_K1: printf("K1键按下 \r\n"); break; /* K2键按下 */ case KEY_DOWN_K2: printf("K2键按下 \r\n"); break; /* K3键按下 */ case KEY_DOWN_K3: printf("K3键按下 \r\n"); 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 (); os_evt_set(0x0001, HandleTaskTCPMain); } }
RL-TCPnet功能测试
这里专门创建了一个app_tcpnet_lib.c文件用于RL-TCPnet功能的测试,此文件主要实现网络主函数main_TcpNet的调用。
#include "includes.h" /* ********************************************************************************************************* * 函 数 名: TCPnetTest * 功能说明: TCPent测试函数。 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void TCPnetTest(void) { while () { os_evt_wait_and(0x0001, 0xFFFF); while (main_TcpNet() == __TRUE); } }
Telnet用户接口文件的实现
KEIL官网有提供Telnet的接口文件,名为Telnet_uif.c文件。我们就是在这个文件上修改。具体修改后的代码如下:
/* Net_Config.c */ extern struct tcp_cfg tcp_config; extern struct tnet_cfg tnet_config; #define tcp_NumSocks tcp_config.NumSocks #define tcp_socket tcp_config.Scb #define tnet_EnAuth tnet_config.EnAuth #define tnet_auth_passw tnet_config.Passw /* ANSI ESC Sequences for terminal control. */ #define CLS "\033[2J" #define TBLUE "\033[37;44m" #define TNORM "\033[0m" /* My structure of a Telnet U32 storage variable. This variable is private */ /* for each Telnet Session and is not altered by Telnet Server. It is only */ /* set to zero when tnet_process_cmd() is called for the first time. */ typedef struct { U8 id; U8 nmax; U8 idx; } MY_BUF; #define MYBUF(p) ((MY_BUF *)p) /* Local variables */ static U8 const tnet_header[] = { CLS "\r\n" " " TBLUE "*=============================================================*\r\n" TNORM " " TBLUE "* RL-TCPnet之Telnet服务器实验 *\r\n" TNORM " " TBLUE "*=============================================================*\r\n" TNORM }; static U8 const tnet_help1[] = { "\r\n\r\n" " 当前支持的命令:\r\n" " ----------------------------\r\n" " ledon - 打开LED4\r\n" " ledoff - 关闭LED4\r\n" " rinfo - 显示远程设备的IP地址和MAC\r\n" }; static U8 const tnet_help2[] = { " help, ? - 显示帮助\r\n" " bye - 断开连接\r\n\r\n" " <ESC>,<^C> - ESC按键,断开连接\r\n" " <BS> - 回车函数,删除左侧字符\r\n" " <UP><DOWN> - 按键UP和DOWM,浏览历史命令\r\n" }; /*---------------------------------------------------------------------------- * Telnet CallBack Functions *---------------------------------------------------------------------------*/ /*--------------------------- tnet_cbfunc -----------------------------------*/ U16 tnet_cbfunc (U8 code, U8 *buf, U16 buflen) { /* This function is called by the Telnet Client to get formated system */ /* messages for different code values. */ /* Values for 'code': */ /* 0 - initial header */ /* 1 - prompt string */ /* 2 - header for login only if authorization is enabled */ /* 3 - string 'Username' for login */ /* 4 - string 'Password' for login */ /* 5 - message 'Login incorrect' */ /* 6 - message 'Login timeout' */ /* 7 - Unsolicited messages from Server (ie. Basic Interpreter) */ U16 len = ; /* Make a reference to disable compiler warning. */ buflen = buflen; switch (code) { case : /* Write initial header after login. */ len = str_copy (buf, (U8 *)&tnet_header); break; case : /* Write a prompt string. */ len = str_copy (buf, "\r\narmfly> "); break; case : /* Write Login header. */ len = str_copy (buf, CLS "\r\nRL-TCPnet之Telnet服务器," " 请登录...\r\n"); break; case : /* Write 'username' prompt. */ len = str_copy (buf, "\r\n用户名: "); break; case : /* Write 'Password' prompt. */ len = str_copy (buf, "\r\n密 码: "); break; case : /* Write 'Login incorrect'.message. */ len = str_copy (buf, "\r\n登录失败"); break; case : /* Write 'Login Timeout' message. */ len = str_copy (buf, "\r\n120秒无操作,退出登录\r\n"); break; } return (len); } /*--------------------------- tnet_process_cmd ------------------------------*/ U16 tnet_process_cmd (U8 *cmd, U8 *buf, U16 buflen, U32 *pvar) { /* This is a Telnet Client callback function to make a formatted output */ /* for 'stdout'. It returns the number of bytes written to the out buffer.*/ /* Hi-bit of return value (len is or-ed with 0x8000) is a disconnect flag.*/ /* Bit 14 (len is or-ed with 0x4000) is a repeat flag for the Tnet client.*/ /* If this bit is set to 1, the system will call the 'tnet_process_cmd()' */ /* again with parameter 'pvar' pointing to a 4-byte buffer. This buffer */ /* can be used for storing different status variables for this function. */ /* It is set to 0 by Telnet server on first call and is not altered by */ /* Telnet server for repeated calls. This function should NEVER write */ /* more than 'buflen' bytes to the buffer. */ /* Parameters: */ /* cmd - telnet received command string */ /* buf - Telnet transmit buffer */ /* buflen - length of this buffer (500-1400 bytes - depends on MSS) */ /* pvar - pointer to local storage buffer used for repeated loops */ /* This is a U32 variable - size is 4 bytes. Value is: */ /* - on 1st call = 0 */ /* - 2nd call = as set by this function on first call */ REMOTEM rm; U16 len = ; switch (MYBUF(pvar)->id) { case : /* First call to this function, the value of '*pvar' is 0 */ break; case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len | 0x4000); case : /* Request a repeated call, bit 14 is a repeat flag. */ return (len |= 0x4000); } /* Simple Command line parser */ len = strlen ((const char *)cmd); if (tnet_ccmp (cmd, "LEDON") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights ON"); bsp_LedOn(); return (len); } if (tnet_ccmp (cmd, "LEDOFF") == __TRUE) { /* 'LED4' command received */ len = str_copy (buf,"\r\n=====>LED4 Lights OFF"); bsp_LedOff(); return (len); } if (tnet_ccmp (cmd, "BYE") == __TRUE) { /* 'BYE' command, send message and disconnect */ len = str_copy (buf, "\r\nDisconnect...\r\n"); /* Hi bit of return value is a disconnect flag */ return (len | 0x8000); } if (tnet_ccmp (cmd, "RINFO") == __TRUE) { /* Display Remote Machine IP and MAC address. */ tnet_get_info (&rm); len = sprintf ((char *)buf,"\r\n Remote IP : %d.%d.%d.%d", rm.IpAdr[],rm.IpAdr[],rm.IpAdr[],rm.IpAdr[]); len += sprintf ((char *)(buf+len), "\r\n Remote MAC: %02X-%02X-%02X-%02X-%02X-%02X", rm.HwAdr[],rm.HwAdr[],rm.HwAdr[], rm.HwAdr[],rm.HwAdr[],rm.HwAdr[]); return (len); } if (tnet_ccmp (cmd, "HELP") == __TRUE || tnet_ccmp (cmd, "?") == __TRUE) { /* 'HELP' command, display help text */ len = str_copy (buf,(U8 *)tnet_help1); len += str_copy (buf+len,(U8 *)tnet_help2); return (len); } /* Unknown command, display message */ len = str_copy (buf, "\r\n==> Unknown Command: "); len += str_copy (buf+len, cmd); return (len); }
32.8 总结
本章节就为大家讲解这么多,其中Telnet的测试稍麻烦些,希望大家实际动手操作一遍,并将其熟练掌握。