Linux C 開發
12 | evutil_make_listen_socket_reuseable(server_socketfd);//設定埠重用 vutil_make_socket_nonblocking(server_socketfd);//設定無阻塞 |
2. 我們首選建立的事件主要用於監聽客戶端的連入。當客戶端有socket連線到伺服器端的時候,回撥函式do_accept就會去執行;當空閒的時候,這個事件就會是一個pending等待狀態,等待有新的連線進來,新的連線進來了之後又會繼續執行。
12 | structevent*ev;ev=event_new(base_ev,server_socketfd,EV_TIMEOUT|EV_READ|EV_PERSIST,do_accept,base_ev); |
3. 在do_accept事件中我們建立了一個新的事件,這個事件的回撥函式是do_read。主要用來迴圈監聽客戶端上傳的資料。do_read這個方法會一直迴圈執行,接收到客戶端資料就會進行處理。
12345 | //建立一個事件,這個事件主要用於監聽和讀取客戶端傳遞過來的資料 |
Bufferevent
上面的socket例子估計經過測試估計大家就會有很多疑問:
1. do_read方法作為一個事件會一直被迴圈
2. 當客戶端連線斷開的時候,do_read方法還是在迴圈,根本不知道客戶端已經斷開socket的連線。
3. 需要解決各種粘包和拆包(相關粘包拆包文章)問題
如果要解決這個問題,我們可能要做大量的工作來維護這些socket的連線狀態,讀取狀態。而Libevent的Bufferevent幫我們解決了這些問題。
Bufferevent主要是用來管理和排程IO事件;而Evbuffer(下面一節會講到)主要用來緩衝網路IO資料。
Bufferevent目前支援TCP協議,而不知道UDP協議。我們這邊也只講TCP協議下的Bufferevent的使用。
我們先看下下面的介面(然後結合下面改進socket的例子,自己動手去實驗一下):
1. 建立Bufferevent API
12 | //建立一個Bufferevent structbufferevent*bufferevent_socket_new(structevent_base*base,evutil_socket_t fd,enumbufferevent_options options); |
引數:
base:即event_base
fd:檔案描述符。如果是socket的方法,則socket需要設定為非阻塞的模式。
options:行為選項,下面是行為選項內容
1. BEV_OPT_CLOSE_ON_FREE :當 bufferevent 被釋放同時關閉底層(socket 被關閉等) 一般用這個選項
2. BEV_OPT_THREADSAFE :為 bufferevent 自動分配鎖,這樣能夠在多執行緒環境中安全使用
3. BEV_OPT_DEFER_CALLBACKS : 當設定了此標誌,bufferevent 會延遲它的所有回撥(參考前面說的延時回撥)
4. BEV_OPT_UNLOCK_CALLBACKS : 如果 bufferevent 被設定為執行緒安全的,使用者提供的回撥被呼叫時 bufferevent 的鎖會被持有。如果設定了此選項,Libevent 將在呼叫你的回撥時釋放 bufferevent 的鎖
2. 釋放Bufferevent
1 | voidbufferevent_free(structbufferevent*bev); |
如果設定了延時回撥BEV_OPT_DEFER_CALLBACKS,則釋放會在延時回撥呼叫了回撥函式之後,才會真正釋放。
3. 設定Bufferevent的回撥函式和相關設定
前面我們說過了,使用了Bufferevent之後,Libevent會幫我們託管三種事件:1. 讀取事件 2. 寫入事件 3. 處理事件
我們先看一下回調函式結構:
1. 讀取和寫入的回撥函式結構,其中 ctx為通用傳遞的引數
1 | typedefvoid(*bufferevent_data_cb)(structbufferevent*bev,void*ctx); |
2. 事件回撥,即連線斷開、錯誤處理等回撥。其中ctx為通用傳遞的引數。
events引數為事件,使用者可以在回撥函式中拿到這個事件來進行事務處理的判斷:
1. BEV_EVENT_READING 在 bufferevent 上進行讀取操作時出現了一個事件
2. BEV_EVENT_WRITING 在 bufferevent 上進行寫入操作時出現了一個事件
3. BEV_EVENT_ERROR 進行 bufferevent 操作時出錯
4. BEV_EVENT_TIMEOUT 在 bufferevent 上出現了超時
5. BEV_EVENT_EOF 在 bufferevent 上遇到了檔案結束符,連線斷開
6. BEV_EVENT_CONNECTED 在 bufferevent 上請求連線完成了
1 | typedefvoid(*bufferevent_event_cb)(structbufferevent*bev,shortevents,void*ctx); |
3. 在bufferevent上設定回撥函式。
bufev:bufferevent_socket_new建立的bufferevent
readcb:讀取事件的回撥函式,沒有則可以為NULL
writecb:寫入事件的回撥函式,沒有則可以為NULL
eventcb:事件函式的回撥函式,沒有則可以為NULL,一般我們可以在這裡面判斷連線斷開等。
cbarg:公用傳輸的傳遞
通過這個函式,我們就可以設定我們需要的一些回撥函式資訊。
12 | voidbufferevent_setcb(structbufferevent*bufev,bufferevent_data_cb readcb,bufferevent_data_cb writecb,bufferevent_event_cb eventcb,void*cbarg); |
取回回撥函式:
12 | voidbufferevent_getcb(structbufferevent*bufev,bufferevent_data_cb*readcb_ptr,bufferevent_data_cb*writecb_ptr,bufferevent_event_cb*eventcb_ptr,void**cbarg_ptr) |
4. 設定Bufferevent事件的型別
1 | bufferevent_enable(bev,EV_READ|EV_WRITE|EV_PERSIST); |
5. 水位設定。
水位設定可以這麼理解,bufferevent相當於一個水位容器,其中引數:
events:EV_READ 則為設定讀取事件;EV_WRITE 則為寫入事件。EV_READ | EV_WRITE 為設定兩者的水位。
lowmark:最低水位,預設為0。這個引數非常重要,例如lowmark設定為10,則當bufferevent容器中有10個字元的時候才會去呼叫readcb這個回撥函式。
1 | voidbufferevent_setwatermark(structbufferevent*bufev,shortevents,size_t lowmark,size_t highmark); |
6. 下面可以看一個設定和回撥函式例子:
12345678910111213141516171819202122232425262728293031323334353637 | //建立一個bufferevent structbufferevent*bev=bufferevent_socket_new(base_ev,client_socketfd,BEV_OPT_CLOSE_ON_FREE);//設定讀取方法和error時候的方法 bufferevent_setcb(bev,read_cb,NULL,error_cb,base_ev);//設定型別 bufferevent_enable(bev,EV_READ|EV_WRITE|EV_PERSIST);//設定水位 bufferevent_setwatermark(bev,EV_READ,0,0);//讀取事件回撥函式 voidread_cb(structbufferevent*bev,void*arg){#define MAX_LINE 256 charline[MAX_LINE+1];intn;evutil_socket_t fd=bufferevent_getfd(bev);while(n=bufferevent_read(bev,line,MAX_LINE),n>0){line[n]='\0';printf("fd=%u, read line: %s\n",fd,line);bufferevent_write(bev,line,n);}puts("haha");}//寫入事件回撥函式 voidwrite_cb(structbufferevent*bev,void*arg){}//事件回撥 voiderror_cb(structbufferevent*bev,shortevent,void*arg){evutil_socket_t fd=bufferevent_getfd(bev);printf("fd = %u, ",fd);if(event&BEV_EVENT_TIMEOUT){printf("Timed out\n");}elseif(event&BEV_EVENT_EOF){printf("connection closed\n");}elseif(event&BEV_EVENT_ERROR){printf("some other error\n");}bufferevent_free(bev);} |
4. 輸入輸出相關函式
1. 獲取buffer:
1234 | // 獲取到輸入 buffer structevbuffer*bufferevent_get_input(structbufferevent*bufev);// 獲取到輸出 buffer structevbuffer*bufferevent_get_output(structbufferevent*bufev); |
2. 寫入和輸出函式,成功返回0,失敗返回-1:
bufev:bufferevent
data:寫入的字串資料
size:字元長度
1234 | //寫入 intbufferevent_write(structbufferevent*bufev,constvoid*data,size_t size);//輸出 size_t bufferevent_read(structbufferevent*bufev,void*data,size_t size); |
3. 寫入輸出函式2:
bufev:bufferevent
buf:buffer塊 下面會講到evbuffer的使用
12 | intbufferevent_write_buffer(structbufferevent*bufev,structevbuffer*buf);intbufferevent_read_buffer(structbufferevent*bufev,structevbuffer*buf); |
使用Bufferevent後的Socket例子:
上面我們已經介紹完了Bufferevent的相關API,可以看下具體例子。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061 |