C語言之libevent和socket示例
阿新 • • 發佈:2018-12-27
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <netinet/tcp.h> #include <event.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <fcntl.h> #include <netinet/in.h> #include <arpa/inet.h> //內部函式,只能被本檔案中的函式呼叫 static short ListenPort = 9999; static long ListenAddr = INADDR_ANY;//任意地址,值就是0 static int MaxConnections = 1024; static int ServerSocket; //建立event static struct event ServerEvent; //將一個socket設定成非阻塞模式 //不論什麼平臺編寫網路程式,都應該使用NONBLOCK socket的方式。這樣可以保證你的程式至少不會在recv/send/accept/connect這些操作上發生block從而將整個網路服務都停下來 int SetNonblock(int fd) { int flags; //fcntl()用來操作檔案描述符的一些特性 if ((flags = fcntl(fd, F_GETFL)) == -1) { 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 len, wlen; //會把引數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"); }*/ } //這個函式當客戶端的socket可寫時由libevent呼叫 void ServerWrite(int fd, short ev, void *arg) { if(!arg) { printf("ServerWrite err!arg null\n"); return; } int len=strlen(arg); if(len <= 0) { printf("ServerWrite err!len:%d\n",len); return; } int wlen = write(fd, arg, len); if (wlen<len) { printf("not all data write back to client!wlen:%d len:%d \n",wlen,len); } } /* 當有一個連線請求準備被接受時,這個函式將被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; //將從連線請求佇列中獲得連線資訊,建立新的套接字,並返回該套接字的檔案描述符。 //新建立的套接字用於伺服器與客戶機的通訊,而原來的套接字仍然處於監聽狀態。 //該函式的第一個引數指定處於監聽狀態的流套接字 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_set(&ServerEvent, cfd, EV_WRITE| EV_PERSIST, ServerWrite, "echo libevent\n"); event_add(&ServerEvent, NULL); printf("Accepted connection from %s \n", (char *)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 base 使用預設的全域性current_base event_init(); retval = NewSocket(); if (retval == -1) { exit(-1); } //event_dispatch() 啟動事件佇列系統,開始監聽(並接受)請求 event_dispatch(); return 0; }
編譯
gcc socket.c -o socket -Wl,-rpath,/usr/local/libevent-2.0.12-stable/lib/ -L/usr/local/libevent-2.0.12-stable/lib/ -levent -I/usr/local/libevent-2.0.12-stable/include/
測試:
終端執行 ./socket 另外開一個終端 telnet 127.0.0.1 8080
閱讀: