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

编写tcp服务器的几个主要步骤:

  1. 调用socket函数,申请一个数据流(tcp)类型的socket;
  2. bind()函数绑定一个ip地址和端口号;
  3. listen()函数启动监听;
  4. accept()函数接受一个tcp连接请求;
  5. 调用tcpsrv_recv_poll()函数利用协议栈提供的poll模型(非传统的select模型)等待客户端数据到达;
  6. 调用recv()函数读取客户端数据并处理之 , 直至所有数据读取完毕返回第5步 , 获取下一个已送达数据的客户端socket;
  7. 定期检查不活跃的客户端 , 调用close()函数关闭tcp链路,释放客户端占用的协议栈资源;
与传统的tcp服务器编程并没有两样 。
协议栈实现了一个poll模型用于服务器的数据读取 。poll模型利用了rtos的信号量机制 。当某个tcp服务器端口有一个或多个客户端有新的数据到达时,协议栈会立即投递一个或多个信号到用户层 。注意,协议栈投递信号的数量取决于新数据到达的次数(tcp层每收到一个携带数据的tcp报文记一次) , 与客户端数量无关 。用户通过tcpsrv_recv_poll()函数得到这个信号,并得到最先送达数据的客户端socket,然后读取该客户端送达的数据 。注意这里一定要把所有数据读取出来 。因为信号被投递的唯一条件就是有新的数据到达 。没有信号 ,  tcpsrv_recv_poll()函数无法得到一个有效的客户端socket,那么剩余数据就只能等到该客户端再次送达新数据时再读了 。
其实,poll模型的运作机制非常简单 。tcp服务器每收到一组新的数据,就会将该数据所属的客户端socket放入接收队列尾部,然后投信号 。所以 , 数据到达、获取socket与投递信号是一系列的连锁反应,且一一对应 。tcpsrv_recv_poll()函数则在用户层接着完成连锁反应的后续动作:等信号、摘取接收队列首部节点、取出首部节点保存的socket、返回该socket以告知用户立即读取数据 。非常简单明了,没有任何拖泥带水 。从这个运作机制我们可以看出:
  1. poll模型的运转效率取决于rtos的信号量处理效率;
  2. tcpsrv_recv_poll()函数每次返回的socket有可能是同一个客户端的,也可能是不同客户端;
  3. 单个客户端已送达的数据长度与信号并不一一对应 , 一一对应的是该客户端新数据到达的次数与信号投递的次数,所以当数据读取次数小于信号数时,存在读取数据长度为0的情形;
  4. tcpsrv_recv_poll()函数返回有效的sokcet后,尽量读取全部数据到用户层进行处理,否则会出现剩余数据无法读取的情形,如果客户端不再上发新的数据的话;
6. udp通讯相比tcp,udp通讯功能的实现相对简单很多 。为udp绑定一个固定端口其就可以作为服务器使用,反之则作为一个客户端使用 。
……#include "onps.h"#define RUDPSRV_IP"192.168.0.2" //* 远端udp服务器的地址#define RUDPSRV_PORT 6416//* 远端udp服务器的端口#define LUDPSRV_PORT 6415//* 本地udp服务器的端口//* udp通讯用缓冲区(接收和发送均使用)static UCHAR l_ubaUdpBuf[256];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;}#if 0//* 如果是想建立一个udp服务器,这里需要调用bind()函数绑定地址和端口if(bind(hSocket, NULL, LUDPSRV_PORT)){printf("bind() failed,%s\r\n", onps_get_last_error(hSocket, NULL));//* 关闭socket释放占用的协议栈资源close(hSocket);return -1;}#else//* 建立一个udp客户端,在这里可以调用connect()函数绑定一个固定的目标服务器,接下来就可以直接使用send()函数发送//* 数据 , 当然在这里你也可以什么都不做(不调用connect()),但接下来你需要使用sendto()函数指定要发送的目标地址if(connect(hSocket, RUDPSRV_IP, RUDPSRV_PORT, 0)){printf("connect %s:%d failed, %s\r\n", RUDPSRV_IP, RUDPSRV_PORT, onps_get_last_error(hSocket, NULL));//* 关闭socket释放占用的协议栈资源close(hSocket);return -1;}#endif//* 与tcp客户端测试一样,接收数据之前要设定udp链路的接收等待的时间 , 单位:秒,这里设定recv()函数等待1秒if(!socket_set_rcv_timeout(hSocket, 1, &enErr))printf("socket_set_rcv_timeout() failed, %s\r\n", szNowTime, onps_error(enErr));INT nCount = 0;while(TRUE && nCount < 1000){//* 发缓冲区填充一段字符串然后得到其填充长度sprintf((char *)l_ubaUdpBuf, "U#%d#%d#>1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ", time(NULL), nCount++);INT nSendDataLen = strlen((const char *)l_ubaUdpBuf);//* 调用send()函数发送数据,如果实际发送长度与字符串长度不相等则说明发送失败if(nSendDataLen != send(hSocket, l_ubaUdpBuf, nSendDataLen, 0))printf("send failed, %s\r\n", onps_get_last_error(hSocket, NULL));//* 接收对端数据之前清0,以便本地能够正确输出收到的对端回馈的字符串memset(l_ubaUdpBuf, 0, sizeof(l_ubaUdpBuf));//* 调用recv()函数接收数据,如果想知道对端地址调用recvfrom()函数,在这里recv()函数为阻塞模式,最长阻塞1秒(如果未收到任何udp报文的话)INT nRcvBytes = recv(hSocket, l_ubaUdpBuf, sizeof(l_ubaUdpBuf));if(nRcvBytes > 0)printf("recv %d bytes, Data = https://www.huyubaike.com/biancheng/

推荐阅读