1. 程式人生 > >socket非同步程式設計--libevent的使用

socket非同步程式設計--libevent的使用

       首先,安裝libevent到任意目錄下:

wget http://monkey.org/~provos/libevent-1.4.13-stable.tar.gz
tar –xzvf libevent-1.4.13-stable.tar.gz
cd libevent-1.4.13-stable
./configure --prefix=/home/mydir/libevent
make && make install

     現在假定我們要設計一個伺服器程式,用於接收客戶端的資料,並將接收的資料回寫給客戶端。下面來構造該程式,由於本僅僅是展示一個Demo,因此程式中將不對錯誤進行處理,假設所有的呼叫都成功

#define PORT 25341
#define BACKLOG 5
#define MEM_SIZE 1024

struct event_base* base;

int main(int argc, char* argv[])
{
    struct sockaddr_in my_addr;
    int sock;

    sock = socket(AF_INET, SOCK_STREAM, 0); 
    int yes = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
    listen(sock, BACKLOG);

    struct event listen_ev;
    base = event_base_new();
    event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);
    event_base_set(base, &listen_ev);
    event_add(&listen_ev, NULL);
    event_base_dispatch(base);

    return 0;
}

      第13行說明建立的是一個TCP socket。第15行是伺服器程式的通常做法,設定了該選項後,在父子程序模型中,當子程序為客戶服務的時候如果父程序退出,可以重新啟動程式完成服務的無縫升級,否則在所有父子程序完全退出前再啟動程式會在該埠上繫結失敗,也即不能完成無縫升級的操作(更多資訊可以參考該函式說明或Steven先生的<網路程式設計>)。第24行用於建立一個事件處理的全域性變數,可以理解為這是一個負責集中處理各種出入IO事件的總管家,它負責接收和派發所有輸入輸出IO事件的資訊,這裡呼叫的是函式event_base_new(), 很多程式裡這裡用的是event_init(),區別就是前者是執行緒安全的、而後者是非執行緒安全的,後者在其官方說明中已經被標誌為過時的函式、且建議用前者代替,libevent中還有很多類似的函式,比如建議用event_base_dispatch代替event_dispatch,用event_assign代替event_set和event_base_set等,關於libevent介面的詳細說明見其官方說明

libevent_doc. 第25行說明在listen_en這個事件監聽sock這個描述字的讀操作,當讀訊息到達是呼叫on_accept函式,EV_PERSIST引數告訴系統持續的監聽sock上的讀事件,如果不加該引數,每次要監聽該事件時就要重複的呼叫26行的event_add函式,從前面的程式碼可知,sock這個描述字是bind到本地的socket埠上,因此其對應的可讀事件自然就是來自客戶端的連線到達,我們就可以呼叫accept無阻塞的返回客戶的連線了。第26行將listen_ev註冊到base這個事件中,相當於告訴處理IO的管家請留意我的listen_ev上的事件。第27行相當於告訴處理IO的管家,當有我的事件到達時你發給我(呼叫on_accept函式),至此對listen_ev的初始化完畢。第28行正式啟動libevent的事件處理機制,使系統執行起來,執行程式的話會發現event_base_dispatch是一個無限迴圈。

下面是on_accept函式的內容

   1: void on_accept(int sock, short event, void* arg)
   2: {
   3:     struct sockaddr_in cli_addr;
   4:     int newfd, sin_size;
   5:     // read_ev must allocate from heap memory, otherwise the program would crash from segmant fault
   6:     struct event* read_ev = (struct event*)malloc(sizeof(struct event));;
   7:     sin_size = sizeof(struct sockaddr_in);
   8:     newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);
   9:     event_set(read_ev, newfd, EV_READ|EV_PERSIST, on_read, read_ev);
  10:     event_base_set(base, read_ev);
  11:     event_add(read_ev, NULL);
  12: } 

        第9-12與前面main函式的24-26相同,即在代表客戶的描述字newfd上監聽可讀事件,當有資料到達是呼叫on_read函式。這裡有亮點需要注意,一是read_ev需要從堆裡malloc出來,如果是在棧上分配,那麼當函式返回時變數佔用的記憶體會被釋放,因此事件主迴圈event_base_dispatch會訪問無效的記憶體而導致程序崩潰(即crash);第二個要注意的是第9行read_ev作為引數傳遞給了on_read函式。

