1. 程式人生 > >C語言之libevent和socket示例

C語言之libevent和socket示例

#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


閱讀: