4. tcp客户端
在协议栈源码工程下,存在一个用vs2015建立的TcpServerForStackTesting工程。其运行在windows平台下,模拟实际应用场景下的tcp服务器。当tcp客户端连接到服务器后,服务器会立即下发一个1100多字节长度的控制报文到客户端。之后在整个tcp链路存续期间,服务器会每隔一段随机的时间(90秒到120秒之间)下发控制报文到客户端,模拟实际应用场景下服务器主动下发指令、数据到客户端的情形。客户端则连续上发数据报文到服务器,服务器回馈一个应答报文给客户端。客户端如果收不到该应答报文则会立即重发,直至收到应答报文或超过重试次数后重连服务器。总之,整个测试场景的设计目标就是完全契合常见的商业应用需求,以此来验证协议栈的核心功能指标是否完全达标。用vs2015打开这个工程,配置管理器指定目标平台为x64。main.cpp文件的头部定义了服务器的端口号以及报文长度等信息:
#define SRV_PORT 6410 //* 服务器端口
#define LISTEN_NUM 10 //* 最大监听数
#define RCV_BUF_SIZE 2048 //* 接收缓冲区容量
#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_SIZE 1300 //* 接收缓冲区容量
#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 0
emac_init(); //* ethernet网卡初始化函数,并注册网卡到协议栈
#else
while(!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;
}
//* 分配一个socket
if(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)
{
//* 收到报文,处理之,报文有两种:一种是应答报文;另一种是服务器主动下发的控制报文
//* 在这里添加你的自定义代码
……
}
//* 发送数据报文