1. 程式人生 > 實用技巧 >一個同步阻塞的udp封裝(c++完整版)

一個同步阻塞的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_recvrecv_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_ip4open 的用法, 我用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、目前,macWindows已經通過編譯,且收發成功。

  C 、專案地址: lib_udp