在Windows環境下實現一個簡單的libevent伺服器

来源:http://www.cnblogs.com/zkhm123/archive/2016/10/19/5976061.html
-Advertisement-
Play Games

最近再學習Libevent由於自己使用的是windows系統,遺憾的是有關在vs下可以參考的程式少之又少。在參考了許多的博客文章後。自己摸索寫了一個簡單的Libevent Server程式。並且在網上找了一個簡單的客戶端程式,測試該代碼成功。今天在此做一個記錄。 Libevent的確是一個非常好用的 ...


  最近再學習Libevent由於自己使用的是windows系統,遺憾的是有關在vs下可以參考的程式少之又少。在參考了許多的博客文章後。自己摸索寫了一個簡單的Libevent Server程式。並且在網上找了一個簡單的客戶端程式,測試該代碼成功。今天在此做一個記錄。

Libevent的確是一個非常好用的東西,還在繼續學習中,後續還要在windows下實現Libevent的多線程使用。今天先把自己搞出來的東西貼上來,僅供學習參考。在vs2015上編譯通過。

 

  預設情況下是單線程的(可以配置成多線程,如果有需要的話),每個線程有且只有一event base,對應一個struct event_base結構體(以及附於其上的事件管理器),用來schedule托管給它的一系列event,可以和操作系統的進程管理類比,當然,要更簡單一點。當一個事件發生後,event_base會在合適的時間(不一定是立即)去調用綁定在這個事件上的函數(傳入一些預定義的參數,以及在綁定時指定的一個參數),直到這個函數執行完,再返回schedule其他事件。

 

//創建一個event_base

struct event_base *base = event_base_new();

assert(base != NULL);

 

event_base內部有一個迴圈,迴圈阻塞在epoll / kqueue等系統調用上,直到有一個 / 一些事件發生,然後去處理這些事件。當然,這些事件要被綁定在這個event_base上。每個事件對應一個struct event,可以是監聽一個fd或者POSIX信號量之類(這裡只講fd了,其他的看manual吧)。struct event使用event_new來創建和綁定,使用event_add來啟用:

 

//創建並綁定一個event

struct event *listen_event;

//參數:event_base, 監聽的fd,事件類型及屬性,綁定的回調函數,給回調函數的參數

 

listen_event = event_new(base, listener, EV_READ | EV_PERSIST, callback_func, (void*)base);

//參數:event,超時時間(struct timeval *類型的,NULL表示無超時設置)

event_add(listen_event, NULL);

 

註:libevent支持的事件及屬性包括(使用bitfield實現,所以要用 | 來讓它們合體)

(a) EV_TIMEOUT: 超時

(b) EV_READ : 只要網路緩衝中還有數據,回調函數就會被觸發

(c) EV_WRITE : 只要塞給網路緩衝的數據被寫完,回調函數就會被觸發

(d) EV_SIGNAL : POSIX信號量,參考manual吧

(e) EV_PERSIST : 不指定這個屬性的話,回調函數被觸發後事件會被刪除

(f) EV_ET : Edge - Trigger邊緣觸發,參考EPOLL_ET

然後需要啟動event_base的迴圈,這樣才能開始處理髮生的事件。迴圈的啟動event base dispatch,迴圈將一直持續,直到不再有需要關註的事件,或者是遇到event_loopbreak() / event_loopexit()函數。

//啟動事件迴圈

event_base_dispatch(base);

接下來關註下綁定到event的回調函數callback_func:傳遞給它的是一個socket fd、一個event類型及屬性bit_field、以及傳遞給event_new的最後一個參數(去上面幾行回顧一下,把event_base給傳進來了,實際上更多地是分配一個結構體,把相關的數據都撂進去,然後丟給event_new,在這裡就能取得到了)。其原型是:

typedef void(*event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)

 

對於一個伺服器而言,上面的流程大概是這樣組合的:

1. listener = socket(),bind(),listen(),設置nonblocking(POSIX系統中可使用fcntl設置,windows不需要設置,

    實際上libevent提供了統一的包裝evutil_make_socket_nonblocking)

2. 創建一個event_base

3. 創建一個event,將該socket托管給event_base,指定要監聽的事件類型,並綁定上相應的回調函數(及需要給它的參數)

    。對於listener socket來說,只需要監聽EV_READ | EV_PERSIST

4. 啟用該事件

5. 進入事件迴圈

-------------- -

6. (非同步)當有client發起請求的時候,調用該回調函數,進行處理。

/*接下來關註下綁定到event的回調函數callback_func:傳遞給它的是一個socket fd、一個event類型及屬性bit_field、以及傳遞給event_new的最後一個參數(去上面幾行回顧一下,把event_base給傳進來了,實際上更多地是分配一個結構體,把相關的數據都撂進去,然後丟給event_new,在這裡就能取得到了)。*/

 

