1. 程式人生 > >另一種實現非阻塞網路通訊的方法———使用libev

另一種實現非阻塞網路通訊的方法———使用libev

背景:最近終於開始了我的實習生之路,本來在進公司之前還比較緊張,儘管拿到了offer,因為畢竟這是一個新的起點,一開始從學生到員工這個身份的轉變讓我有些不太適應,但是還好在公司裡遇到了人超級好的軟體經理Alex以及其他精明能幹的小夥伴們,所以這個過渡時間也很快。

一開始Alex讓我通過公司的一個專案瞭解libev這個庫,我在看同事寫的程式碼的過程中遇到的問題實在太多,由於我之前寫的和這個專案類似的程式碼實現非阻塞網路連線都是通過select方法,但是同事寫的程式碼中大量使用到了libev中的API,對於從未接觸過的我來說想看懂真的是痴人說夢,但是我相信我的學習能力,於是我就開始了自己對libev的探索之路。

libev是一個事件驅動的程式設計框架,通過向libev中註冊感興趣的事件以及相應的回撥函式,那麼libev會對所註冊的事件源進行管理,並在事件發生時呼叫對應的回撥函式進行處理。我們的例子中就是對socket的連線事件,讀寫事件進行監聽並呼叫相應的回撥函式進行處理。

網上有許多介紹libev的部落格和程式碼,部落格一般都寫得很容易懂,但是程式碼一般就只是偽碼了,想要執行必須自己不斷地debug,我在綜合了各種博文中的方法之後寫出瞭如下程式碼,功能是同一個服務端提供多個客戶端連線,接收到客戶端連線之後把訊息回傳,若客戶端收到q,則斷開連線。

伺服器端程式碼如下

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
#include<ev.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<string.h>
#define PORT 9999
#define BUFFER_SIZE 1024
int total_clients=0;
void accept_cb(struct ev_loop *loop,struct ev_io *watcher,int revents);
void read_cb(struct ev_loop *loop,struct ev_io *watcher,int revents);
int main()
{
		struct ev_loop *loop=ev_default_loop(0);
		int sd;
		struct sockaddr_in addr;
		//int addr_len=sizeof(addr);
		struct ev_io socket_accept;

		if((sd=socket(AF_INET,SOCK_STREAM,0))<0){
				printf("socket error");
				return -1;
		}
		bzero(&addr,sizeof(addr));
		addr.sin_family=AF_INET;
		addr.sin_port=htons(PORT);
		addr.sin_addr.s_addr=INADDR_ANY;
		if(bind(sd,(struct sockaddr*)&addr,sizeof(addr))!=0){
				printf("bind error");
		}
		if(listen(sd,0)<0){
				printf("listen error");
				return -1;
		}
		ev_io_init(&socket_accept,accept_cb,sd,EV_READ);
		ev_io_start(loop,&socket_accept);
		while(1){
				ev_loop(loop,0);
		}
		return 0;
}
void accept_cb(struct ev_loop *loop,struct ev_io *watcher,int revents)
{
		//struct sockaddr_in client_addr;
		int client_sd;
		struct ev_io *w_client=(struct ev_io*)malloc(sizeof(struct ev_io));
		if(EV_ERROR & revents){
				printf("error event in accept");
				return ;
		}
		//client_sd=accept(watcher->fd,(struct sockaddr *)&client_addr,&client_len);
		client_sd=accept(watcher->fd,NULL,NULL);
		if(client_sd<0){
				printf("accept error");
				return;
		}
		total_clients++;
		printf("successfully connected with client.\n");
		printf("%d client connected .\n",total_clients);
		ev_io_init(w_client,read_cb,client_sd,EV_READ);
		ev_io_start(loop,w_client);
}
void read_cb(struct ev_loop *loop,struct ev_io *watcher,int revents)
{
		char buffer[BUFFER_SIZE];
		int read;
		if(EV_ERROR & revents){
				printf("error event in read");
				return;
		}
		read=recv(watcher->fd,buffer,BUFFER_SIZE,0);
		if(read==0){
				ev_io_stop(loop,watcher);
				free(watcher);
				perror("peer might closing");
				total_clients--;
				printf("%d client connected .\n",total_clients);
				return;
		}
		else{
				buffer[read]='\0';
				printf("get the message: %s\n",buffer);
		}
		
		send(watcher->fd,buffer,read,0);
		bzero(buffer,read);

}
客戶端程式碼如下
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define PORT 9999
#define BUFFER_SIZE 1024
int main()
{
		int sd;
		struct sockaddr_in addr;
		//int addr_len=sizeof(addr);
		char buffer[BUFFER_SIZE]="";
		if((sd=socket(AF_INET,SOCK_STREAM,0))<0)
		{
				perror("socket error");
				return -1;
		}
		bzero(&addr,sizeof(addr));

		addr.sin_family=AF_INET;
		addr.sin_port=htons(PORT);
		addr.sin_addr.s_addr=htonl(INADDR_ANY);

		if(connect(sd,(struct sockaddr *)&addr,sizeof(addr))<0)
		{
				perror("connect error");
				return -1;
		}
		while(strcmp(buffer,"q")!=0)
		{
				scanf("%s",buffer);
				send(sd,buffer,strlen(buffer),0);	
				recv(sd,buffer,BUFFER_SIZE,0);
				printf("message:%s\n",buffer);
		}
		return 0;
}
編譯的時候用gcc -Wall  -lev引數,-Wall用來顯示所有警告和錯誤,在我的程式裡是沒有的,因為本人親測可用,-lev代表編譯的時候連結上libev這個庫。