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:錯誤編碼
回傳值
回傳值為字串指標,指向具體的錯誤描述字串的指標,
示例
略
回傳目錄
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/530496.html
標籤:嵌入式
上一篇:TEE學習(一) OP-TEE
下一篇:TEE學習(一) OP-TEE