伺服器端代碼:Server.cpp

  1 #include <WinSock2.h>
  2 #include <stdlib.h>
  3 #include <stdio.h>
  4 #include <string.h>
  5 #include <errno.h>
  6 #include <event2/event.h>
  7 #include <event2/bufferevent.h>
  8 #include<iostream>
  9 #include<cassert>
 10 #pragma comment (lib,"ws2_32.lib")
 11 #include<ws2tcpip.h>
 12 #define LISTEN_PORT 9999
 13 #define LIATEN_BACKLOG 32
 14 using namespace std;
 15 /*********************************************************************************
 16 *                                      函數聲明
 17 **********************************************************************************/
 18 //accept回掉函數
 19 void do_accept_cb(evutil_socket_t listener, short event, void *arg);
 20 //read 回調函數
 21 void read_cb(struct bufferevent *bev, void *arg);
 22 //error回調函數
 23 void error_cb(struct bufferevent *bev, short event, void *arg);
 24 //write 回調函數
 25 void write_cb(struct bufferevent *bev, void *arg);
 26 /*********************************************************************************
 27 *                                      函數體
 28 **********************************************************************************/
 29 //accept回掉函數
 30 void do_accept_cb(evutil_socket_t listener, short event, void *arg)
 31 {
 32     //傳入的event_base指針
 33     struct event_base *base = (struct event_base*)arg;
 34     //socket描述符
 35     evutil_socket_t fd;
 36     //聲明地址
 37     struct sockaddr_in sin;
 38     //地址長度聲明
 39     socklen_t slen = sizeof(sin);
 40     //接收客戶端
 41     fd = accept(listener, (struct sockaddr *)&sin, &slen);
 42     if (fd < 0)
 43     {
 44         perror("error accept");
 45         return;
 46     }
 47     printf("ACCEPT: fd = %u\n", fd);
 48     ////註冊一個bufferevent_socket_new事件
 49     struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
 50     ////設置回掉函數
 51     bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
 52     ////設置該事件的屬性
 53     bufferevent_enable(bev, EV_READ | EV_WRITE | EV_PERSIST);
 54 }
 55 ////read 回調函數
 56 void read_cb(struct bufferevent *bev, void *arg)
 57 {
 58 #define MAX_LINE 256
 59     char line[MAX_LINE + 1];
 60     int n;
 61     //通過傳入參數bev找到socket fd
 62     evutil_socket_t fd = bufferevent_getfd(bev);
 63     //
 64     while (n = bufferevent_read(bev, line, MAX_LINE))
 65     {
 66         line[n] = '\0';
 67         printf("fd=%u, read line: %s\n", fd, line);
 68         //將獲取的數據返回給客戶端
 69         bufferevent_write(bev, line, n);
 70     }
 71 }
 72 ////error回調函數
 73 void error_cb(struct bufferevent *bev, short event, void *arg)
 74 {
 75     //通過傳入參數bev找到socket fd
 76     evutil_socket_t fd = bufferevent_getfd(bev);
 77     //cout << "fd = " << fd << endl;
 78     if (event & BEV_EVENT_TIMEOUT)
 79     {
 80         printf("Timed out\n"); //if bufferevent_set_timeouts() called
 81     }
 82     else if (event & BEV_EVENT_EOF)
 83     {
 84         printf("connection closed\n");
 85     }
 86     else if (event & BEV_EVENT_ERROR)
 87     {
 88         printf("some other error\n");
 89     }
 90     bufferevent_free(bev);
 91 }
 92 ////write 回調函數
 93 void write_cb(struct bufferevent *bev, void *arg)
 94 {
 95     char str[50];
 96     //通過傳入參數bev找到socket fd
 97     evutil_socket_t fd = bufferevent_getfd(bev);
 98     //cin >> str;
 99     printf("輸入數據!");
100     scanf_s("%d", &str);
101     bufferevent_write(bev, &str, sizeof(str));
102 }
103 
104 int main()
105 {
106     int ret;
107     evutil_socket_t listener;
108     WSADATA  Ws;
109     //Init Windows Socket
110     if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
111     {
112         return -1;
113     }
114     listener = socket(AF_INET, SOCK_STREAM, 0);
115     assert(listener > 0);
116     evutil_make_listen_socket_reuseable(listener);
117     struct sockaddr_in sin;
118     sin.sin_family = AF_INET;
119     sin.sin_addr.s_addr = 0;
120     sin.sin_port = htons(LISTEN_PORT);
121     if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
122         perror("bind");
123         return 1;
124     }
125     if (listen(listener, 1000) < 0) {
126         perror("listen");
127         return 1;
128     }
129     printf("Listening...\n");
130     evutil_make_socket_nonblocking(listener);
131     struct event_base *base = event_base_new();
132     assert(base != NULL);
133     struct event *listen_event;
134     listen_event = event_new(base, listener, EV_READ | EV_PERSIST, do_accept_cb, (void*)base);
135     event_add(listen_event, NULL);
136     event_base_dispatch(base);
137     printf("The End.");
138     return 0;
139 }

客戶端代碼:Client.cpp

 1 /******* 客戶端程式  client.c ************/
 2 #define _WINSOCK_DEPRECATED_NO_WARNINGS
 3 #define _CRT_SECURE_NO_WARNINGS
 4 #include <stdlib.h>  
 5 #include <stdio.h>  
 6 #include <errno.h>  
 7 #include <string.h>       
 8 #include<winsock2.h>
 9 #include<ws2tcpip.h>
