一個同步阻塞的udp封裝(c++完整版)
0、支援
A、Windows: 目前只有win10開發機, 編譯測試已經通過
B、Mac osx: 編譯及測試已經通過
C、Windows: 生成 C++標準動態庫、MFC動態庫,
D、支援靜態庫,但靜態庫沒有測試。
1、序言
這是一個簡單的封裝, 同步阻塞。 相對非同步非阻塞IO,效率低下。 用到了C++11相關的特性。 目前僅支援IPv4, ipv6正在更新
專案引用了spdlog庫: https://github.com/gabime/spdlog
使用步驟j:
A、初始化
B、open
C 、發/收
D、 shutdown
2、引數解釋
A、初始化引數
做了一個包,目的是為了避免不可知的錯誤,結構如下:
1 struct udp_param 2 { 3 private: 4 enum 5 { 6 ip4_len_16 = 16, 7 }; 8 9 public: 10 // 是否接收自己發出的訊息 11 bool _recv_loop = false; 12 13 // 是否將錯誤資訊記錄到檔案 14 bool _is_log_debug = false; 15 16 // 組播? 單薄?廣播? 17 udp_cast_type _cast_type = udp_multi_cast; 18 19 // 目標埠 20 unsigned short _port_dst = 20086; 21 22 // 目標地址 23 char _pip4_dst[ip4_len_16] = { "233.3.3.3" }; 24 25 // 本機地址 26 char _pip4_local[ip4_len_16] = { "127.0.0.1" }; 27 28 29 public: 30 void zero() 31 { 32 memset(this, 0, sizeof(udp_param)); 33 } 34 35 udp_param() 36 { 37 zero(); 38 } 39 };
這裡包括了 引數: 是否接收自己發出的訊息、目標地址、本機IP、何種方式建立UDP(組播?單薄?廣播?)
這裡,udp_cast_type 的定義:
1 /* 2 * how to use udp socket 3 */ 4 enum udp_cast_type 5 { 6 udp_multi_cast = 1, // multi cast 7 udp_uni_cast = 2, // uni cast 8 udp_broad_cast = 3, // broad cast 9 };
3、函式詳解
A、init_ip4
初始化udp相關引數,需要用到上面的結構體,成功,返回0。 若不是0,
- 返回1 : 目標地址為空
-返回2: 目標地址長度不正確, 最少長度應為7
1 /* @ brief: to check parameters 2 * @ upd_param & param - parameters of udp 3 * @ return - int 4 0 - success 5 1 - faluere, param._port_dst is zero. 6 2 - faluere, param._pip4_dst's length is less than 7 7 */ 8 virtual int init_ip4(udp_param& param) = 0;
B、open
上一步傳入引數,這一步是為了初始化套接字udp相關引數,第一個引數為:接收發送超時, 第二個引數為接收資料需要用的物件(參看下一節)。
--返回0 - 成功
-- 返回20086 - 錯誤,udpl的型別udp_cast_type指定失敗
-- 返回其他-- 錯誤程式碼,初始化套接字udp失敗時的系統錯誤程式碼 。
/* @ brief: initialize socket of udp * @ const unsigned int time_out_send - sending time out. * @ return - int 0 - success 20086 - failure, the parameter's type of udp_cast_type gets wrong X - faluere, the error's id to call setsockopt function */ virtual int open(const unsigned int time_out_send, udpsocket_recv* pfunc_recv = nullptr) = 0;
C、send
傳送資料時,呼叫的函式,第一個引數時: 傳送內容, 第二個引數: 傳送的資料長度
---返回 -20086 - 失敗,套接字沒有初始化
--- X >= 0 成功, 表示成功傳送的位元組數
--- X < 0 . 傳送失敗,返回系統錯誤程式碼, X就是錯誤程式碼。
1 /* @ brief: send data after initializing success 2 * @ const char * psend - the data to send 3 * @ const int len_send - the length of sending data 4 * @ return - int 5 -20086 - failure, initialize socket failure 6 X> 0 - success, X is length of sending success 7 X < 0 - failure, X is error msg's id of calling sendto 8 */ 9 virtual int send(const char *psend, const unsigned int len_send) = 0;
D、shutdown
釋放初始化的資源。
---返回0 - 成功
--- 返回-20086 , 失敗,套接字沒有初始化
--- 返回 X, 失敗,X表示系統錯誤程式碼
1 /* @ brief: close socket 2 * @ return - int 3 0 - success 4 -20086 - failure, udp doesnt open 5 X - failure, error msg's id 6 */ 7 virtual int shutdown() = 0;
E、建立 udp_create
將會返回一個指標,若返回 NULL, 則表示建立失敗
udpsocket* udp_create()
F、釋放 udp_release
釋放上面建立的udpsocket物件, 並將指標置為NULL
void udp_release(udpsocket* pudp);
說明: 需要顯示 呼叫該函式, 若不是使用 智慧指標,且不顯示呼叫該函式,則會記憶體洩漏。
4、接收
A、需要重寫父類udpsocket_recv 的recv_data 函式。 目前的接收緩衝區長度為10k - 1, 超過10k - 1, 則丟棄
函式的第一個引數:收到的資料的緩衝區
函式的第二個引數: 收到資料的長度
1 /* @ brief: when socket has received data, it will call this function to pass data 2 * @ char * pdata_recv - data received 3 * @ unsigned int recv_data_len - the received length 4 * @ return - void 5 6 */ 7 virtual void recv_data(char *pdata_recv, unsigned int recv_data_len) = 0;
B、初始化傳入接收物件
上面的函式open的第二個引數需要傳遞 的就是udpsocket_recv的一個物件。 使用: 自己新建一個類 繼承udpsocket_recv
例如:
1 class my_udp : public udpsocket_recv 2 { 3 public: 4 explicit my_udp(); 5 virtual ~my_udp(); 6 7 // ------------------------------------------------------------------------------- 8 int init_ip4(udp_param& param); 9 int open(const unsigned int time_out_send); 10 int send(const char *psend, const unsigned int len_send); 11 int shutdown(); 12 void recv_data(char *pdata_recv, unsigned int recv_data_len); 13 14 private: 15 udpsocket * _pudp = nullptr; 16 };
my_udp 繼承udpsocket_recv ,並實現了介面 recv_data ,這樣,當底層收到資料,my_udp::recv_data將會收到底層收到的資料。
怎麼傳入? 需要上面的函式 open . 這裡以 my_udp 為例。
1 /* 2 * @brief: 3 */ 4 int my_udp::open(const unsigned int time_out_send) 5 { 6 if (!_pudp) 7 return -20000; 8 9 return _pudp->open(time_out_send, this); 10 }
這裡, open的第二個引數傳遞: this, 這樣就OK拉。
5、收發示例
我這裡演示: 自己 已經把udp做好了動態庫,做一個類實現收發
這裡,需要接收資料,就需要繼承udpsocket_recv,並實現函式recv_data .
還是以上面的my_udp為例。 下面這個例子是用VS2015 up3寫的。其中用到了 TRACE
A 、my_udp.h
1 #pragma once 2 #include "include/udpsocket_interface.h" 3 #include <memory> 4 5 6 using namespace oct_udp; 7 8 class my_udp : public udpsocket_recv 9 { 10 public: 11 explicit my_udp(); 12 virtual ~my_udp(); 13 14 // ------------------------------------------------------------------------------- 15 int init_ip4(udp_param& param); 16 int open(const unsigned int time_out_send); 17 int send(const char *psend, const unsigned int len_send); 18 int shutdown(); 19 void recv_data(char *pdata_recv, unsigned int recv_data_len); 20 21 private: 22 udpsocket * _pudp = nullptr; 23 };
B、udp.cpp
1 #include "stdafx.h" 2 #include "my_udp.h" 3 4 #pragma comment(lib, "lib/mfc_lib_udp_shared.lib") 5 6 7 my_udp::my_udp() 8 { 9 _pudp = udp_wsa_create(); 10 } 11 12 13 my_udp::~my_udp() 14 { 15 16 } 17 18 /* 19 * @brief: 20 */ 21 int my_udp::init_ip4(udp_param& param) 22 { 23 if (!_pudp) 24 return -20000; 25 26 return _pudp->init_ip4(param); 27 } 28 29 /* 30 * @brief: 31 */ 32 int my_udp::open(const unsigned int time_out_send) 33 { 34 if (!_pudp) 35 return -20000; 36 37 return _pudp->open(time_out_send, this); 38 } 39 40 /* 41 * @brief: 42 */ 43 int my_udp::send(const char *psend, const unsigned int len_send) 44 { 45 if (!_pudp) 46 return -20000; 47 48 return _pudp->send(psend, len_send); 49 } 50 51 /* 52 * @brief: 53 */ 54 int my_udp::shutdown() 55 { 56 if (!_pudp) 57 return -20000; 58 59 int ret_val = _pudp->shutdown(); 60 udp_release(_pudp); 61 62 return ret_val; 63 } 64 65 /* 66 * @brief: 67 */ 68 void my_udp::recv_data(char *pdata_recv, unsigned int recv_data_len) 69 { 70 #ifdef _DEBUG 71 TRACE("\n------------- recv data: %d ---------------------------\n", recv_data_len); 72 for (int i = 0; i < recv_data_len; i++) 73 { 74 TRACE("\n%.2X ", pdata_recv[i]); 75 } 76 #endif // 77 }
C、初始化
類已經準備好了。下面開始初始化, 看註釋。
1 udp_param param; 2 3 // 記錄錯誤資訊到檔案 4 param._is_log_debug = true; 5 // 指定為組播(多播) 6 param._cast_type = udp_multi_cast; 7 8 // 組播(多播)埠 9 param._port_dst = 12345; 10 11 // 是否接收自己發出的訊息 12 param._recv_loop = true; 13 14 // 本機IP4 15 char arr[] = "10.1.1.5"; 16 // 目標地址IP4 17 char arr_dst[] = "233.0.0.11"; 18 19 // 20 memcpy(param._pip4_dst, arr_dst, strlen(arr_dst)); 21 memcpy(param._pip4_local, arr, strlen(arr));
D、init_ip4 和 open 的用法, 我用MFC建立專案,做的測試
1 // 初始化引數 2 int ret_val = _pudp->init_ip4(param); 3 if (0 != ret_val) 4 { 5 AfxMessageBox(L"初始化失敗"); 6 return TRUE; 7 } 8 9 // 開啟UDP 10 ret_val = _pudp->open(10); 11 if (0 != ret_val) 12 { 13 AfxMessageBox(L"OPEN失敗"); 14 CString str; 15 str.Format(L"%d", ret_val); 16 AfxMessageBox(str); 17 return TRUE; 18 }
E、傳送資料
1 if (_pudp) 2 { 3 ret = _pudp->send(_arr_send, 16); 4 TRACE("\n\n 第%d傳送,傳送結果:%d\n\n", index++, ret); 5 }
F、接收資料
還記得上面的 my_udp類? 找到函式recv_data.
1 TRACE("\n------------- recv data: %d ---------------------------\n", recv_data_len); 2 for (int i = 0; i < recv_data_len; i++) 3 { 4 TRACE("\n%.2X ", pdata_recv[i]); 5 }
G、釋放
呼叫函式 shutdown,釋放套接字udp初始化的資源。
1 if (_pudp) 2 _pudp->shutdown();
如果不是用的c++11的智慧指標。則需要顯示呼叫函式 udp_release 該函式,釋放後,將指標設定為NULL。
6、說明
A、目前僅支援 IPV4, IPV6正在路上
B、目前,mac和Windows已經通過編譯,且收發成功。
C 、專案地址: lib_udp