3 onps棧使用說明——tcp、udp通訊測試

4. tcp客戶端在協議棧源碼工程下,存在一個用vs2015建立的TcpServerForStackTesting工程 。其運行在windows平臺下,模擬實際應用場景下的tcp服務器 。當tcp客戶端連接到服務器后,服務器會立即下發一個1100多字節長度的控制報文到客戶端 。之后在整個tcp鏈路存續期間,服務器會每隔一段隨機的時間(90秒到120秒之間)下發控制報文到客戶端 , 模擬實際應用場景下服務器主動下發指令、數據到客戶端的情形 。客戶端則連續上發數據報文到服務器 , 服務器回饋一個應答報文給客戶端 ??蛻舳巳绻詹坏皆搼饒笪膭t會立即重發,直至收到應答報文或超過重試次數后重連服務器 ??傊麄€測試場景的設計目標就是完全契合常見的商業應用需求,以此來驗證協議棧的核心功能指標是否完全達標 。用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;}

推薦閱讀