1. 底层API
由协议栈底层提供的api,用于涉及底层操作的一些功能实现,这些api接口函数的原型定义分布于不同的文件,它们被统一include进了onps.h中:
- open_npstack_load:将协议栈载入目标系统,协议栈开始运行
- open_npstack_unload:将协议栈载出目标系统,协议栈结束运行
- route_add:添加一条静态路由
- route_del:删除一条静态路由
- route_del_ext:删除指定网卡在路由表中的所有路由条目,禁止网卡跨网段通讯
- route_get_default:获取缺省路由
- dhcp_req_addr:向dhcp服务器请求租用一个动态地址
- ethernet_add:添加ethernet网卡
- ethernet_del:删除ethernet网卡
- ethernet_put_packet:将收到的ethernet报文推送给协议栈
- netif_is_ready:网卡是否已就绪(进入工作状态)
- netif_get_by_name:通过网卡名称查找网卡
- buddy_alloc:申请一块指定大小的内存
- buddy_free:释放申请的内存
- buf_list_get_next_node:取出下一个链表节点
- buf_list_get_len:链表所有节点携带的数据长度之和
- buf_list_merge_packet:合并链表节点携带的数据将其放入用户指定的缓冲区
open_npstack_load
功能
协议栈的入口函数,目标系统调用该函数启动协议栈。换言之,在使用协议栈之前必须首先成功先调用该函数。
原型
BOOL open_npstack_load(EN_ONPSERR *penErr);
入口参数
- penErr:指向错误编码的指针,当函数执行失败,该参数用于接收实际的错误码
返回值
加载成功,返回TRUE;反之返回FALSE,具体的错误信息参见参数penErr返回的错误码。
示例
EN_ONPSERR enErr;
if(open_npstack_load(&enErr)) //* 加载协议栈
{
//* 协议栈加载成功,在这里添加你的自定义代码
……
}
else
printf("协议栈加载失败, %s\r\n", onps_error(enErr)); //* 打印错误信息
open_npstack_unload
功能
协议栈的退出函数。一旦调用该函数,协议栈会结束运行并释放占用的相关系统资源。
原型
void open_npstack_unload(void);
入口参数
无
返回值
无
示例
略
route_add
功能
添加一条静态路由到路由表。
原型
BOOL route_add(PST_NETIF pstNetif, UINT unDestination, UINT unGateway, UINT unGenmask, EN_ONPSERR *penErr);
入口参数
- pstNetif:指向网卡控制块ST_NETIF的指针,该指针唯一的标识一块网卡,其用于指定静态路由添加到哪个网卡
- unDestination:目标网段地址,如果其值为0则其为缺省路由
- unGateway:网关地址
- unGenmask:子网掩码
- penErr:指向错误编码的指针,当函数执行失败,该参数用于接收实际的错误码
返回值
添加成功则返回TRUE,否则返回FALSE,具体的错误信息参见参数penErr返回的错误码。
示例
EN_ONPSERR enErr;
PST_NETIF pstNetif = netif_get_by_name("eth0");
if(pstNetif && route_add(pstNetif, inet_addr_small("47.92.239.0"), inet_addr_small("192.168.0.1"), inet_addr_small("255.255.255.0"), &enErr))
{
//* 添加成功,在这里添加你的自定义代码
……
}
else
printf("静态路由添加失败, %s\r\n", onps_error(enErr)); //* 打印错误信息
route_del
功能
删除一条静态路由。
原型
void route_del(UINT unDestination);
入口参数
- unDestination:指定要删除的目标网段地址
返回值
无
示例
略
route_del_ext
功能
删除指定网卡在路由表中的所有路由条目,禁止网卡跨网段通讯。
原型
void route_del_ext(PST_NETIF pstNetif);
入口参数
- pstNetif:指向网卡控制块ST_NETIF的指针,指定要删除哪个网卡的所有路由条目
返回值
无
示例
略
route_get_default
功能
获取缺省路由信息。
原型
PST_NETIF route_get_default(void);
入口参数
无
返回值
返回缺省路由绑定的网卡控制块首地址。
示例
略
dhcp_req_addr
功能
启动dhcp客户端,向dhcp服务器请求租用一个动态地址。
原型
BOOL dhcp_req_addr(PST_NETIF pstNetif, EN_ONPSERR *penErr);
入口参数
- pstNetif:指定要进行dhcp请求的网卡
- penErr:指向错误编码的指针,当函数执行失败,该参数用于接收实际的错误码
返回值
请求成功则返回TRUE,否则返回FALSE,具体的错误信息参见参数penErr返回的错误码。
示例
参见上一篇博文《onps栈移植说明(3)——添加网卡》。
ethernet_add
功能
增加新的ethernet网卡到协议栈。
原型
PST_NETIF ethernet_add(const CHAR *pszIfName,
const UCHAR ubaMacAddr[ETH_MAC_ADDR_LEN],
PST_IPV4 pstIPv4,
PFUN_EMAC_SEND pfunEmacSend,
void (*pfunStartTHEmacRecv)(void *pvParam),
PST_NETIF *ppstNetif,
EN_ONPSERR *penErr);
入口参数
- pszIfName:网卡名称
- ubaMacAddr:网卡 mac 地址
- pstIPv4:指向 ST_IPV4 结构体的指针(include/netif/netif.h),这个结构体保存用户指定的 ip地址、网关、dns、子网掩码等配置信息
- pfunEmacSend:函数指针,指向发送函数,函数原型为 INT(* PFUN_EMAC_SEND)(SHORT sBufListHead, UCHAR *pubErr),这个指针指向的其实就是网卡发送函数
- pfunStartTHEmacRecv:指向线程启动函数的指针,协议栈使用该指针指向的函数启动协议栈内部工作线程——ethernet网卡接收线程
- ppstNetif:二维指针,协议栈成功注册网卡后 ethernet_add()函数会返回一个 PST_NETIF 指针给调用者,这个参数指向这个指针,其最终会被协议栈通过 pvParam 参数传递给 pfunStartTHEmacRecv 指向的函数
- penErr:指向错误编码的指针,当函数执行失败,该参数用于接收实际的错误码
返回值
注册成功,返回一个 PST_NETIF 类型的指针,该指针指向新添加的网卡控制块,唯一的标识新添加的这块网卡;注册失败则返回 NULL,具体错误信息参见 penErr参数携带的错误码。
示例
参见上一篇博文《onps栈移植说明(3)——添加网卡》。
ethernet_del
功能
删除ethernet网卡。
原型
void ethernet_del(PST_NETIF *ppstNetif);
入口参数
- ppstNetif:指向网卡控制块PST_NETIF指针的指针,其指向ethernet_add()函数返回的PST_NETIF指针的首地址
返回值
无
示例
略
ethernet_put_packet
功能
将收到的ethernet报文推送给协议栈。
原型
void ethernet_put_packet(PST_NETIF pstNetif, PST_SLINKEDLIST_NODE pstNode);
入口参数
- pstNetif:指向网卡控制块ST_NETIF的指针
- pstNode:指向协议栈ethernet网卡接收链表节点的指针
返回值
无
示例
参见上一篇博文《onps栈移植说明(3)——添加网卡》。
netif_is_ready
功能
检查网卡是否已进入或正处于正常工作状态。
原型
BOOL netif_is_ready(const CHAR *pszIfName);
入口参数
- pszIfName:指向网卡名称的指针
返回值
返回TRUE,网卡已就绪;反之则返回FALSE。
示例
略
netif_get_by_name
功能
通过网卡名称查找网卡。
原型
PST_NETIF netif_get_by_name(const CHAR *pszIfName);
入口参数
- pszIfName:指向网卡名称的指针
返回值
存在则返回指向网卡控制块ST_NETIF的指针;否则返回NULL。
示例
略
buddy_alloc
功能
向内存管理单元(mmu)申请一块指定大小的内存。
原型
void *buddy_alloc(UINT unSize, EN_ONPSERR *penErr);
入口参数
- unSize:申请分配的内存大小,单位:字节
- penErr:指向错误编码的指针,当函数执行失败,该参数用于接收实际的错误码
返回值
申请成功返回内存首地址;反之则返回NULL,具体错误信息参见 penErr参数携带的错误码。
示例
参见buf_list_merge_packet函数示例或上一篇博文《onps栈移植说明(3)——添加网卡》。
buddy_free
功能
归还(释放)先前通过buddy_alloc()函数申请的内存。
原型
BOOL buddy_free(void *pvStart);
入口参数
- pvStart:指向要归还的内存首地址的指针
返回值
成功归还内存返回TRUE;当pvStart指向的并不是buddy_alloc()函数返回的内存首地址时则返回FALSE。
示例
参见buf_list_merge_packet函数示例。
buf_list_get_next_node
功能
按链接顺序从链表首部逐个取出链表节点。
原型
void *buf_list_get_next_node(SHORT *psNextNode, USHORT *pusDataLen);
入口参数
- psNextNode:指向链表下一个节点的指针
- pusDataLen:指向数据长度的指针,出口参数,其用于接收节点携带的数据长度
返回值
如果尚未到达链表尾部,则返回当前链表节点携带的数据的首地址;反之则返回NULL。
示例
参见上一篇博文《onps栈移植说明(3)——添加网卡》。
buf_list_get_len
功能
统计链表所有节点携带的数据长度之和。
原型
UINT buf_list_get_len(SHORT sBufListHead);
入口参数
- sBufListHead:链表首地址
返回值
返回链表所有节点携带的数据长度之和。
示例
参见buf_list_merge_packet函数示例或上一篇博文《onps栈移植说明(3)——添加网卡》。
buf_list_merge_packet
功能
从链表首节点开始顺序取出数据将其放入用户指定的缓冲区,把零散的链表数据合并成一块连续数据。
原型
void buf_list_merge_packet(SHORT sBufListHead, UCHAR *pubPacket);
入口参数
- sBufListHead:链表首地址
- pubPacket:指向用户缓冲区的指针,其用于接收合并后的数据以返回给用户使用
返回值
无
示例
EN_ONPSERR enErr;
UINT unDataLen = buf_list_get_len(sBufListHead /* 链表首地址 */);
UCHAR *pubBuf = (UCHAR *)buddy_alloc(unDataLen, &enErr); //* 申请一块缓冲区用于保存合并后的数据
if(pubBuf)
{
//* 合并,合并后的数据保存在了pubBuf中,数据长度为unDataLen
buf_list_merge_packet(sBufListHead, pubBuf);
//* 在这里增加你自己的代码处理合并合并后的数据
……
//* 处理完毕,释放刚才申请的内存
buddy_free(pubBuf);
}
2. Berkeley sockets
协议栈提供的伯克利套接字(Berkeley sockets)并不是严格按照传统socket标准设计实现的,而是我根据以往socket编程经验,以方便用户使用、简化用户编码为设计目标,重新声明并定义的一组常见socket接口函数。协议栈简化了传统BSD socket编程需要的一些繁琐操作,将一些不必要的操作细节改为底层实现,比如select/poll模型、阻塞及非阻塞读写操作等。简化并不意味着推翻,socket接口函数的基本定义、主要参数、使用方法并没有改变,你完全可以根据以往经验快速上手并熟练使用onps栈sockets。这一点相信你已经从前面的测试代码中得到了佐证。 socket层所有接口函数的实现源码被封装在了单独的一个文件中,参见bsd/socket.c。其对应的头文件socket.h有这些接口函数的定义和说明。目前协议栈提供的socket接口函数有十几个,能够满足绝大部分应用场景的需求。这些接口函数如下:
- socket:创建一个socket,目前仅支持udp和tcp两种类型
- close:关闭一个socket,释放当前占用的协议栈资源
- connect:与目标tcp服务器建立连接(阻塞型)或绑定一个固定的udp服务器地址
- connect_nb:与目标tcp服务器建立连接(非阻塞型)
- is_tcp_connected:获取当前tcp链路的连接状态
- send:数据发送函数,tcp链路下为阻塞型
- send_nb:数据发送函数,非阻塞型
- is_tcp_send_ok:数据是否已成功送达tcp链路的对端(收到tcp ack报文)
- sendto:udp数据发送函数,发送数据到指定目标地址
- recv:数据接收函数,udp/tcp链路通用
- recvfrom:数据接收函数,用于udp链路,接收数据的同时函数会返回数据源的地址信息
- socket_set_rcv_timeout:设定recv()函数接收等待的时长,单位:秒
- bind:绑定一个固定端口、地址
- listen:tcp服务器进入监听状态
- accept:接受一个到达的tcp连接请求
- tcpsrv_recv_poll:tcp服务器专用函数,等待任意一个或多个tcp客户端数据到达信号
- socket_get_last_error:获取socket最近一次发生的错误信息
- socket_get_last_error_code:获取socket最近一次发生的错误编码
socket
功能
创建一个socket,支持udp和tcp两种类型,即:SOCK_DGRAM和SOCK_STREAM。注意,不使用时一定要调用close()函数关闭,以释放其占用的协议栈相关资源。
原型
SOCKET socket(INT family, INT type, INT protocol, EN_ONPSERR *penErr);
入口参数
- family:目前仅支持IPv4地址,即AF_INET,其它地址族如AF_INET6之类的不支持
- type:指定socket类型,支持SOCK_STREAM和SOCK_DGRAM两种类型,前者为tcp,后者为udp
- protocol:未使用,固定为0
- penErr:指向错误编码的指针,当socket()函数执行失败,该参数用于接收实际的错误码
返回值
执行成功返回socket句柄,失败返回INVALID_SOCKET,具体的错误信息参见penErr参数返回的错误码。
示例
EN_ONPSERR enErr;
//* tcp
SOCKET hSocket = socket(AF_INET, SOCK_STREAM, 0, &enErr);
if(INVALID_SOCKET == hSocket) //* 返回一个无效的socket
printf("%s\r\n", onps_error(enErr)); //*打印错误信息
//* udp
SOCKET hSocket = socket(AF_INET, SOCK_DGRAM, 0, &enErr);
……
close
功能
关闭socket,释放占用的协议栈资源。
原型
void close(SOCKET socket);
入口参数
- socket:要关闭的socket句柄
返回值
无
示例
EN_ONPSERR enErr;
SOCKET hSocket = socket(AF_INET, SOCK_STREAM, 0, &enErr);
……
if(INVALID_SOCKET != hSocket)
close(hSocket);
connect
功能
用于tcp类型的socket时,其功能为与目标服务器建立tcp连接,阻塞型;用于udp类型的socket时,其功能为绑定一个固定的目标通讯地址,udp通讯均与这个固定地址进行。
原型
INT connect(SOCKET socket, const CHAR *srv_ip, USHORT srv_port, INT nConnTimeout);
入口参数
- socket:要进行connect操作的socket句柄
- srv_ip:目标服务器地址
- srv_port:目标服务器端口
- nConnTimeout:仅用于tcp通讯,指定连接超时时间(单位:秒),参数值如小于等于0则协议栈会采用缺省值,该值由TCP_CONN_TIMEOUT宏指定(参见sys_config.h);udp通讯未使用这个参数,可以指定任意一个值
返回值
0:连接成功
-1:连接失败,具体的错误信息通过onps_get_last_error()获得
示例
EN_ONPSERR enErr;
SOCKET hSocket = socket(AF_INET, SOCK_STREAM, 0, &enErr);
if(INVALID_SOCKET == hSocket)
{
printf("%s\r\n", onps_error(enErr));
return;
}
if(!connect(hSocket, "47.92.239.107", 6410, 10))
{
//* 连接成功,在这里添加你的自定义代码
……
}
else
{
//* 连接失败,打印错误信息
printf("%s\r\n", onps_get_last_error(hSocket, NULL));
}
……
close(hSocket);
connect_nb
功能
仅用于tcp类型的socket,非阻塞型,与目标tcp服务器建立连接。
原型
INT connect_nb(SOCKET socket, const CHAR *srv_ip, USHORT srv_port);
入口参数
- socket:要进行connect操作的socket句柄
- srv_ip:目标服务器地址
- srv_port:目标服务器端口
返回值
0:连接成功 1:连接中-1:连接失败,具体的错误信息通过onps_get_last_error()获得
示例
EN_ONPSERR enErr;
SOCKET hSocket = socket(AF_INET, SOCK_STREAM, 0, &enErr);
if(INVALID_SOCKET == hSocket)
{
printf("%s\r\n", onps_error(enErr));
return;
}
//* 循环等待tcp连接成功
while(1)
{
INT nRtnVal = connect_nb(hSocket, "47.92.239.107", 6410);
if(!nRtnVal)
{
//* 连接成功,在这里增加你的自定义代码
……
break; //* 退出循环,不再轮询检查tcp连接进程
}
else if(nRtnVal < 0)
{
//* 连接失败,打印错误信息并退出循环不再轮询检查tcp连接进程
printf("%s\r\n", onps_get_last_error(hSocket, NULL));
break;
}
else;
//* 连接中,tcp三次握手操作尚未完成,此时你可以干点别的事情,或者延时一小段时间后继续检查当前连接状态
……
os_sleep_secs(1);
}
……
close(hSocket);
is_tcp_connected
功能
检查tcp链路是否处于连接状态。
原型
INT is_tcp_connected(SOCKET socket, EN_ONPSERR *penErr);
入口参数
- socket:socket句柄
- penErr:指向错误编码的指针,函数执行失败时该参数用于接收实际的错误码
返回值
0:未连接 1:已连接-1:函数执行失败,具体的错误信息通过参数penErr获得
示例
EN_ONPSERR enErr;
……
INT nRtnVal = is_tcp_connected(hSocket, &enErr);
if(nRtnVal > 0)
printf("已连接\r\n");
else if(!nRtnVal)
printf("未连接\r\n");
else
printf("检查失败,%s\r\n", onps_error(enErr));
……
send
功能
发送数据到目标地址。注意tcp链路下为阻塞型,直至收到对端的tcp层ack报文或超时才会返回。udp链路下为非阻塞型,且只有在调用connect()函数后才能使用这个函数。
原型
INT send(SOCKET socket, UCHAR *pubData, INT nDataLen, INT nWaitAckTimeout);
入口参数
- socket:socket句柄
- pubData:指向要发送的数据的指针
- nDataLen:要发送的数据的长度,单位:字节
- nWaitAckTimeout:仅用于tcp链路,指定发送超时时间,单位:秒,如参数值不大于0,则协议栈采用系统缺省值,该值由TCP_ACK_TIMEOUT宏指定(参见sys_config.h);udp链路未使用,可指定任意值
返回值
发送成功,则返回值等于nDataLen;发送失败,返回值不等于nDataLen,具体的错误信息通过onps_get_last_error()获得。
示例
/* tcp链路下send()函数使用示例 */
EN_ONPSERR enErr;
……
UCHAR ubUserData[128];
INT nSndBytes, nSndNum = 0;
__lblSend:
if(nSndNum > 2)
{
//* 超出重传次数,不再重传,可以关闭当前tcp链路重连tcp服务器或者你自己的其它处理方式
……
return;
}
nSndBytes = send(hSocket, ubUserData, sizeof(ubUserData), 3);
if(sizeof(ubUserData) == nSndBytes)
{
//* 发送成功,在这里添加你自己的处理代码
……
}
else
{
const CHAR *pszErr = onps_get_last_error(hSocket, &enErr);
if(enErr == ERRTCPACKTIMEOUT) //* 等待tcp层的ack报文超时
{
//* 数据重传,用户层实现tcp层重传机制
nSndNum++;
goto __lblSend;
}
else //* 其它错误,意味着底层协议栈捕捉到了内存不够用、网卡故障等类似的严重问题
{
//* 没必要触发重传机制了,根据你自己的具体情形增加容错处理代码并打印错误信息
……
printf("发送失败,%s\r\n", pszErr);
}
}
……
send_nb
功能
发送数据到目标地址,非阻塞型,其它与send函数完全相同。
原型
INT send_nb(SOCKET socket, UCHAR *pubData, INT nDataLen);
入口参数
- socket:socket句柄
- pubData:指向要发送的数据的指针
- nDataLen:要发送的数据的长度,单位:字节
返回值
发送成功,则返回值等于nDataLen;返回值为0,上一组数正处于发送中(尚未收到对端的tcp ack报文),需要等待其发送成功后再发送当前数据;发送失败,返回值小于等于0,具体的错误信息通过onps_get_last_error()获得。
示例
/* tcp链路下send_nb()函数使用示例 */
EN_ONPSERR enErr;
……
UCHAR ubUserData[128];
INT nRtnVal;
__lblSend:
nRtnVal = send_nb(hSocket, ubUserData, sizeof(ubUserData));
if(sizeof(ubUserData) == nRtnVal)
{
//* 调用is_tcp_send_ok()函数等待是否已成功送达对端,或者(同时)做点别的事情
……
}
else if(0 == nRtnVal)
{
//* 上一组数据尚未发送完毕,需要等待发送完毕后再发送当前数据,等待期间你可以在这里做点别的事情
……
goto __lblSend;
}
else
{
//* 发送失败,协议栈底层捕捉到了一个严重的系统错误,这里增加你的容错代码并打印错误信息,不再继续发送
……
printf("发送失败,%s\r\n", onps_get_last_error(hSocket, NULL));
}
……
is_tcp_send_ok
功能
非阻塞型,数据是否已成功送达tcp链路的对端(已收到对端回馈的tcp ack报文)。
原型
INT is_tcp_send_ok(SOCKET socket);
入口参数
- socket:socket句柄
返回值
0:发送中 1:发送成功-1:发送失败,具体错误信息通过onps_get_last_error()函数获得
示例
EN_ONPSERR enErr;
……
UCHAR ubUserData[128];
INT nRtnVal;
__lblSend:
nRtnVal = send_nb(hSocket, ubUserData, sizeof(ubUserData));
if(sizeof(ubUserData) == nRtnVal)
{
//* 数据已通过网卡成功送出,接下来轮询等待对端回馈的tcp ack报文,确保数据成功送达对端
while(1)
{
INT nResult = is_tcp_send_ok(hSocket);
if(nResult == 1)
{
//* 发送成功了,退出轮询等待
break;
}
else if(nResult < 0)
{
//* 协议栈底层捕捉到了一个严重的系统错误,不再轮询等待,并打印错误信息
……
printf("%s\r\n", onps_get_last_error(hSocket, NULL));
break;
}
//* 发送中,在这里你可以做点别的事情
……
}
……
}
else if(0 == nRtnVal)
{
//* 上一组数据尚未发送完毕,需要等待发送完毕后再发送当前数据,等待期间你可以在这里做点别的事情
……
goto __lblSend;
}
else
{
//* 发送失败,协议栈底层捕捉到了一个严重的系统错误,这里打印错误信息,不再继续发送
printf("发送失败,%s\r\n", onps_get_last_error(hSocket, NULL));
}
……
sendto
功能
非阻塞型,仅用于udp通讯,发送数据到指定的目标地址。
原型
INT sendto(SOCKET socket, const CHAR *dest_ip, USHORT dest_port, UCHAR *pubData, INT nDataLen);
入口参数
- socket:socket句柄
- dest_ip:目标地址
- dest_port:目标端口
- pubData:指向要发送的数据的指针
- nDataLen:要发送的数据的长度,单位:字节
返回值
发送成功,返回值等于nDataLen,反之则发送失败,具体的错误信息通过onps_get_last_error()获得。
示例
EN_ONPSERR enErr;
……
UCHAR ubUserData[128];
INT nSndBytes = sendto(hSocket, "47.92.239.107", 6411, ubUserData, sizeof(ubUserData));
if(sizeof(ubUserData) == nSndBytes)
{
//* 发送成功,在这里增加你自己的业务代码
……
}
else
{
//* 发送失败,在这里增加你自己的容错代码并打印错误信息
……
printf("发送失败,%s\r\n", onps_get_last_error(hSocket, NULL));
}
……
recv
功能
读取链路对端发送的数据。其阻塞类型取决于socket_set_rcv_timeout()函数设定的接收等待时长。缺省为阻塞型,一直等待直至收到数据或报错。
原型
INT recv(SOCKET socket, UCHAR *pubDataBuf, INT nDataBufSize);
入口参数
- socket:socket句柄
- pubDataBuf:指向数据接收缓冲区的指针
- nDataBufSize:数据接收缓冲区的大小,单位:字节
返回值
大于等于0为实际到达的数据长度,单位:字节;小于0,接收失败,具体的的错误信息通过onps_get_last_error()获得。
示例
EN_ONPSERR enErr;
……
UCHAR ubRcvBuf[1500];
INT nRcvBytes = recv(hSocket, ubRcvBuf, sizeof(ubRcvBuf));
if(nRcvBytes > 0) //* 收到数据
{
……
}
else
{
if(nRcvBytes < 0) //* 协议栈底层捕捉到了一个严重错误,在这里增加你的容错代码并打印错误信息
{
……
printf("%s\r\n", onps_get_last_error(hSocket, NULL));
}
}
……
socket_set_rcv_timeout
功能
设定recv()函数接收等待的时长。其设定的接收等待时长决定了recv()函数的阻塞类型。等于0:非阻塞,recv()不等待立即返回大于0:阻塞,recv()等待指定时长直至数据到达或超时小于0:阻塞,recv()一直等待直至数据到达或出错
原型
BOOL socket_set_rcv_timeout(SOCKET socket, CHAR bRcvTimeout, EN_ONPSERR *penErr);
入口参数
- family:目前仅支持IPv4地址,即AF_INET,其它地址族如AF_INET6之类的不支持
- bRcvTimeout:recv()函数的接收等待时长,单位:秒
- penErr:指向错误编码的指针,当socket()函数执行失败,该参数用于接收实际的错误码
返回值
设置成功返回TRUE,否则返回FALSE,具体的错误信息通过参数penErr获得。
示例
EN_ONPSERR enErr;
……
if(!socket_set_rcv_timeout(hSocket, 1, &enErr))
{
//* 设置失败,打印错误信息,此时系统采用缺省值,即recv()函数一直等待直至收到数据或协议栈报错
printf("%s\r\n", onps_error(enErr));
}
……
recvfrom
功能
接收数据并返回数据源的地址信息,仅用于udp通讯。
原型
INT recvfrom(SOCKET socket, UCHAR *pubDataBuf, INT nDataBufSize, in_addr_t *punFromIP, USHORT *pusFromPort);
入口参数
- family:目前仅支持IPv4地址,即AF_INET,其它地址族如AF_INET6之类的不支持
- pubDataBuf:指向数据接收缓冲区的指针
- nDataBufSize:数据接收缓冲区的大小,单位:字节
- punFromIP:指向数据源ip地址的指针
- pusFromPort:指向数据源端口的指针
返回值
实际收到的数据的长度,单位:字节;小于0则接收失败,具体的的错误信息通过onps_get_last_error()获得。
示例
EN_ONPSERR enErr;
……
UCHAR ubRcvBuf[512];
in_addr_t unFromIp;
USHORT usFromPort;
INT nRcvBytes = recvfrom(hSocket, ubRcvBuf, sizeof(ubRcvBuf), &unFromIp, &usFromPort);
if(nRcvBytes > 0) //* 收到数据
{
CHAR szAddr[20];
printf("收到来自地址%s:%d的%d字节的数据\r\n", inet_ntoa_safe_ext(unFromIp, szAddr), usFromPort, nRcvBytes);
……
}
else
{
if(nRcvBytes < 0) //* 协议栈底层捕捉到了一个严重错误,在这里增加你的容错代码并打印错误信息
{
……
printf("发送失败,%s\r\n", onps_get_last_error(hSocket, NULL));
}
}
……
bind
功能
绑定一个ip地址和端口。
原型
INT bind(SOCKET socket, const CHAR *pszNetifIp, USHORT usPort);
入口参数
- family:目前仅支持IPv4地址,即AF_INET,其它地址族如AF_INET6之类的不支持
- pszNetifIp:指向要绑定的ip地址的指针,为NULL绑定任意网络接口
- usPort: 要绑定的端口
返回值
0:成功-1:失败,具体的的错误信息通过onps_get_last_error()获得
示例
EN_ONPSERR enErr;
……
SOCKET hSockSrv = socket(AF_INET, SOCK_STREAM, 0, &enErr);
if(INVALID_SOCKET != hSockSrv)
{
if(!bind(hSockSrv, NULL, 6411)) //* 绑定成功
……
else //* 绑定失败
printf("%s\r\n", onps_get_last_error(hSocket, NULL)); //* 打印错误信息
}
else
printf("%s\r\n", onps_error(enErr)); //*打印错误信息
……
listen
功能
tcp服务器进入监听状态,等待tcp客户端连接请求的到达。
原型
INT listen(SOCKET socket, USHORT backlog);
入口参数
- family:目前仅支持IPv4地址,即AF_INET,其它地址族如AF_INET6之类的不支持
- backlog:等待用户层接受(accept)连接请求的tcp客户端数量
返回值
0:成功-1:失败,具体的的错误信息通过onps_get_last_error()获得
示例
EN_ONPSERR enErr;
……
SOCKET hSockSrv = socket(AF_INET, SOCK_STREAM, 0, &enErr);
if(INVALID_SOCKET != hSockSrv)
{
if(!bind(hSockSrv, NULL, 6411)) //* 绑定成功
{
if(!listen(hSockSrv, usBacklog)) //* 进入监听状态
{
……
}
else //* 失败
printf("%s\r\n", onps_get_last_error(hSocket, NULL)); //* 打印错误信息
}
else //* 绑定失败
printf("%s\r\n", onps_get_last_error(hSocket, NULL)); //* 打印错误信息
}
else
printf("%s\r\n", onps_error(enErr)); //*打印错误信息
……
accept
功能
阻塞/非阻塞型,接受一个到达的tcp连接请求。
原型
SOCKET accept(SOCKET socket, in_addr_t *punCltIP, USHORT *pusCltPort, INT nWaitSecs, EN_ONPSERR *penErr);
入口参数
- family:目前仅支持IPv4地址,即AF_INET,其它地址族如AF_INET6之类的不支持
- punCltIP:指向tcp客户端Ip地址的指针
- pusCltPort:指向tcp客户端端口的指针
- nWaitSecs:指定等待时长,单位:秒。0,不等待,立即返回;大于0,等待指定时间直至收到一个客户端连接请求或超时;小于0,一直等待,直至收到一个客户端连接请求或协议栈报错
- penErr:指向错误编码的指针,函数执行失败时该参数用于接收实际的错误码
返回值
返回请求连接的tcp客户端的socket句柄;当没有新的客户端连接请求到达或协议栈报错时返回INVALID_SOCKET,具体的错误码通过penErr参数获得。
示例
EN_ONPSERR enErr;
……
SOCKET hSockSrv = socket(AF_INET, SOCK_STREAM, 0, &enErr);
if(INVALID_SOCKET != hSockSrv)
{
if(!bind(hSockSrv, NULL, 6411)) //* 绑定成功
{
if(!listen(hSockSrv, usBacklog)) //* 进入监听状态
{
//* 循环等待并处理到达的tcp连接请求
while(1)
{
in_addr_t unCltIP;
USHORT usCltPort;
SOCKET hSockClt = accept(hSockSrv, &unCltIP, &usCltPort, 1, &enErr);
if(INVALID_SOCKET != hSockClt) //* 返回了一个有效的客户端socket句柄
{
//* 新的客户端到达,在这里增加你的自定义代码
……
}
else
{
//* 错误码为ERRNO代表无错误发生,意味着没有新的客户端连接请求到达,回到循环开始处继续等待即可
if(ERRNO == enErr)
continue;
else //* 不等于ERRNO意味着协议栈报错,需要处理
{
……
printf("%s\r\n", onps_error(enErr)); //* 打印错误信息
}
}
}
}
else //* 失败
printf("%s\r\n", onps_get_last_error(hSocket, NULL)); //* 打印错误信息
}
else //* 绑定失败
printf("%s\r\n", onps_get_last_error(hSocket, NULL)); //* 打印错误信息
}
else
printf("%s\r\n", onps_error(enErr)); //*打印错误信息
……
tcpsrv_recv_poll
功能
阻塞/非阻塞型,tcp服务器数据接收专用函数,等待任意一个或多个tcp客户端数据到达信号。协议栈利用rtos提供的信号量实现了一个poll模型,当有一个及以上的tcp客户端数据到达,均会触发一个信号到用户层,我们通过tcpsrv_recv_poll()函数等待这个信号。这个函数的第二个参数值表示这个函数最长等待多少秒,等待期间有任意一个或多个客户端数据到达则立即返回最先到达的这个客户端的socket,继续调用这个函数则继续返回下一个客户端socket,直至返回一个无效的socket才意味着当前所有已送达的数据均已读取完毕,已经没有任何客户端有新数据到达了。
原型
SOCKET tcpsrv_recv_poll(SOCKET hSocketSrv, INT nWaitSecs, EN_ONPSERR *penErr);
入口参数
- hSocketSrv:tcp服务器的socket句柄
- nWaitSecs:等待时长,单位:秒。0,不等待,立即返回;大于0,等待指定时间直至收到一个/多个客户端数据到达信号或超时;小于0,一直等待,直至收到一个/多个客户端数据到达信号或协议栈报错
- penErr:指向错误编码的指针,函数执行失败时该参数用于接收实际的错误码
返回值
返回已经收到数据的tcp客户端的socket句柄;当没有任何tcp客户端收到数据或协议栈报错时返回INVALID_SOCKET,具体的错误码通过penErr参数获得。
示例
//* 完成tcp服务器的数据读取工作
void tcp_server_recv(void *pvData)
{
SOCKET hSockClt;
EN_ONPSERR enErr;
INT nRcvBytes;
UCHAR ubaRcvBuf[100];
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));
}
}
}
socket_get_last_error/onps_get_last_error
功能
获取socket最近一次发生的错误,包括描述信息及错误编码。该函数其实是前面示例代码中出现的onps_get_last_error()函数的二次封装,功能及使用方式与之完全相同。
原型
const CHAR *socket_get_last_error(SOCKET socket, EN_ONPSERR *penErr);
入口参数
- socket:socket句柄
- penErr:指向错误编码的指针,该参数用于接收实际的错误码
返回值
返回值为字符串指针,指向socket最近一次发生的错误描述字符串。
示例
略。
socket_get_last_error_code
功能
获取socket最近一次发生的错误编码。
原型
EN_ONPSERR socket_get_last_error_code(SOCKET socket);
入口参数
- socket:socket句柄
返回值
返回值为socket最近一次发生的错误编码。
示例
略。
3. 常用工具函数
协议栈还提供了一组网络编程常见的工具函数以供用户使用,同时还提供了一些常用的比如字符串操作、16进制格式化转换输出等函数:
- htonxx系列:网络字节序转换函数
- inet_xx系列:网络地址转换函数
- ip_addressing:检查ip地址是否在同一网段
- strtok_safe:线程安全的strtok函数
- snprintf_hex:将16进制数据格式化转换成字符串
- printf_hex:将16进制数据格式化转换成字符串后输出到控制台
- onps_error:将协议栈返回的错误码转换成具体的描述字符串
htonll
功能
实现64位长整型数的网络字节序转换。
原型
LONGLONG htonll(LONGLONG llVal);
入口参数
- llVal:64位长整型数
返回值
返回值为字节序转换后的64位长整型数。
示例
略。
htonl
功能
实现32位整型数的网络字节序转换。
原型
LONG htonl(LONG lVal);
入口参数
- lVal:32整型数
返回值
返回值为字节序转换后的32位整型数。
示例
略。
htons
功能
实现16位短整型数的网络字节序转换。
原型
SHORT htonl(SHORT sVal);
入口参数
- sVal:16位短整型数
返回值
返回值为字节序转换后的16位短整型数。
示例
略。
inet_addr
功能
实现点分十进制1Pv4地址到4字节无符号整型地址的转换,即10.0.1.2转换为0x0A000102。
原型
in_addr_t inet_addr(const char *pszIP);
入口参数
- pszIP:指向点分十进制IPv4地址字符串的指针
返回值
返回值为无符号32位整型地址。
示例
略。
inet_addr_small
功能
实现点分十进制1Pv4地址到4字节无符号整型地址的转换,即10.0.1.2转换为0x0201000A。
原型
in_addr_t inet_addr_small(const char *pszIP);
入口参数
- pszIP:指向点分十进制IPv4地址字符串的指针
返回值
返回值为无符号32位整型地址。
示例
略。
inet_ntoa
功能
注意,这是一个线程不安全的函数,实现in_addr类型的地址到点分十进制1Pv4地址的转换。
原型
char *inet_ntoa(struct in_addr stInAddr);
入口参数
- stInAddr:指向in_addr类型的IPv4地址的指针
返回值
返回字符串指针,指向转换后的点分十进制格式的IPv4地址字符串。
示例
struct in_addr stAddr;
stSrcAddr.s_addr = inet_addr_small("192.168.0.9");
printf("%s\r\n", inet_ntoa(stAddr));
inet_ntoa_ext
功能
注意,这是一个线程不安全的函数,实现4字节无符号整型地址到点分十进制1Pv4地址的转换。
原型
char *inet_ntoa_ext(in_addr_t unAddr);
入口参数
- unAddr:要转换的IPv4地址,4字节无符号整型格式
返回值
返回字符串指针,指向转换后的点分十进制格式的IPv4地址字符串。
示例
in_addr_t unAddr = inet_addr_small("192.168.0.9");
printf("%s\r\n", inet_ntoa_ext(unAddr));
inet_ntoa_safe
功能
注意,这是一个线程安全的函数,实现in_addr类型的地址到点分十进制1Pv4地址的转换。
原型
char *inet_ntoa_safe(struct in_addr stInAddr, char *pszAddr);
入口参数
- stInAddr:指向in_addr类型的IPv4地址的指针
- pszAddr:指向转换后的点分十进制IPv4地址字符串的指针
返回值
返回字符串指针,指向转换后的点分十进制格式的IPv4地址字符串,其地址其实就是参数pszAddr指向的地址。
示例
CHAR szAddr[20];
struct in_addr stAddr;
stSrcAddr.s_addr = inet_addr_small("192.168.0.9");
printf("%s\r\n", inet_ntoa_safe(stAddr, szAddr));
inet_ntoa_safe_ext
功能
注意,这是一个线程安全的函数,实现4字节无符号整型地址到点分十进制1Pv4地址的转换。
原型
char *inet_ntoa_safe_ext(in_addr_t unAddr, char *pszAddr);
入口参数
- unAddr:要转换的IPv4地址,4字节无符号整型格式
- pszAddr:指向转换后的点分十进制IPv4地址字符串的指针
返回值
返回值为字符串指针,指向转换后的点分十进制格式的IPv4地址字符串,其地址其实就是参数pszAddr指向的地址。
示例
CHAR szAddr[20];
in_addr_t unAddr = inet_addr_small("192.168.0.9");
printf("%s\r\n", inet_ntoa_safe_ext(unAddr, szAddr));
ip_addressing
功能
比较两个IPv4地址是否属于同一网段。
原型
BOOL ip_addressing(in_addr_t un1stIp, in_addr_t un2ndIp, in_addr_t unGenmask);
入口参数
- un1stIp:第一个被比较的IPv4地址
- un2ndIp:第二个被比较的IPv4地址
- unGenmask:子网掩码
返回值
返回TRUE表示在同一网段,FALSE则不属于同一网段。
示例
略
strtok_safe
功能
线程安全的strtok函数。
原型
CHAR *strtok_safe(CHAR **ppszStart, const CHAR *pszSplitStr);
入口参数
- ppszStart:指向下一个要被截取的字符串片段的指针的指针
- pszSplitStr:指向分隔符的指针
返回值
返回字符串指针,指向下一个分隔符之前的字符串;返回值为NULL则截取完毕。
示例
CHAR szTestStr[64];
sprintf(szTestStr, "123;456;789,ABC;,EFG");
CHAR *pszStart = szTestStr;
CHAR *pszItem = strtok_safe(&pszStart, ";");
while(NULL != pszItem)
{
printf("%s\r\n", pszItem);
pszItem = strtok_safe(&pszStart, ";");
}
snprintf_hex
功能
将16进制数据格式化为字符串。
原型
void snprintf_hex(const UCHAR *pubHexData, USHORT usHexDataLen, CHAR *pszDstBuf, UINT unDstBufSize, BOOL blIsSeparate);
入口参数
- pubHexData:指向16进制数据的指针
- usHexDataLen:16进制数据的长度
- pszDstBuf:指向接收缓冲区的指针,其用于接收格式化后的字符串
- unDstBufSize:接收缓冲区的长度,单位:字节
- blIsSeparate:格式化后的字符串在两个16进制数据之间是否增加空格,即2A 1F还是2A1F
返回值
无
示例
UCHAR ubHexData[16] = "\xAB\xCD\x2A\x1F\x3C\x4D";
CHAR szHexDataStr[64];
snprintf_hex(ubHexData, 6, szHexDataStr, 64, TRUE);
printf("%s\r\n", szHexDataStr);
printf_hex
功能
将16进制数据格式化为字符串输出到控制台。
原型
void printf_hex(const UCHAR *pubHexData, USHORT usHexDataLen, UCHAR ubBytesPerLine);
入口参数
- pubHexData:指向16进制数据的指针
- usHexDataLen:16进制数据的长度
- ubBytesPerLine:每行固定输出多少 字节的数据,比如16字节一行
返回值
无
示例
UCHAR ubHexData[16] = "\xAB\xCD\x2A\x1F\x3C\x4D\xAA\x4E\xFE\x45\x6B\x9A\x05\x71\x8E\x1B\x52\x78";
printf_hex(ubHexData, 18, 16);
onps_error
功能
将协议栈返回的错误码转换成具体的描述字符串。
原型
const CHAR *onps_error(EN_ONPSERR enErr);
入口参数
- enErr:错误编码
返回值
返回值为字符串指针,指向具体的错误描述字符串的指针。
示例
略
标签:
留言评论