libevent介紹及示例
阿新 • • 發佈:2019-01-31
-
#include <stdio.h>
-
#include <string.h>
-
#include <stdlib.h>
-
#include <netinet/in.h>
-
#include <netinet/tcp.h>
-
#include <event.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <errno.h>
-
#include <fcntl.h>
-
static short ListenPort = 8080;
-
static long ListenAddr =
-
static int MaxConnections = 1024;
-
static int ServerSocket;
-
static struct event ServerEvent;//建立event
-
//不論在什麼平臺編寫網路程式,都應該使用NONBLOCK將一個socket設定成非阻塞模式。這樣可以保證你的程式至少不會在recv/send/accept/connect這些操作上發生block從而將整個網路服務都停下來
-
int SetNonblock(int fd)
-
{
-
int flags;
-
if ((flags = fcntl(fd, F_GETFL)
-
return -1;
-
}
-
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
-
return -1;
-
}
-
return 0;
-
}
-
//這個函式當客戶端的socket可讀時由libevent呼叫
-
void ServerRead(int fd, short ev, void *arg)
-
{
-
struct client *client = (struct client *)arg;
-
u_char buf[8196];
-
int
-
//會把引數fd 所指的檔案傳送count個位元組到buf指標所指的記憶體中
-
len = read(fd, buf, sizeof(buf));
-
if (len == 0) {
-
/* 客戶端斷開連線,在這裡移除讀事件並且釋放客戶資料結構 */
-
printf("disconnected\n");
-
close(fd);
-
event_del(&ServerEvent);
-
free(client);
-
return;
-
} else if (len < 0) {
-
/* 出現了其它的錯誤,在這裡關閉socket,移除事件並且釋放客戶資料結構 */
-
printf("socket fail %s\n", strerror(errno));
-
close(fd);
-
event_del(&ServerEvent);
-
free(client);
-
return;
-
}
-
/*
-
為了簡便,我們直接將資料寫回到客戶端。通常我們不能在非阻塞的應用程式中這麼做,
-
我們應該將資料放到佇列中,等待可寫事件的時候再寫回客戶端。
-
如果使用多個終端進行socket連線會出現錯誤socket fail Bad file descriptor
-
*/
-
wlen = write(fd, buf, len);
-
if (wlen < len) {
-
printf("not all data write back to client\n");
-
}
-
return;
-
}
-
/*
-
當有一個連線請求準備被接受時,這個函式將被libevent呼叫並傳遞給三個變數:
-
int fd:觸發事件的檔案描述符.
-
short event:觸發事件的型別EV_TIMEOUT,EV_SIGNAL, EV_READ, or EV_WRITE.
-
void* :由arg引數指定的變數.
-
*/
-
void ServerAccept(int fd, short ev, void *arg)
-
{
-
int cfd;
-
struct sockaddr_in addr;
-
socklen_t addrlen = sizeof(addr);
-
int yes = 1;
-
int retval;
-
//將從連線請求佇列中獲得連線資訊,建立新的套接字,並返回該套接字的檔案描述符。
-
//新建立的套接字用於伺服器與客戶機的通訊,而原來的套接字仍然處於監聽狀態。
-
//該函式的第一個引數指定處於監聽狀態的流套接字
-
cfd = accept(fd, (struct
sockaddr *)&addr, &addrlen);
-
if (cfd == -1) {
-
printf("accept(): can not accept client connection");
-
return;
-
}
-
if (SetNonblock(cfd) == -1) {
-
close(cfd);
-
return;
-
}
-
//設定與某個套接字關聯的選項
-
//引數二 IPPROTO_TCP:TCP選項
-
//引數三 TCP_NODELAY 不使用Nagle演算法 選擇立即傳送資料而不是等待產生更多的資料然後再一次傳送
-
// 更多引數TCP_NODELAY 和 TCP_CORK
-
//引數四 新選項TCP_NODELAY的值
-
if (setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1) {
-
printf("setsockopt(): TCP_NODELAY %s\n", strerror(errno));
-
close(cfd);
-
return;
-
}
-
event_set(&ServerEvent, cfd, EV_READ | EV_PERSIST, ServerRead, NULL);
-
event_add(&ServerEvent, NULL);
-
printf("Accepted connection from %s\n", inet_ntoa(addr.sin_addr));
-
}
-
int NewSocket(void)
-
{
-
struct sockaddr_in sa;
-
//socket函式來建立一個能夠進行網路通訊的套接字。
-
//第一個引數指定應用程式使用的通訊協議的協議族,對於TCP/IP協議族,該引數置AF_INET;
-
//第二個引數指定要建立的套接字型別
-
//流套接字型別為SOCK_STREAM、資料報套接字型別為SOCK_DGRAM、原始套接字SOCK_RAW
-
//第三個引數指定應用程式所使用的通訊協議。
-
ServerSocket = socket(AF_INET, SOCK_STREAM, 0);
-
if (ServerSocket == -1) {
-
printf("socket(): can not create server socket\n");
-
return -1;
-
}
-
if (SetNonblock(ServerSocket) == -1) {
-
return -1;
-
}
-
//清空記憶體資料
-
memset(&sa, 0, sizeof(sa));
-
sa.sin_family = AF_INET;
-
//htons將一個無符號短整型數值轉換為網路位元組序
-
sa.sin_port = htons(ListenPort);
-
//htonl將主機的無符號長整形數轉換成網路位元組順序
-
sa.sin_addr.s_addr = htonl(ListenAddr);
-
//(struct sockaddr*)&sa將sa強制轉換為sockaddr型別的指標
-
/*struct sockaddr
-
資料結構用做bind、connect、recvfrom、sendto等函式的引數,指明地址資訊。
-
但一般程式設計中並不直接針對此資料結構操作,而是使用另一個與sockaddr等價的資料結構 struct sockaddr_in
-
sockaddr_in和sockaddr是並列的結構,指向sockaddr_in的結構體的指標也可以指向
-
sockadd的結構體,並代替它。也就是說,你可以使用sockaddr_in建立你所需要的資訊,
-
在最後用進行型別轉換就可以了
-
*/
-
//bind函式用於將套接字繫結到一個已知的地址上
-
if (bind(ServerSocket, (struct
sockaddr*)&sa, sizeof(sa)) == -1) {
-
close(ServerSocket);
-
printf("bind(): can not bind server socket");
-
return -1;
-
}
-
//執行listen 之後套接字進入被動模式
-
//MaxConnections 連線請求佇列的最大長度,佇列滿了以後,將拒絕新的連線請求
-
if (listen(ServerSocket, MaxConnections) == -1) {
-
printf("listen(): can not listen server socket");
-
close(ServerSocket);
-
return -1;
-
}
-
/*
-
event_set的引數:
-
+ 引數1: 為要建立的event
-
+ 引數2: file descriptor,建立純計時器可以設定其為-1,即巨集evtimer_set定義的那樣
-
+ 引數3: 設定event種類,常用的EV_READ, EV_WRITE, EV_PERSIST, EV_SIGNAL, EV_TIMEOUT,純計時器設定該引數為0
-
+ 引數4: event被啟用之後觸發的callback函式
-
+ 引數5: 傳遞給callback函式的引數
-
備註:
-
如果初始化event的時候設定其為persistent的(設定了EV_PERSIST),
-
則使用event_add將其新增到偵聽事件集合後(pending狀態),
-
該event會持續保持pending狀態,即該event可以無限次參加libevent的事件偵聽。
-
每當其被啟用觸發callback函式執行之後,該event自動從active轉回為pending狀態,
-
繼續參加libevent的偵聽(當啟用條件滿足,又可以繼續執行其callback)。
-
除非在程式碼中使用event_del()函式將該event從libevent的偵聽事件集合中刪除。
-
如果不通過設定EV_PERSIST使得event是persistent的,需要在event的callback中再次呼叫event_add
-
(即在每次pending變為active之後,在callback中再將其設定為pending)
-
*/
-
event_set(&ServerEvent, ServerSocket, EV_READ | EV_PERSIST, ServerAccept, NULL);
-
//將event新增到libevent偵聽的事件集中
-
if (event_add(&ServerEvent, 0) == -1) {
-
printf("event_add(): can not add accept event into libevent");
-
close(ServerSocket);
-
return -1;
-
}
-
return 0;
-
}
-
int main(int argc, char *argv[])
-
{
-
int retval;
-
event_init(); //初始化event
base使用預設的全域性current_base
-
retval = NewSocket();
-
if (retval == -1) {
-
exit(-1);
-
}
-
event_dispatch(); //啟動事件佇列系統,開始監聽(並接受)請求
-
return 0;
- }