10 #include<iostream>
11 
12 #pragma comment (lib,"ws2_32.lib")
13 int main(int argc, char *argv[])
14 {
15     WSADATA  Ws;
16     //Init Windows Socket
17     if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
18     {
19         return 0;
20     }
21     int sockfd;
22     char buffer[1024];
23     struct sockaddr_in server_addr;
24     struct hostent *host;
25     int portnumber, nbytes;
26 
27     if ((host = gethostbyname("127.0.0.1")) == NULL)
28     {
29         fprintf(stderr, "Gethostname error\n");
30         exit(1);
31     }
32 
33     if ((portnumber = atoi("9999"))<0)
34     {
35         fprintf(stderr, "Usage:%s hostname portnumber\a\n", argv[0]);
36         exit(1);
37     }
38 
39     /* 客戶程式開始建立 sockfd描述符  */
40     if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
41     {
42         fprintf(stderr, "Socket Error:%s\a\n", strerror(errno));
43         exit(1);
44     }
45 
46     /* 客戶程式填充服務端的資料       */
47     memset(&server_addr,0, sizeof(server_addr));
48     server_addr.sin_family = AF_INET;
49     server_addr.sin_port = htons(portnumber);
50     server_addr.sin_addr = *((struct in_addr *)host->h_addr);
51 
52     /* 客戶程式發起連接請求         */
53     if (connect(sockfd, (struct sockaddr *)(&server_addr), sizeof(struct sockaddr)) == -1)
54     {
55         fprintf(stderr, "Connect Error:%s\a\n", strerror(errno));
56         exit(1);
57     }
58 
59     while (true)
60     {
61         char MESSAGE[] = "hello server..\n";
62         //bufferevent_write(buf_ev,MESSAGE,strlen(MESSAGE));  
63         //  
64         if (-1 == (::send(sockfd, MESSAGE, strlen(MESSAGE), 0)))
65         {
66             printf("the net has a error occured..");
67             break;
68         }
69 
70         if ((nbytes = recv(sockfd, buffer, 1024,0)) == -1)
71         {
72             fprintf(stderr, "read error:%s\n", strerror(errno));
73             exit(1);
74         }
75 
76         buffer[nbytes] = '\0';
77         printf("I have received:%s\n", buffer);
78         memset(buffer, 0, 1024);
79 
80         Sleep(2);
81 
82     }
83     /* 結束通訊     */
84     closesocket(sockfd);
85     exit(0);
86 
87     return 0;
88 }

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 最近我發現了一個有趣的問題,這個問題的答案乍一看下騙過了我的眼睛。看一下這三個類: package com.ds.test; public class Upper { String upperString; public Upper() { Initializer.initialize(this); ...
  • 20161018問題解析請點擊今日問題下方的“【Java每日一題】20161019”查看 今日問題:上面兩個對int類型數組(集合)的求和操作,性能分析哪個更優?(點擊以下“【Java每日一題】20161019”查看20161018問題解析) 題目原發佈於公眾號、簡書:【Java每日一題】20161 ...
  • 前言 國際慣例,本文寫於本人使用《大話設計模式》一書學習設計模式的路上,適用於初學設計模式的道友交流之用,大神誤入,請留下您寶貴的意見,感激不盡; 開閉原則 定義 軟體實體應當對擴展開放,對修改關閉; 特征 1. 對擴展開放:當需要添加新的功能,或者說擴展功能時,我們不需要修改原來的代碼,只需要添加 ...
  • 上傳excel解析存到資料庫時報: org.apache.poi.poifs.filesystem.OfficeXmlFileException: The supplied data appears to be in the Office 2007+ XML. You are calling the ...
  • 英文文檔: all(iterable) Return True if all elements of the iterable are true (or if the iterable is empty). Equivalent to: 說明: 1. 接受一個可迭代器對象為參數,當參數為空或者不為可 ...
  • 原文鏈接:http://www.bdqn.cn/news/201303/8270.shtml 管道流可以實現兩個線程之間,二進位數據的傳輸。 管道流就像一條管道,一端輸入數據,別一端則輸出數據。通常要分別用兩個不同的線程來控制它們。 使用方法如下: [html] view plaincopy imp ...
  • 前言 國際慣例,本文寫於本人使用《大話設計模式》一書學習設計模式的路上,適用於初學設計模式的道友交流之用,大神誤入,請留下您寶貴的意見,感激不盡; 《大話設計模式》這本書名字太長,此篇之後統一稱呼《大設》; 單一職責原則 定義 就一個類而言,應該僅有一個引起它變化的原因 理解 類級別的單一職責 我們 ...
  • 下麵是總結的幾個使用方法 include 'PHPExcel.php'; include 'PHPExcel/Writer/Excel2007.php'; //或者include 'PHPExcel/Writer/Excel5.php'; 用於輸出.xls的 創建一個excel $objPHPExc ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...