下面是on_read函式的內容

   1: void on_read(int sock, short event, void* arg)
   2: {
   3:     struct event* write_ev;
   4:     int size;
   5:     char* buffer = (char*)malloc(MEM_SIZE);
   6:     bzero(buffer, MEM_SIZE);
   7:     size = recv(sock, buffer, MEM_SIZE, 0);
   8:     printf("receive data:%s, size:%d\n", buffer, size);
   9:     if (size == 0) {
  10:         event_del((struct event*)arg);
  11:         free((struct event*)arg);
  12:         close(sock);
  13:         return;
  14:     }
  15:     write_ev = (struct event*) malloc(sizeof(struct event));;
  16:     event_set(write_ev, sock, EV_WRITE, on_write, buffer);
  17:     event_base_set(base, write_ev);
  18:     event_add(write_ev, NULL);
  19: }

     第9行,當從socket讀返回0標誌對方已經關閉了連線,因此這個時候就沒必要繼續監聽該套介面上的事件,由於EV_READ在on_accept函式裡是用EV_PERSIST引數註冊的,因此要顯示的呼叫event_del函式取消對該事件的監聽。第18-21行與on_accept函式的6-11行類似,當可寫時呼叫on_write函式,注意第19行將buffer作為引數傳遞給了on_write。這段程式還有比較嚴重的問題,後面進行說明。

on_write函式的實現

1 void on_write(int sock, short event, void* arg)
2 {
3     char* buffer = (char*)arg;
4     send(sock, buffer, strlen(buffer), 0); 
5 
6     free(buffer);
7 }

     on_write函式中向客戶端回寫資料,然後釋放on_read函式中malloc出來的buffer。在很多書合編程指導中都很強調資源的所有權,經常要求誰分配資源、就由誰釋放資源,這樣對資源的管理指責就更明確,不容易出問題,但是通過該例子我們發現在非同步程式設計中資源的分配與釋放往往是由不同的所有者操作的,因此也是比較容易出問題的地方。

     其實在on_read函式中從socket讀取資料後程序就可以直接呼叫write/send介面向客戶回寫資料了,因為寫事件已經滿足,不存在非同步不非同步的問題,這裡進行on_write的非同步操作僅僅是為了說明非同步程式設計中資源的管理與釋放的問題,另外一方面,直接呼叫write/send函式向客戶端寫資料可能導致程式較長時間阻塞在IO操作上,比如socket的輸出緩衝區已滿,則write/send操作阻塞到有可用的緩衝區之後才能進行實際的寫操作,而通過向寫事件註冊on_accept函式,那麼libevent會在合適的時間呼叫我們的callback函式,(比如對於會引起IO阻塞的情況比如socket輸出緩衝區滿,則由libevent設計演算法來處理,如此當回撥on_accept函式時我們在呼叫IO操作就不會發生真正的IO之外的阻塞)。注:前面括號中是我個人認為一個庫應該實現的功能,至於libevent是不是實現這樣的功能並不清楚也無意深究。

     再來看看前面提到的on_read函式中存在的問題,首先write_ev是動態分配的記憶體,但是沒有釋放,因此存在記憶體洩漏,另外,on_read中進行malloc操作,那麼當多次呼叫該函式的時候就會造成記憶體的多次洩漏。這裡的解決方法是對socket的描述字可以封裝一個結構體來保護讀、寫的事件以及資料緩衝區,整理後的完整程式碼如下

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>

#include <event.h>


#define PORT        25341
#define BACKLOG     5
#define MEM_SIZE    1024

struct event_base* base;
struct sock_ev {
    struct event* read_ev;
    struct event* write_ev;
    char* buffer;
};

void release_sock_event(struct sock_ev* ev)
{
    event_del(ev->read_ev);
    free(ev->read_ev);
    free(ev->write_ev);
    free(ev->buffer);
    free(ev);
}

void on_write(int sock, short event, void* arg)
{
    char* buffer = (char*)arg;
    send(sock, buffer, strlen(buffer), 0);

    free(buffer);
}

void on_read(int sock, short event, void* arg)
{
    struct event* write_ev;
    int size;
    struct sock_ev* ev = (struct sock_ev*)arg;
    ev->buffer = (char*)malloc(MEM_SIZE);
    bzero(ev->buffer, MEM_SIZE);
    size = recv(sock, ev->buffer, MEM_SIZE, 0);
    printf("receive data:%s, size:%d\n", ev->buffer, size);
    if (size == 0) {
        release_sock_event(ev);
        close(sock);
        return;
    }
    event_set(ev->write_ev, sock, EV_WRITE, on_write, ev->buffer);
    event_base_set(base, ev->write_ev);
    event_add(ev->write_ev, NULL);
}

