3 onps栈使用说明——tcp、udp通讯测试

4. tcp客户端在协议栈源码工程下,存在一个用vs2015建立的TcpServerForStackTesting工程 。其运行在windows平台下,模拟实际应用场景下的tcp服务器 。当tcp客户端连接到服务器后,服务器会立即下发一个1100多字节长度的控制报文到客户端 。之后在整个tcp链路存续期间,服务器会每隔一段随机的时间(90秒到120秒之间)下发控制报文到客户端,模拟实际应用场景下服务器主动下发指令、数据到客户端的情形 。客户端则连续上发数据报文到服务器,服务器回馈一个应答报文给客户端 。客户端如果收不到该应答报文则会立即重发,直至收到应答报文或超过重试次数后重连服务器 。总之,整个测试场景的设计目标就是完全契合常见的商业应用需求,以此来验证协议栈的核心功能指标是否完全达标 。用vs2015打开这个工程,配置管理器指定目标平台为x64 。main.cpp文件的头部定义了服务器的端口号以及报文长度等信息:
#define SRV_PORT6410 //* 服务器端口#define LISTEN_NUM10//* 最大监听数#define RCV_BUF_SIZE2048 //* 接收缓冲区容量#define PKT_DATA_LEN_MAX 1200 //* 报文携带的数据最大长度,凡是超过这个长度的报文都将被丢弃我们可以依据实际情形调整上述配置并利用这个模拟服务器测试tcp客户端的通讯功能 。
……#include "onps.h"#define PKT_FLAG 0xEE //* 通讯报文的头部和尾部标志typedef struct _ST_COMMUPKT_HDR_ { //* 数据及控制指令报文头部结构CHAR bFlag;//* 报文头部标志,其值参看PKT_FLAG宏CHAR bCmd;//* 指令,0为数据报文,1为控制指令报文CHAR bLinkIdx;//* tcp链路标识,当存在多个tcp链路时,该字段用于标识这是哪一个链路UINT unSeqNum;//* 报文序号UINT unTimestamp;//* 报文被发送时刻的unix时间戳USHORT usDataLen;//* 携带的数据长度USHORT usChechsum;//* 校验和(crc16),覆盖除头部和尾部标志字符串之外的所有字段} PACKED ST_COMMUPKT_HDR, *PST_COMMUPKT_HDR; typedef struct _ST_COMMUPKT_ACK_ { //* 数据即控制指令应答报文结构ST_COMMUPKT_HDR stHdr; //* 报文头UINT unTimestamp;//* unix时间戳,其值为被应答报文携带的时间戳CHAR bLinkIdx;//* tcp链路标识,其值为被应答报文携带的链路标识CHAR bTail;//* 报文尾部标志,其值参看PKT_FLAG宏} PACKED ST_COMMUPKT_ACK, *PST_COMMUPKT_ACK;//* 提前申请一块静态存储时期的缓冲区用于tcp客户端的接收和发送,因为接收和发送的报文都比较大,所以不使用动态申请的方式#define RCV_BUF_SIZE1300//* 接收缓冲区容量#define PKT_DATA_LEN_MAX 1200//* 报文携带的数据最大长度,凡是超过这个长度的报文都将被丢弃static UCHAR l_ubaRcvBuf[RCV_BUF_SIZE]; //* 接收缓冲区static UCHAR l_ubaSndBuf[sizeof(ST_COMMUPKT_HDR) + PKT_DATA_LEN_MAX]; //* 发送缓冲区,ST_COMMUPKT_HDR为通讯报文头部结构体int main(void){EN_ONPSERR enErr;SOCKET hSocket = INVALID_SOCKET;if(open_npstack_load(&enErr)){printf("The open source network protocol stack (ver %s) is loaded successfully. \r\n", ONPS_VER);//* 协议栈加载成功,在这里初始化ethernet网卡或等待ppp链路就绪#if 0emac_init(); //* ethernet网卡初始化函数,并注册网卡到协议栈#elsewhile(!netif_is_ready("ppp0")) //* 等待ppp链路建立成功os_sleep_secs(1);#endif}else{printf("The open source network protocol stack failed to load, %s\r\n", onps_error(enErr));return -1;}//* 分配一个socketif(INVALID_SOCKET == (hSocket = socket(AF_INET, SOCK_STREAM, 0, &enErr))){//* 返回了一个无效的socket,打印错误日志printf("<1>socket() failed, %s\r\n", onps_error(enErr));return -1;}//* 连接成功则connect()函数返回0,非0值则连接失败if(connect(hSocket, "192.168.0.2", 6410, 10)){printf("connect 192.168.0.2:6410 failed, %s\r\n", onps_get_last_error(hSocket, NULL));close(hSocket);return -1;}//* 等待接收服务器应答或控制报文的时长(即recv()函数的等待时长),单位:秒 。0不等待;大于0等待指定秒数;-1一直//* 等待直至数据到达或报错 。设置成功返回TRUE,否则返回FALSE 。这里我们设置recv()函数不等待//* 注意,只有连接成功后才可设置这个接收等待时长,在这里我们设置接收不等待,recv()函数立即返回,非阻塞型if(!socket_set_rcv_timeout(hSocket, 0, &enErr))printf("socket_set_rcv_timeout() failed, %s\r\n", onps_error(enErr));INT nThIdx = 0;while(TRUE && nThIdx < 1000){//* 接收,前面已经设置recv()函数不等待,有数据则读取数据后立即返回,无数据则立即返回INT nRcvBytes = recv(hSocket, ubaRcvBuf, sizeof(ubaRcvBuf));if(nRcvBytes > 0){//* 收到报文,处理之,报文有两种:一种是应答报文;另一种是服务器主动下发的控制报文//* 在这里添加你的自定义代码……}//* 发送数据报文到服务器,首先封装要发送的数据报文,PST_COMMUPKT_HDR其类型为指向ST_COMMUPKT_HDR结构体的指//* 针,这个结构体是与TcpServerForStackTesting服务器通讯用的报文头部结构PST_COMMUPKT_HDR pstHdr = (PST_COMMUPKT_HDR)l_ubaSndBuf;pstHdr->bFlag = (CHAR)PKT_FLAG;pstHdr->bCmd = 0x00;pstHdr->bLinkIdx = (CHAR)nThIdx++;pstHdr->unSeqNum = unSeqNum;pstHdr->unTimestamp = time(NULL);pstHdr->usDataLen = 900; //* 填充随机数据,随机数据长度加ST_COMMUPKT_HDR结构体长度不超过l_ubaSndBuf的长度即可pstHdr->usChechsum = 0;pstHdr->usChechsum = crc16(l_ubaSndBuf + sizeof(CHAR), sizeof(ST_COMMUPKT_HDR) - sizeof(CHAR) + 900, 0xFFFF);l_ubaSndBuf[sizeof(ST_COMMUPKT_HDR) + 900] = PKT_FLAG;//* 发送上面已经封装好的数据报文INT nPacketLen = sizeof(ST_COMMUPKT_HDR) + pstHdr->usDataLen + 1;INT nSndBytes = send(hSocket, l_ubaSndBuf, nPacketLen, 3);if(nSndBytes != nPacketLen) //* 与实际要发送的数据不相等的话就意味着发送失败了{printf("<err>sent %d bytes failed, %s\r\n", nPacketLen, onps_get_last_error(hSocket, &enErr));//* 关闭socket,断开当前tcp连接,释放占用的协议栈资源close(hSocket);return -1;}}//* 关闭socket,断开当前tcp连接,释放占用的协议栈资源close(hSocket);return 0;}

经验总结扩展阅读