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

编写tcp客户端的几个关键步骤:

  1. 调用socket函数 , 申请一个数据流(tcp)类型的socket;
  2. connect()函数建立tcp连接;
  3. recv()函数等待接收服务器下发的应答及控制报文;
  4. send()函数将封装好的数据报文发送给服务器;
  5. close()函数关闭socket,断开当前tcp连接;
真实场景下,单个tcp报文携带的数据长度的上限基本在1K左右 。所以,在上面给出的功能测试代码中,单个通讯报文的长度也设定在这个范围内 。客户端循环上报服务器的数据报文的长度900多字节,服务器下发开发板的控制报文长度1100多字节 。
与传统的socket编程相比,除了上述几个函数的原型与Berkeley sockets标准有细微的差别,在功能及使用方式上没有任何改变 。之所以对函数原型进行调整,原因是传统的socket编程模型比较繁琐——特别是阻塞/非阻塞的设计很不简洁,需要一些看起来很“突兀”地额外编码,比如select操作 。在设计协议栈的socket模型时,考虑到类似select之类的操作细节完全可以借助rtos的信号量机制将其封装到底层实现,从而达成简化用户编码,让socket编程更加简洁、优雅的目的 。因此 , 最终呈现给用户的协议栈socket模型部分偏离了Berkeley标准 。
5. tcp服务器常见的tcp服务器要完成的工作无外乎就是接受连接请求,接收客户端上发的数据,下发应答或控制报文,清除不活跃的客户端以释放其占用的系统资源 。因此,tcp服务器的功能测试代码分为两部分实现:一部分在主线程完成启动tcp服务器、等待接受连接请求这两项工作(为了突出主要步骤,清除不活跃客户端的工作在这里省略);另一部分单独建立一个线程完成读取客户端数据并下发应答报文的工作 。
……#include "onps.h"#define LTCPSRV_PORT6411 //* tcp测试服务器端口#define LTCPSRV_BACKLOG_NUM 5//* 排队等待接受连接请求的客户端数量static SOCKET l_hSockSrv;//* tcp服务器socket , 这是一个静态存储时期的变量 , 因为服务器数据接收线程也要使用这个变量//* 启动tcp服务器SOCKET tcp_server_start(USHORT usSrvPort, USHORT usBacklog){EN_ONPSERR enErr;SOCKET hSockSrv;do {//* 申请一个sockethSockSrv = socket(AF_INET, SOCK_STREAM, 0, &enErr);if(INVALID_SOCKET == hSockSrv)break;//* 绑定地址和端口,功能与Berkeley sockets提供的bind()函数相同if(bind(hSockSrv, NULL, usSrvPort))break;//* 启动监听,同样与Berkeley sockets提供的listen()函数相同if(listen(hSockSrv, usBacklog))break;return hSockSrv;} while(FALSE);//* 执行到这里意味着前面出现了错误,无法正常启动tcp服务器了if(INVALID_SOCKET != hSockSrv)close(hSockSrv);printf("%s\r\n", onps_error(enErr));//* tcp服务器启动失败 , 返回一个无效的socket句柄return INVALID_SOCKET;}//* 完成tcp服务器的数据读取工作static void THTcpSrvRead(void *pvData){SOCKET hSockClt;EN_ONPSERR enErr;INT nRcvBytes;UCHAR ubaRcvBuf[256];while(TRUE){//* 等待客户端有新数据到达hSockClt = tcpsrv_recv_poll(l_hSockSrv, 1, &enErr);if(INVALID_SOCKET != hSockClt) //* 有效的socket{//* 注意这里一定要尽量读取完毕该客户端的所有已到达的数据,因为每个客户端只有新数据到达时才会触发一个信号到用户//* 层,如果你没有读取完毕就只能等到该客户端送达下一组数据时再读取了 , 这可能会导致数据处理延迟问题while(TRUE){//* 读取数据nRcvBytes = recv(hSockClt, ubaRcvBuf, 256);if(nRcvBytes > 0){//* 原封不动的回送给客户端,利用回显来模拟服务器回馈应答报文的场景send(hSockClt, ubaRcvBuf, nRcvBytes, 1);}else //* 已经读取完毕{if(nRcvBytes < 0){//* 协议栈底层报错,这里需要增加你的容错代码处理这个错误并打印错误信息printf("%s\r\n", onps_get_last_error(hSocket, NULL));}break;}}}else //* 无效的socket{//* 返回一个无效的socket时需要判断是否存在错误,如果不存在则意味着1秒内没有任何数据到达,否则打印这个错误if(ERRNO != enErr){printf("tcpsrv_recv_poll() failed, %s\r\n", onps_error(enErr));break;}}}}int main(void){EN_ONPSERR enErr;if(open_npstack_load(&enErr)){printf("The open source network protocol stack (ver %s) is loaded successfully. \r\n", ONPS_VER);//* 协议栈加载成功,在这里初始化ethernet网卡,并注册网卡到协议栈emac_init();}else{printf("The open source network protocol stack failed to load, %s\r\n", onps_error(enErr));return -1;}//* 启动tcp服务器l_hSockSrv = tcp_server_start(LTCPSRV_PORT, LTCPSRV_BACKLOG_NUM);if(INVALID_SOCKET != l_hSockSrv){//* 在这里添加工作线程启动代码,启动tcp服务器数据读取线程THTcpSrvRead……}//* 进入主线程的主逻辑处理循环,等待tcp客户端连接请求到来while(TRUE){//* 接受连接请求in_addr_t unCltIP;USHORT usCltPort;SOCKET hSockClt = accept(l_hSockSrv, &unCltIP, &usCltPort, 1, &enErr);if(INVALID_SOCKET != hSockClt){//* 在这里你自己的代码处理新到达的客户端……}else{printf("accept() failed, %s\r\n", onps_error(enErr));break;}}//* 关闭socket,释放占用的协议栈资源close(l_hSockSrv);return 0;}

推荐阅读