void on_accept(int sock, short event, void* arg)
{
    struct sockaddr_in cli_addr;
    int newfd, sin_size;
    struct sock_ev* ev = (struct sock_ev*)malloc(sizeof(struct sock_ev));
    ev->read_ev = (struct event*)malloc(sizeof(struct event));
    ev->write_ev = (struct event*)malloc(sizeof(struct event));
    sin_size = sizeof(struct sockaddr_in);
    newfd = accept(sock, (struct sockaddr*)&cli_addr, &sin_size);
    event_set(ev->read_ev, newfd, EV_READ|EV_PERSIST, on_read, ev);
    event_base_set(base, ev->read_ev);
    event_add(ev->read_ev, NULL);
}

int main(int argc, char* argv[])
{
    struct sockaddr_in my_addr;
    int sock;

    sock = socket(AF_INET, SOCK_STREAM, 0);
    int yes = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
    memset(&my_addr, 0, sizeof(my_addr));
    my_addr.sin_family = AF_INET;
    my_addr.sin_port = htons(PORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bind(sock, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
    listen(sock, BACKLOG);

    struct event listen_ev;
    base = event_base_new();
    event_set(&listen_ev, sock, EV_READ|EV_PERSIST, on_accept, NULL);
    event_base_set(base, &listen_ev);
    event_add(&listen_ev, NULL);
    event_base_dispatch(base);

    return 0;

      程式編譯的時候要加 -levent 連線選項,以連線libevent的共享庫,但是執行的時候依然爆出如下錯誤:error while loading shared libraries: libevent-1.4.so.2: cannot open shared object file: No such file or directory, 這個是程式找不到共享庫的位置,通過執行echo $LD_LIBRARY_PATH可以看到系統庫的環境變數裡沒有我們安裝的路徑,即由--prefix制定的路徑,執行export LD_LIBRARY_PATH=/home/mydir/libevent/lib/:$LD_LIBRARY_PATH將該路徑加入系統環境變數裡,再執行程式就可以了。

相關推薦

socket非同步程式設計--libevent的使用

       首先,安裝libevent到任意目錄下: wget http://monkey.org/~provos/libevent-1.4.13-stable.tar.gz tar –xzvf libevent-1.4.13-stable.tar.gz cd libev

c++ socket 非同步程式設計

在網路通訊中,由於網路擁擠或一次傳送的資料量過大等原因,經常會發生交換的資料在短時間內不能傳送完,收發資料的函式因此不能返回,這種現象叫做阻塞。 Winsock對有可能阻塞的函式提供了兩種處理方式:阻塞和非阻塞方式。 阻塞模式      在阻塞方式下,收發資料的函式在被

C#中Socket通訊程式設計非同步實現

本文將在C#中Socket同步通訊的基礎上,分析和研究Socket非同步程式設計的實現方法,目的是深入瞭解Socket程式設計的基本原理,增強對網路遊戲開發相關內容的認識。 什麼是Socket程式設計的非同步是實現 所謂Socket程式設計的非同步實現是指按

Scala 非同步程式設計之 Future (二)

上篇部落格講了scala中的Future,實際上java 在jdk1.5後增加了callable,也實現了Future,以《Netty In Action》中的程式碼為例,看一下java中Future的實現: import java.util.concurrent.Callable; im

Scala 非同步程式設計之 Future

同步非同步,阻塞非阻塞,在IO模型中幾個概念組合在一起不是很容易理解,但是隻從程式碼執行的角度看同步非同步是很清晰的: 同步代表這段程式碼中的邏輯必須執行完畢,而非同步代表呼叫馬上返回,但通常情況下是獲取不到需要的值。 同步:val  value={ Thread.sleep(

java:socket 網路程式設計

socket的通俗解釋: 套接字=主機+埠號。兩個東西配在一起,叫做“配套”。 另外“套”也有對應的意思,它可以把網路上的兩個應用對應起來,所以用“套”。 它是用來與另一個應用連線的,所以用“接”。 又因為它是一小段資料,很小一小段,所以叫“字”。 “套接字",就是一小段用來將網路個兩個應用

[Socket網路程式設計]一個封鎖操作被對 WSACancelBlockingCall 的呼叫中斷。

原文地址:http://www.cnblogs.com/xiwang/archive/2012/10/25/2740114.html記錄在此,方便查閱。 C#中在使用UDPClient迴圈監聽埠,在斷開UPDClient的時候,使用try...catch捕獲了異常,System.NET.Socket

Dart非同步程式設計之Stream

Dart非同步程式設計包含兩部分:Future和Stream 上篇文章已介紹了Future,此篇文章為大家介紹下另一塊–Stream Dart 非同步事件流 Stream 基本概念 顧名思義,Stream 就是流的意思,表示發出的一系列的非同步資料。可以簡單地認為 Strea

Dart非同步程式設計之Future

Dart非同步程式設計包含兩部分:Future和Stream 本文將詳細介紹Future Dart非同步程式設計-future 非同步程式設計:Futures Dart是一個單執行緒程式語言。如果任何程式碼阻塞執行緒執行都會導致程式卡死。非同步程式設計防止出現阻塞操作。Dar

Python_day6:socket網路程式設計

一、socket   socket即套接字,用於描述IP地址和埠,是一個通訊鏈的控制代碼,應用程式通常通過"套接字"向網路發出請求或者應答網路請求。   最簡單的socket,一次 1 import socket 2 server = socket.socket() #獲得例項

python------Socket網路程式設計(二)粘包問題

一.socket網路程式設計  粘包:服務端兩次傳送指令在一起,它會把兩次傳送內容合在一起傳送,稱為粘包,從而出現錯誤。 解決方法:(比較low的方法) 有些需要實時更新的,用sleep有延遲,不能這樣解決問題。 解決方法之高階方法: 客戶端: 二.傳送檔案 ftp s

門禁系統socket通訊程式設計

最近遇到一個socke udp協議通訊的需求,而且是16進位制資料接收。這樣在傳輸引數的時候老是提示引數錯誤,因為計算機是不能直接傳輸16進位制的,會自行轉換,所有以下程式碼非常完美的解決我的問題,同時也讓我認識到並不是所有socket都是需要一個客戶端和服務端程式碼 <?php  &nbs

Socket網路程式設計進階與實戰資源分享

Socket網路程式設計進階與實戰資源分享 Socket網路程式設計進階與實戰資源分享 獲取資源新增qq+2100776785 獲取資源新增qq+2100776785 第1章 課程介紹 本章將從軟體測試的起源與發展、測試行業的現狀及職業生涯規劃等整體做介紹。 第2章 軟體測試工程師必

java非同步程式設計降低延遲

目錄 java非同步程式設計降低延遲 一、ExecutorService和CompletionService 二、CompletableFuture(重要) 三、stream中的parallel(並行流) 四、實際使用的另外一點總結: java非同步

python 協程及socket網路程式設計

協程 什麼是協程 協程,英文Coroutines,是一種比執行緒更加輕量級的存在。正如一個程序可以擁有多個執行緒一樣,一個執行緒也可以擁有多個協程。 最重要的是,協程不是被作業系統核心所管理,而完全是由程式所控制(也就是在使用者態執行)。 這樣帶來的好處就是效能得到了很大的提升,不會

JavaScript非同步程式設計筆記

非同步事件的工作方式 事件!事件到底是怎麼工作的?JavaScript出現了多久,對JavaScript非同步事件模型就迷惘了多久。迷惘導致bug,bug導致加班,加班導致沒時間撩妹子,這不是js攻城獅想要的生活。 ==為了妹子,一定要理解好JavaScript事件== JavaScript事件的執行

Python Socket網路程式設計(一)初識SocketSocket初步使用

目錄 前言 網路程式設計 實質 IP地址和埠 資料傳輸協議 協議 Socket

Python Socket網路程式設計(二)區域網內和區域網與廣域網的持續通訊

目錄 前言 IP地址 簡介 公有IP 私有IP 區域網之間網路通訊 前提 功能描述

銀行業務系統(c/s架構、socket網路程式設計、多執行緒)

1、功能要求 包括兩類使用者:管理人員和普通使用者(本文只寫了普通使用者程式) 普通使用者功能:登入登出、存取款、轉賬、查詢餘額 2、技術要求 要求用到多程序多執行緒 要求同時允許多個使用者操作(因為沒有註冊賬號功能,且只初始化了兩個賬號資訊,所以同時只能允許兩個賬號線上)

【FastReport教程】介紹C#中的非同步程式設計(下)

【下載FastReport.Netdownload最新版本】 非同步程式設計模型出現在.Net Framework的第一個版本中。APM允許使用兩種方法建立同步方法的非同步版本 - Begin 和End 。 所以,只有兩種方法: public IAsyncResult Begin{MethodName}(