最新教程下载:http://www.armbbs.cn/forum.php?mod=viewthread&tid=104619
第8章 ThreadX NetXDUO之TCP服务器
本章节为大家讲解NetXDUO的TCP服务器实现,学习本章节前,务必要优先学习第7章TCP传输控制协议基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。
8.1 初学者重要提示
8.2 TCP服务器API函数
8.3 TCP服务器的实现方法
8.4 网络调试助手和板子的调试操作步骤
8.5 实验例程说明
8.6 总结
8.1 初学者重要提示
1、 学习本章节前,务必保证已经学习了第7章的基础知识。
2、 本章要掌握的函数稍多,可以先学会基本的使用,然后再深入了解这些函数使用时的注意事项,争取达到熟练使用。
3、 socket和监听的关系:
- 创建的一个socket只能创建一个监听。
- 创建的一个socket不能够监听多个 。
- 创建多个socket可以创建多个监听。
- 创建多个socket可以仅创建一个监听。
8.2 TCP服务器API函数
下面一张图说明ThreadX NetXDUO TCP Socket的各种API玩法:
8.2.1 函数nx_system_initialize
函数原型:
VOID nx_system_initialize(VOID);
函数描述:
NetXDUO初始化,所有其它功能调用之前必须优先调用此函数。
8.2.2 函数nx_packet_pool_create
函数原型:
UINT nx_packet_pool_create( NX_PACKET_POOL *pool_ptr, CHAR *name, ULONG payload_size, VOID *memory_ptr, ULONG memory_size);
函数描述:
此函数用于数据包内存池创建
函数参数:
1、 第1个参数是内存池控制块的地址。
2、 第2个参数是内存池名字。
3、 第3个参数是内存池中每个数据包的字节数。 此值必须至少为 40 个字节,并且还必须可以被 4 整除。
4、 第4个参数是内存池中数据地址,此地址必须ULONG对齐。
5、 第5个参数是内存池大小。
6、 返回值:
- NX_SUCCESS:(0x00) 创建内存池成功。
- NX_PTR_ERROR:(0x07) 第1个参数地址无效。
- NX_SIZE_ERROR:(0x09) 第5个参数内存池大小无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
使用举例:
/* 创建内存池 */ status = nx_packet_pool_create(&pool_0, /* 内存池控制块 */ "NetX Main Packet Pool",/* 内存池名 */ 1536, /* 内存池每个数据包大小,单位字节此值必须至少为 40 个字节,并且还必须可以被 4 整除 */ (ULONG*)(((int)packet_pool_area + 15) & ~15) ,/* 内存池地址,此地址必须ULONG对齐 */ NX_PACKET_POOL_SIZE); /* 内存池大小 */
8.2.3 函数nx_ip_create
函数原型:
UINT nx_ip_create( NX_IP *ip_ptr, CHAR *name, ULONG ip_address, ULONG network_mask, NX_PACKET_POOL *default_pool, VOID (*ip_network_driver)(NX_IP_DRIVER *), VOID *memory_ptr, ULONG memory_size, UINT priority);
函数描述:
此函数使用用户提供的 IP 地址,数据包内存内存池和网络驱动程序创建 IP 实例。注意,直到 IP任务执行之后,才会调用网络驱动。
函数参数:
1、 第1个参数是创建IP实例的控制块指针。
2、 第2个参数是IP实例的名字。
3、 第3个参数是IP地址。
4、 第4个参数是子网掩码
5、 第5个参数是内存池地址。
6、 第6个参数是网卡驱动地址。
7、 第7个参数是IP任务栈地址
8、 第8个参数是IP任务栈大小,单位字节。
9、 第9个参数是IP任务优先级。
10、 返回值
- NX_SUCCESS:(0x00) 创建 IP 实例成功。
- NX_NOT_IMPLEMENTED:(0x4A) 未正确配置 NetX Duo 库。
- NX_PTR_ERROR:(0x07) IP控制块地址、网络驱动函数指针、内存池地址或任务栈地址无效。
- NX_SIZE_ERROR:(0x09) 提供的任务栈大小太小。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_IP_ADDRESS_ERROR:(0x21) 提供的 IP 地址无效。
- NX_OPTION_ERROR:(0x21) 提供的 IP 任务优先级无效。
使用举例:
/* 例化IP */ status = nx_ip_create(&ip_0, /* IP实例控制块 */ "NetX IP Instance 0", /* IP实例名 */ IP_ADDRESS(IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3), /* IP地址 */ 0xFFFFFF00UL, /* 子网掩码 */ &pool_0, /* 内存池 */ nx_driver_stm32h7xx, /* 网卡驱动 */ (UCHAR*)AppTaskNetXStk, /* IP任务栈地址 */ sizeof(AppTaskNetXStk), /* IP任务栈大小,单位字节 */ APP_CFG_TASK_NETX_PRIO); /* IP任务优先级 */
8.2.4 函数nx_arp_enable
函数原型:
UINT nx_arp_enable( NX_IP *ip_ptr, VOID *arp_cache_memory, ULONG arp_cache_size);
函数描述:
此函数用于使能ARP地址解析。
函数参数:
1、 ip_ptr:IP实例地址。
2、 arp_cache_memory:ARP缓存地址。
3、 arp_cache_size:每个 ARP 条目均为 52 个字节,因此,ARP 条目总数是52字节整数倍。
4、 返回值
- NX_SUCCESS:(0x00) 启用 ARP 成功。
- NX_PTR_ERROR:(0x07) IP实例地址或ARP缓存地址无效。
- NX_SIZE_ERROR:(0x09) 用户提供的 ARP 缓存内存太小。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_ALREADY_ENABLED:(0x15) 此组件已启用。
使用举例:
int32_t tcp_sock; tcp_sock = netTCP_GetSocket (tcp_cb_server); if (tcp_sock > 0) { res = netTCP_Listen (tcp_sock, PORT_NUM); } if(netTCP_SendReady(tcp_sock) == true ) { }
8.2.5 函数nx_ip_fragment_enable
函数原型:
UINT nx_ip_fragment_enable(NX_IP *ip_ptr);
函数描述:
此函数用于启用 IPv4 和 IPv6 数据包分段和重组功能。创建 IP 任务时,此服务会自动禁用。
函数参数:
1、 第1个参数是IP实例地址。
2、 返回值
- NX_SUCCESS:(0x00) 启用 IP 分段成功。
- NX_PTR_ERROR:(0x07) IP 实例地址无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) IP 分段功能未编译到 NetX Duo 中。
使用举例:
/* 使能fragment */ status = nx_ip_fragment_enable(&ip_0);
8.2.6 函数nx_tcp_enable
函数原型:
UINT nx_tcp_enable(NX_IP *ip_ptr);
函数描述:
此函数用于使能TCP组件。
函数参数:
1、 第1个参数是IP实例地址。
2、 返回值
- NX_SUCCESS:(0x00) 启用 TCP 成功。
- NX_ALREADY_ENABLED:(0x15) TCP 已启用。
- NX_PTR_ERROR:(0x07) IP 实例地址无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
使用举例:
/* 使能TCP */ status = nx_tcp_enable(&ip_0);
8.2.7 函数nx_tcp_socket_create
函数原型:
UINT nx_tcp_socket_create( NX_IP *ip_ptr, NX_TCP_SOCKET *socket_ptr, CHAR *name, ULONG type_of_service, ULONG fragment, UINT time_to_live, ULONG window_size, VOID (*urgent_data_callback)(NX_TCP_SOCKET *socket_ptr), VOID (*disconnect_callback)(NX_TCP_SOCKET *socket_ptr));
函数描述:
此函数用于创建TCP Socket。
函数参数:
1、 第1个参数是IP实例地址。
2、 第2个参数是TCP控制块地址。
3、 第3个参数是任务控制块名字。
4、 第4个参数是服务类型,支持的参数如下:
- NX_IP_NORMAL (0x00000000)
- NX_IP_MIN_DELAY (0x00100000)
- NX_IP_MAX_DATA (0x00080000)
- NX_IP_MAX_RELIABLE (0x00040000)
- NX_IP_MIN_COST (0x00020000)
5、 第5个参数指定是否允许进行 IP 分段。 如果指定 NX_FRAGMENT_OKAY (0x0),则允许进行 IP 分段。 如果指定 NX_DONT_FRAGMENT (0x4000),则会禁止进行 IP 分段。
6、 第6个参数是指定一个 8 位的值,用于定义此数据包在被丢弃之前可通过的路由器数目。 默认值由 NX_IP_TIME_TO_LIVE 指定。
7、 第7个参数定义TCP Socket接收队列中允许的最大字节数。
8、 第8个参数用于在接收流中检测到紧急数据时调用的回调函数。如果此值为 NX_NULL,则会忽略紧急数据。
9、 第9个参数是TCP Socket另一端发出断开连接时调用的回调函数。如果此值为 NX_NULL,则会禁用断开连接回调函数。
10、 返回值:
- NX_SUCCESS:(0x00) 创建 TCP Socket成功。
- NX_OPTION_ERROR:(0x0A) 服务类型、分段、窗口大小或生存时间选项无效。
- NX_PTR_ERROR:(0x07) IP 实例或TCP Socket指针无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
注意事项:
- 应用程序回调(第9个参数)是在IP任务里面调用的。
使用举例:
/* 创建TCP Socket */ ret = nx_tcp_socket_create(&ip_0, /* IP实例控制块 */ &TCPSocket, /* TCP控制块 */ "TCP Server Socket", /* TCP Socket名 */ NX_IP_NORMAL, /* IP服务类型 */ NX_FRAGMENT_OKAY, /* 使能IP分段 */ NX_IP_TIME_TO_LIVE, /*用于定义此数据包在被丢弃之前可通过的路由器数目 */ 4320, /* TCP Socket接收队列中允许的最大字节数 */ NX_NULL, /* 用于在接收流中检测到紧急数据时调用的回调函数 */ NX_NULL); /* TCP Socket另一端发出断开连接时调用的回调函数 */
8.2.8 函数nx_tcp_server_socket_listen
函数原型:
UINT nx_tcp_server_socket_listen( NX_IP *ip_ptr, UINT port, NX_TCP_SOCKET *socket_ptr, UINT listen_queue_size, VOID (*listen_callback)(NX_TCP_SOCKET *socket_ptr, UINT port));
函数描述:
此函数用于监听指定 TCP 端口上的客户端连接请求。 接收到客户端连接请求时,提供的服务器Socket就会与指定的端口绑定,并调用所提供的监听回调函数。
如果应用程序希望在同一端口上处理其他客户端连接,则必须使用可用的Socket(处于关闭状态的Socket)调用 nx_tcp_server_socket_relisten来建立下一个连接。 在调用重新监听之前,其他客户端连接会进行排队。 超过最大队列深度时,就会丢弃最早的连接请求,以此将新连接请求排入队列。 最大队列深度由此函数指定。
函数参数:
1、 第1个参数是IP实例地址。
2、 第2个参数是要监听的端口号,范围0 – 0xFFFF。
3、 第3个参数是Socket地址。
4、 第4个参数是可以监听的连接数。
5、 第5个参数是监听回调函数,如果设置为NULL,则不使用监听回调。
6、 返回值
- NX_SUCCESS:(0x00) 启用 TCP 端口监听成功。
- NX_MAX_LISTEN:(0x33) 没有更多的监听请求供使用,nx_api.h 中定义的常量NX_MAX_LISTEN_REQUESTS 定义了监听请求数上限。
- NX_NOT_CLOSED:(0x35) 提供的Socket未处于关闭状态。
- NX_ALREADY_BOUND:(0x22) 提供的Socket已与某个端口绑定。
- NX_DUPLICATE_LISTEN:(0x34) 此端口已有Socket请求。
- NX_INVALID_PORT:(0x46) 指定了无效的端口。
- NX_PTR_ERROR:(0x07) IP实例地址 或Socket地址无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
注意事项:
- 监听回调是在IP任务里面调用的。
使用举例:
/* * 监听新的链接。 * 创建回调TCP_listen_callback表示监听到新连接。 */ ret = nx_tcp_server_socket_listen(&ip_0, /* IP实例控制块 */ DEFAULT_PORT, /* 默认端口 */ &TCPSocket, /* TCP Socket控制块 */ MAX_TCP_CLIENTS, /* 可以监听的连接数 */ NULL); /* 监听回调函数 */
8.2.9 函数nx_tcp_server_socket_relisten
函数原型:
UINT nx_tcp_server_socket_relisten( NX_IP *ip_ptr, UINT port, NX_TCP_SOCKET *socket_ptr);
函数描述:
前面监听的端口上接收到连接之后,可以调用次函数。此函数的主要目的是提供新的Socket用于下一个客户端连接。如果已有排队中的连接请求,则调用此函数期间就会立即处理该连接。
函数参数:
1、 第1个参数是IP实例地址。
2、 第2个参数是监听的端口号,范围0 – 0xFFFF。
3、 第3个参数是用于下一客户端连接的Socket。
4、 返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00) 重新监听TCP 端口成功。
- NX_NOT_CLOSED:(0x35) 提供的Socket未处于关闭状态。
- NX_ALREADY_BOUND:(0x22) 提供的Socket已与某个端口绑定。
- NX_INVALID_RELISTEN:(0x47) 此端口已有一个有效的Socket或者指定的端口监听请求。
- NX_CONNECTION_PENDING:(0x48) 与 NX_SUCCESS 相同,只不过存在已排队的连接请求,并且已在调用期间处理该请求。
- NX_INVALID_PORT:(0x46) 指定了无效的端口。
- NX_PTR_ERROR:(0x07) IP 实例地址或监听回调无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
注意事项:
- 函数nx_tcp_server_socket_listen设置的监听回调,此函数也会调用。
使用举例:
/* 重新监听 */ nx_tcp_server_socket_relisten(&ip_0, DEFAULT_PORT, &TCPSocket);
8.2.10 函数nx_tcp_server_socket_accept
函数原型:
UINT nx_tcp_server_socket_accept( NX_TCP_SOCKET *socket_ptr, ULONG wait_option);
函数描述:
此函数用于接收TCP客户端连接请求。在用户调用了监听或者重新监听函数后,又或者监听回调函数之后,可以立即调用此函数。无法立即建立连接的时候,此函数会根据等待参数挂起。
函数参数:
1、 第1个参数是TCP Socket控制块地址。
2、 第2个参数是等待选项,支持的参数如下:
- NX_NO_WAIT (0x00000000)。
- NX_WAIT_FOREVER (0xFFFFFFFF)。
- 以时钟周期为单位的超时值(0x00000001 到 0xFFFFFFFE)。
3、 返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00) 接受 TCP Socket(被动连接)成功。
- NX_NOT_LISTEN_STATE:(0x36) 提供的TCP Socket未处于监听状态。
- NX_IN_PROGRESS:(0x37) 未指定等待,连接尝试正在进行中。
- NX_WAIT_ABORTED:(0x1A) 已通过调用 tx_thread_wait_abort 中止挂起。
- NX_PTR_ERROR:(0x07)Socket指针错误。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
注意事项:
- 不再需要该连接之后,应用程序必须调用 nx_tcp_server_socket_unaccept,以删除Socket与服务器端口的绑定。
- 应用程序回调是在 IP任务中调用的。
使用举例:
/* 启动TCP通信前,接收新连接 */ ret = nx_tcp_server_socket_accept(&TCPSocket, /* TCP Socket控制块 */ TX_WAIT_FOREVER); /* 等待连接 */ if (ret) { Error_Handler(__FILE__, __LINE__); }
8.2.11 函数nx_tcp_server_socket_unaccept
函数原型:
UINT nx_tcp_server_socket_unaccept(NX_TCP_SOCKET *socket_ptr);
函数描述:
此服务用于删除Socket与服务器端口的绑定。在断开连接之后,或者没有成功的接收连接时,应用程序必须调用此函数。
函数参数:
1、 第1个参数是TCP Socket指针。
2、 返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00) 取消接受服务器套接字成功。
- NX_NOT_LISTEN_STATE:(0x36) 服务器套接字处于不正确的状态,可能未断开连接。
- NX_PTR_ERROR:(0x07) Socket指针无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
使用举例:
/* 解除Socket和服务器端口的绑定 */ nx_tcp_server_socket_unaccept(&TCPSocket);
8.2.12 函数nx_tcp_socket_info_get
函数原型:
UINT nx_tcp_socket_info_get( NX_TCP_SOCKET *socket_ptr, ULONG *tcp_packets_sent, ULONG *tcp_bytes_sent, ULONG *tcp_packets_received, ULONG *tcp_bytes_received, ULONG *tcp_retransmit_packets, ULONG *tcp_packets_queued, ULONG *tcp_checksum_errors, ULONG *tcp_socket_state, ULONG *tcp_transmit_queue_depth, ULONG *tcp_transmit_window, ULONG *tcp_receive_window);
函数描述:
用于获取TCP Socket相关信息。
函数参数:
1、 第1个参数是TCP Socket指针。
2、 第2个参数是发送的TCP数据包总数目。
3、 第3个参数是发送的TCP总字节数。
4、 第4个参数是接收的TCP数据包总数目。
5、 第5个参数是接收的TCP总字节数。
6、 第6个参数是重新传输的TCP数据包总数目。
7、 第7个参数是Socket上TCP排队的TCP数据包总数。
8、 第8个参数是Socket上有校验和错误的TCP数据包总数。
9、 第9个参数是Socket当前状态。
10、 第10个参数是仍在排队等待ACK的发送数据包总数。
11、 第11个参数是当前发送窗口大小。
12、 第12个参数是当前接收窗口大小。
13、 返回值:
- NX_SUCCESS:(0x00) 检索 TCP Socket信息成功。
- NX_PTR_ERROR:(0x07) Socket指针无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
使用举例:
/* 获取socket状态 */ nx_tcp_socket_info_get(&TCPSocket, /* TCP Socket控制块 */ NULL, /* 发送的TCP数据包总数目 */ NULL, /* 发送的TCP总字节数 */ NULL, /* 接收TCP数据包总数目 */ NULL, /* 接收的TCP总字节数 */ NULL, /* 重新传输的TCP数据包总数目 */ NULL, /* Socket上TCP排队的TCP数据包总数 */ NULL, /* Socket上有校验和错误的TCP数据包总数 */ &socket_state, /* Socket当前状态 */ NULL, /* 仍在排队等待ACK的发送数据包总数 */ NULL, /* 当前发送窗口大小 */ NULL); /* 当前接收窗口大小 */
8.2.13 函数nx_tcp_socket_receive
函数原型:
UINT nx_tcp_socket_receive( NX_TCP_SOCKET *socket_ptr, NX_PACKET **packet_ptr, ULONG wait_option);
函数描述:
此函数用于从指定的Socket接收TCP数据,如果指定的Socket上没有已经排队的数据,则调用方会根据提供的等待选项参数挂起。
函数参数:
1、 第1个参数是TCP Socket指针
2、 第2个参数是TCP数据包指针。
3、 第3个参数是Socket队列上没有数据时的处理:
- NX_NO_WAIT (0x00000000)。
- NX_WAIT_FOREVER (0xFFFFFFFF)。
- 以时钟周期为单位的超时值(0x00000001 到 0xFFFFFFFE)。
4、 返回值:
- NX_SUCCESS:(0x00) 接收Socket数据成功。
- NX_NOT_BOUND:(0x24) Socket未绑定。
- NX_NO_PACKET:(0x01) 未收到任何数据。
- NX_WAIT_ABORTED:(0x1A) 通过调用 tx_thread_wait_abort 中止挂起。
- NX_NOT_CONNECTED:(0x38) 该Socket不再处于已连接状态。
- NX_PTR_ERROR:(0x07) Socket指针或返回数据包指针无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
注意事项:
- 如果返回了 NX_SUCCESS,则应用程序负责:不再需要收到数据包时将其释放。
使用举例:
/* 接收TCP客户端发的TCP数据包 */ ret = nx_tcp_socket_receive(&TCPSocket, /* TCP Socket控制块 */ &data_packet, /* 接收到的数据包 */ NX_WAIT_FOREVER); /* 永久等待 */
8.2.14 函数nx_tcp_socket_send
函数原型:
UINT nx_tcp_socket_send( NX_TCP_SOCKET *socket_ptr, NX_PACKET *packet_ptr, ULONG wait_option);
函数描述:
此函数用于TCP Socket数据发送。 如果接收方最近一次建议的窗口大小低于此请求,则此函数可以根据指定的等待参数挂起。此函数可保证不会将大于 MSS 的数据包数据发送到 IP 层。
函数参数:
1、 第1个参数是TCP Socket句柄。
2、 第2个参数是TCP数据包指针。
3、 第3个参数是发送的数据包大于接收方窗口大小时的参数处理,支持如下参数:
- NX_NO_WAIT (0x00000000)
- NX_WAIT_FOREVER (0xFFFFFFFF)
- 以时钟周期为单位的超时值(0x00000001 到 0xFFFFFFFE)
4、 返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00) Socket发送成功。
- NX_NOT_BOUND:(0x24) Socket未与任何端口绑定。
- NX_NO_INTERFACE_ADDRESS:(0x50) 找不到合适的传出接口。
- NX_NOT_CONNECTED:(0x38) 套接字不再处于已连接状态。
- NX_WINDOW_OVERFLOW:(0x39) 请求大于接收方所播发的窗口大小(以字节为单位)。
- NX_WAIT_ABORTED:(0x1A) 已通过调用 tx_thread_wait_abort 中止所请求的挂起。
- NX_INVALID_PACKET:(0x12) 数据包未分配。
- NX_TX_QUEUE_DEPTH:(0x49) 已达到最大传输队列深度。
- NX_OVERFLOW:(0x03) 数据包追加指针无效。
- NX_PTR_ERROR:(0x07) 套接字指针无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
- NX_UNDERFLOW:(0x02) 数据包前置指针无效。
注意事项:
- 除非返回了错误,否则应用程序不应在调用此函数后释放该数据包。这样做会导致不可预知的结果,因为网络驱动程序还会在传输后尝试释放该数据包。
使用举例:
/* 立即将接收到的数据发送回去 */ ret = nx_tcp_socket_send(&TCPSocket, /* TCP Socket控制块 */ data_packet, /* 数据包 */ NX_WAIT_FOREVER); /* 永久等待 */
8.2.15 函数nx_packet_data_retrieve
函数原型:
UINT nx_packet_data_retrieve( NX_PACKET *packet_ptr, VOID *buffer_start, ULONG *bytes_copied);
函数描述:
此函数用于将提供的数据包中的数据复制到提供的缓冲区。复制的实际字节数由形参bytes_copied 所指向的存储单元返回。
注意,此函数不会更改该数据包的内部状态。检索的数据仍存在于该数据包中。
函数参数:
1、 第1个参数是指向源数据包的指针
2、 第2个参数是目的数据包的地址。
3、 第3个参数是最终复制的字节数存储地址。
4、 返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00)复制数据包数据成功。
- NX_INVALID_PACKET:(0x12) 数据包无效。
- NX_PTR_ERROR:(0x07) 形参地址无效。
注意事项:
目标缓冲区的大小必须足以容纳该数据包的内容。否则内存会损坏,导致不可预知的结果。
使用举例:
/* 获取客户端发来的数据 */ nx_packet_data_retrieve(data_packet, /* 接收到的数据包 */ data_buffer, /* 解析出数据 */ &bytes_read); /* 数据大小 */
8.2.16 函数nx_packet_release
函数原型:
UINT nx_packet_release(NX_PACKET *packet_ptr);
函数描述:
此函数用于释放数据包,包括链接到指定数据包的任何其他数据包。如果有其他任务在等待这个数据包,则该任务会获得该数据包并继续执行。
函数参数:
1、第1个参数是数据包地址。
2、返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00) 释放数据包成功。
- NX_PTR_ERROR:(0x07) 数据包指针无效。
- NX_UNDERFLOW:(0x02) 预置指针小于有效负载开始位置。
- NX_OVERFLOW:(0x03) 追加指针大于有效负载结束位置。
注意事项:
应用程序必须防止多次释放同一数据包,否则会导致不可预知的结果。
8.2.17 函数nx_tcp_socket_disconnect
函数原型:
UINT nx_tcp_socket_disconnect( NX_TCP_SOCKET *socket_ptr, ULONG wait_option);
函数描述:
此函数用于断开已建立的客户端或服务器Socket。在服务器Socket断开连接后应该有一个取消接受请求,而断开连接的客户端Socket会处于准备好接受其他连接请求的状态。 如果断开连接过程无法立即完成,则该函数会根据提供的等待选项挂起。
函数参数:
1、第1个参数是TCP Socket地址。
2、第2个参数等待断开连接时,支持的参数:
- NX_NO_WAIT (0x00000000)。
- NX_WAIT_FOREVER (0xFFFFFFFF)。
- 以时钟周期为单位的超时值(0x00000001 到 0xFFFFFFFE)。
3、返回值,返回以下几种状态值:
- NX_SUCCESS:(0x00) 断开Socket连接成功。
- NX_NOT_CONNECTED:(0x38) 指定的Socket未连接。
- NX_IN_PROGRESS:(0x37) 断开连接正在进行。
- NX_WAIT_ABORTED:(0x1A) 已通过调用 tx_thread_wait_abort 中止挂起请求。
- NX_PTR_ERROR:(0x07) Socket指针无效。
- NX_CALLER_ERROR:(0x11) 此服务的调用方无效。
- NX_NOT_ENABLED:(0x14) 此组件尚未启用。
使用举例:
/* 断开连接 */ nx_tcp_socket_disconnect(&TCPSocket, NX_WAIT_FOREVER);
8.3 TCP服务器的实现方法
8.3.1 NetXDUO初始化
创建TCP服务器前,要初始化NetX,创建内存池,例化IP:
/* ********************************************************************************************************* * 函 数 名: NetXTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void NetXTest(void) { UINT status; UINT ret; ULONG socket_state; UINT old_priority; NX_PACKET *data_packet; ULONG bytes_read; ULONG peer_ip_address; ULONG peer_port; /* 初始化NetX */ nx_system_initialize(); /* 创建内存池 */ status = nx_packet_pool_create(&pool_0, /* 内存池控制块 */ "NetX Main Packet Pool",/* 内存池名 */ 1536, /* 内存池每个数据包大小,单位字节此值必须至少为 40 个字节,并且还必须可以被 4 整除 */ (ULONG*)(((int)packet_pool_area + 15) & ~15) ,/* 内存池地址,此地址必须ULONG对齐 */ NX_PACKET_POOL_SIZE); /* 内存池大小 */ /* 检测创建是否失败 */ if (status) error_counter++; /* 例化IP */ status = nx_ip_create(&ip_0, /* IP实例控制块 */ "NetX IP Instance 0", /* IP实例名 */ IP_ADDRESS(IP_ADDR0, IP_ADDR1, IP_ADDR2, IP_ADDR3), /* IP地址 */ 0xFFFFFF00UL, /* 子网掩码 */ &pool_0, /* 内存池 */ nx_driver_stm32h7xx, /* 网卡驱动 */ (UCHAR*)AppTaskNetXStk, /* IP任务栈地址 */ sizeof(AppTaskNetXStk), /* IP任务栈大小,单位字节 */ APP_CFG_TASK_NETX_PRIO); /* IP任务优先级 */ /* 检测创建是否失败 */ if (status) error_counter++; /* 使能ARP,并提供ARP缓存 */ status = nx_arp_enable(&ip_0, /* IP实例控制块 */ (void *)arp_space_area, /* ARP缓存地址 */ sizeof(arp_space_area)); /* 每个 ARP 条目均为 52 个字节,因此,ARP 条目总数是52字节整数倍 */ /* 使能fragment */ status = nx_ip_fragment_enable(&ip_0); /* 检测使能成功 */ if (status) error_counter++; /* 使能TCP */ status = nx_tcp_enable(&ip_0); /* 检测使能成功 */ if (status) error_counter++; /* 使能UDP */ status = nx_udp_enable(&ip_0); /* 检测使能成功 */ if (status) error_counter++; /* 使能ICMP */ status = nx_icmp_enable(&ip_0); /* 检测使能成功 */ if (status) error_counter++; /* NETX初始化完毕后,重新设置优先级 */ tx_thread_priority_change(netx_thread_ptr, APP_CFG_TASK_NETX_PRIO1, &old_priority); tx_thread_priority_change(&AppTaskNetXProTCB, APP_CFG_TASK_NetXPro_PRIO1, &old_priority); /* 省略 */ }
程序末尾务优先级做了特别处理,创建的时候先设置为低优先级,检测到网线正常连接并初始了网络后将优先级设置到正常水平。
8.3.2 TCP服务器实现
下面是创建TCP服务器并创建监听
/* ********************************************************************************************************* * 函 数 名: NetXTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void NetXTest(void) { /* 省略 */ /* 创建TCP Socket */ ret = nx_tcp_socket_create(&ip_0, /* IP实例控制块 */ &TCPSocket, /* TCP控制块 */ "TCP Server Socket", /* TCP Socket名 */ NX_IP_NORMAL, /* IP服务类型 */ NX_FRAGMENT_OKAY, /* 使能IP分段 */ NX_IP_TIME_TO_LIVE, /*用于定义此数据包在被丢弃之前可通过的路由器数目 */ 4320, /* TCP Socket接收队列中允许的最大字节数 */ NX_NULL, /* 用于在接收流中检测到紧急数据时调用的回调函数 */ NX_NULL); /* TCP Socket另一端发出断开连接时调用的回调函数 */ if (ret) { Error_Handler(__FILE__, __LINE__); } /* * 监听新的链接。 * 创建回调TCP_listen_callback表示监听到新连接。 */ ret = nx_tcp_server_socket_listen(&ip_0, /* IP实例控制块 */ DEFAULT_PORT, /* 默认端口 */ &TCPSocket, /* TCP Socket控制块 */ MAX_TCP_CLIENTS, /* 可以监听的连接数 */ NULL); /* 监听回调函数 */ if (ret) { Error_Handler(__FILE__, __LINE__); } /* 启动TCP通信前,接收新连接 */ ret = nx_tcp_server_socket_accept(&TCPSocket, /* TCP Socket控制块 */ TX_WAIT_FOREVER); /* 监听回调函数 */ if (ret) { Error_Handler(__FILE__, __LINE__); } /* 省略 */ }
8.3.3 TCP回环通信实现
回环的意思就是电脑端网络助手发送数据给板子后,板子再将数据返回。
/* ********************************************************************************************************* * 函 数 名: NetXTest * 功能说明: TCPnet应用 * 形 参: 无 * 返 回 值: 无 ********************************************************************************************************* */ void NetXTest(void) { /* 省略 */ while(1) { TX_MEMSET(data_buffer, '\0', sizeof(data_buffer)); /* 获取socket状态 */ nx_tcp_socket_info_get(&TCPSocket, /* TCP Socket控制块 */ NULL, /* 发送的TCP数据包总数目 */ NULL, /* 发送的TCP总字节数 */ NULL, /* 接收TCP数据包总数目 */ NULL, /* 接收的TCP总字节数 */ NULL, /* 重新传输的TCP数据包总数目 */ NULL, /* Socket上TCP排队的TCP数据包总数 */ NULL, /* Socket上有校验和错误的TCP数据包总数 */ &socket_state, /* Socket当前状态 */ NULL, /* 仍在排队等待ACK的发送数据包总数 */ NULL, /* 当前发送窗口大小 */ NULL); /* 当前接收窗口大小 */ /* 如果连接还没有建立,继续接受新连接,成功的话开启接收数据 */ if(socket_state != NX_TCP_ESTABLISHED) { /* 启动TCP通信前,接收新连接 */ ret = nx_tcp_server_socket_accept(&TCPSocket, /* TCP Socket控制块 */ TX_WAIT_FOREVER); /* 等待连接 */ if (ret) { Error_Handler(__FILE__, __LINE__); } } if((socket_state == NX_TCP_ESTABLISHED)&&(ret == NX_SUCCESS)) { /* 接收TCP客户端发的TCP数据包 */ ret = nx_tcp_socket_receive(&TCPSocket, /* TCP Socket控制块 */ &data_packet, /* 接收到的数据包 */ NX_WAIT_FOREVER); /* 永久等待 */ if (ret == NX_SUCCESS) { /* 获取客户端的IP地址和端口 */ nx_tcp_socket_peer_info_get(&TCPSocket, /* TCP Socket控制块 */ &peer_ip_address, /* 远程IP地址 */ &peer_port); /* 远程端口号 */ /* 获取客户端发来的数据 */ nx_packet_data_retrieve(data_packet, /* 接收到的数据包 */ data_buffer, /* 解析出数据 */ &bytes_read); /* 数据大小 */ /* 打印接收到数据 */ PRINT_DATA(peer_ip_address, (unsigned int)peer_port, data_buffer); /* 立即将接收到的数据发送回去 */ ret = nx_tcp_socket_send(&TCPSocket, /* TCP Socket控制块 */ data_packet, /* 数据包 */ NX_WAIT_FOREVER); /* 永久等待 */ } else { /* 断开连接 */ nx_tcp_socket_disconnect(&TCPSocket, NX_WAIT_FOREVER); /* 解除Socket和服务器端口的绑定 */ nx_tcp_server_socket_unaccept(&TCPSocket); /* 重新监听 */ nx_tcp_server_socket_relisten(&ip_0, DEFAULT_PORT, &TCPSocket); } } } /* 省略 */ }
8.4 网络调试助手和板子的调试操作步骤
我们这里使用下面这款调试助手,当然,任何其它网络调试助手均可,不限制:
http://www.armbbs.cn/forum.php?mod=viewthread&tid=1568 。
8.4.1 测试使用的DM916X网口并注意跳线帽
测试时,网线要插到DM916X网口上:
8.4.2 RJ45网络变压器插座上绿灯和黄灯现象
各种网卡、交换机等网络设备都不一样,一般来讲:绿灯分为亮或不亮(代表网络速度),黄灯分为闪烁或不闪烁(代表是否有数据收发)。
绿灯:长亮代表100M; 不亮代表10M。
黄灯:长亮代表无数据收发; 闪烁代表有数据收发。
也有些千兆网卡的灯以颜色区分,不亮代表10M / 绿色代表100M / 黄色代表1000M。现在10M的网络基本看不到了,如果一个灯长亮,基本可以说明100M网络或更高,而另一个灯时而闪烁,那代表有数据收发,具体要看网络设备了。甚至有些低等网卡如TP-LINK,只有一个灯,亮代表连通,闪烁代表数据收发。
对于开发板上面的RJ45网络变压器插座上面的灯而言,绿灯代表数据收发,长亮的话表示无数据收发,闪烁代表有数据收发。黄灯代表网络速度,长亮代表100M,不亮代表10M。
8.4.3 第1步,设置板子IP地址
我们这里使用使用固定IP(或者说静态IP一个意思),设置也比较省事。我们这里以开发板和电脑直连的方式进行说明,即通过一根网线直接将开发板的网口和电脑端的网口连接起来即可。如果大家使用的是笔记本,强烈推荐测试期间将笔记本的WIFI网络禁止,各种代理软件和虚拟网卡也暂时关闭。等测试完毕了再逐一打开,查看是否有问题。
对于固定IP方式,也可以接到路由器或者交换机上面测试,特别注意板子设置的IP地址不要跟路由器或者交换机上其它设备的IP冲突了,测试阶段还是建议采用电脑直连方式,跑通了再跑其它方式。
在文件demo_dm9162_netx.h中设置IP地址,具体配置如下(大家更新自己的情况修改):
/* ********************************************************************************************************* * IP相关 ********************************************************************************************************* */ #define DEFAULT_PORT 1001 /* TCP服务器监听端口号 */ #define IP_ADDR0 192 #define IP_ADDR1 168 #define IP_ADDR2 28 #define IP_ADDR3 245
8.4.4 第2步,设置电脑IP地址
一定要将电脑端的IP地址设置到跟开发板在一个IP段,即都是192.168.28.X。第2步中已经将开发板的IP设置为192.168.28.245,我们这里就将电脑的IP设置为192.168.28.221。我这里是WIN7 64bit系统。
(1)右击桌面上的“网络”图标,选择属性。
(2)弹出的界面中选项“本地连接”
(3)选择“属性(P)”
(4)双击“Internet协议版本4(TCP/Ipv4)”选项。
(5)配置IP地址、子网掩码和默认网关,DNS无需配置。
(6)点击了“确定”按钮后,退回到之前的界面,这里的“确定”按钮不要忘了点击:
8.4.5 第3步,测试ping是否成功
下载例程到开发板,然后ping 192.168.28.245,查看是否连接上。
(1)WIN+R组合键打开“运行”窗口,输入cmd。
(2)输入ping 192.168.28.245后,回车,也是可以的。
收发相同,没有数据丢失,说明ping命令也是成功的。
8.4.6 第3步,网络调试助手创建TCP客户端
- 打开调试助手,点击左上角创建连接:
- 弹出如下界面,类型选择TCP,目标IP设置为192.168.28.245端口号1001,最后点击创建:
- 创建后的界面效果如下:
- 点击连接,连接后的界面效果如下:
8.4.7 第5步,TCP服务器回环测试
板子和网络调试助手建立连接后就可以相互收发数据了。
发送和接收一致,说明移植是没问题的。
8.5 实验例程
配套例子:
V6-2402_ThreadX NetXDUO TCP Server
实验目的:
- 学习ThreadX NetXDUO TCP Server实现
实验内容:
- 共创建了如下几个任务,通过按下按键K1可以通过串口打印任务堆栈使用情况
======================================================
OS CPU Usage = 1.31%
=======================================================
任务优先级 任务栈大小 当前使用栈 最大栈使用 任务名
Prio StackSize CurStack MaxStack Taskname
2 4092 303 459 App Task Start
30 1020 303 303 App Task STAT
31 1020 87 71 App Task IDLE
5 4092 311 551 App Msp Pro
7 4092 303 719 App Task UserIF
6 4092 255 359 App NETX Pro
3 4092 415 535 NetX IP Instance 0
0 1020 191 235 System Timer Thread
串口软件可以使用SecureCRT或者H7-TOOL RTT查看打印信息。
App Task Start任务 :启动任务,这里用作BSP驱动包处理。
App Task MspPro任务 :消息处理。
App Task UserIF任务 :按键消息处理。
App Task COM任务 :这里用作LED闪烁。
System Timer Thread任务:系统定时器任务
操作说明:
1、由于程序使用了DWT时钟周期计数器,程序下载后,请将板子重新上电使用,防止DWT时钟周期计数器没有正常复位。
2、NetX网络协议栈操作:
(1) 默认IP地址192.168.28.245,在demo_dm9162_netx.c开头定义,用户可根据需要修改。
(2) 可以在电脑端用网络调试软件创建TCP Client连接此服务器端,端口号1001。
(3) 实现了一个简单的回环通信,用户使用上位机发送的数据通过板子返回到上位机。
串口打印信息方式(AC5,AC6和IAR):
波特率 115200,数据位 8,奇偶校验位无,停止位 1
8.6 总结
本章节主要为大家讲解了TCP服务器的实现,大家也可以创建各种玩法加强认识。