linux的异步事件处理--epoll

在socket网络编程中,如果当前已经有连接了,那么另外一个请求想连接服务器,只能等待了。

因此解决的办法有下面4中。

 

fork的方式

这种方式很好理解,

代码如下

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8111
#define MESSAGE_SIZE 1024

int main(){

  int ret = -1;

  int pid;
  int socket_fd = -1;
  int accept_fd = -1;

  int curpos = 0;
  int backlog = 10;
  int flag = 1;

  struct sockaddr_in local_addr, remote_addr;

  //create a tcp socket
  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if ( socket_fd == -1 ){
    perror("create socket error");
    exit(1);
  }

  //set option of socket
  ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
  if ( ret == -1 ){
    perror("setsockopt error");
  }

  //set local address
  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(PORT);
  local_addr.sin_addr.s_addr = INADDR_ANY;
  bzero(&(local_addr.sin_zero), 8);

  //bind socket
  ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
  if(ret == -1 ) {
    perror("bind error");
    exit(1);
  }

  ret = listen(socket_fd, backlog);
  if ( ret == -1 ){
    perror("listen error");
    exit(1);
  }

  for(;;){

    int addr_len = sizeof( struct sockaddr_in );

    //accept an new connection, block......
    accept_fd = accept( socket_fd, (struct sockaddr *)&remote_addr, &addr_len );

    //create a sub process
    pid = fork();

    //子进程
    if( pid==0 ){ 

      char in_buf[MESSAGE_SIZE] = {0,};

      for(;;){

        memset(in_buf, 0, MESSAGE_SIZE);

        ret = recv(accept_fd ,&in_buf, MESSAGE_SIZE, 0);
        if(ret == 0){
          break; 
        } 

        printf( "receive message:%s\n", in_buf );
        send(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);

      }

      printf("close client connection...\n");
      close(accept_fd);

    } 

    //parent process
    
  }

  if(pid != 0 ){
    printf("quit server...\n");
    close(socket_fd);
  }

  return 0;
}

select的方式

需要先了解异步IO,

使用select的例子,如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8888
#define FD_SIZE 1024
#define MESSAGE_SIZE 1024

int main(){

  int ret = -1;

  int pid;
  int accept_fd = -1;
  int socket_fd = -1;
  int accept_fds[FD_SIZE] = {-1, };

  int curpos = -1;
  int maxpos = 0;
  int backlog = 10;
  int flags = 1; //open REUSEADDR option

  int max_fd = -1;
  fd_set fd_sets;
  int events=0;

  struct sockaddr_in local_addr, remote_addr;

  //create a tcp socket
  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if ( socket_fd == -1 ){
    perror("create socket error");
    exit(1);
  }

  //set option of socket
  ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));
  if ( ret == -1 ){
    perror("setsockopt error");
  }

  //NONBLOCK
  flags = fcntl(socket_fd, F_GETFL, 0);
  fcntl(socket_fd, F_SETFL, flags | O_NONBLOCK);

  //set local address
  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(PORT);
  local_addr.sin_addr.s_addr = INADDR_ANY;
  bzero(&(local_addr.sin_zero), 8);

  //bind socket
  ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
  if(ret == -1 ) {
    perror("bind error");
    exit(1);
  }

  ret = listen(socket_fd, backlog);
  if ( ret == -1 ){
    perror("listen error");
    exit(1);
  }

  max_fd = socket_fd; //每次都重新设置 max_fd
  for(int i=0; i< FD_SIZE; i++){
    accept_fds[i] = -1; 
  }  

  for(;;) {

    FD_ZERO(&fd_sets); //清空sets
    FD_SET(socket_fd, &fd_sets); //将socket_fd 添加到sets

    for(int k=0; k < maxpos; k++){
      if(accept_fds[k] != -1){
        if(accept_fds[k] > max_fd){
          max_fd = accept_fds[k]; 
        }
        printf("fd:%d, k:%d, max_fd:%d\n", accept_fds[k], k, max_fd);
        FD_SET(accept_fds[k], &fd_sets); //继续向sets添加fd
      }
    }

    //遍历所有的fd
    events = select( max_fd + 1, &fd_sets, NULL, NULL, NULL );
    if(events < 0) {
      perror("select");
      break;
    }else if(events == 0){
      printf("select time out ......");
      continue;
    }else if( events ){

      printf("events:%d\n", events);

      if( FD_ISSET(socket_fd, &fd_sets)){ // 如果来的是新连接
        printf("listen event :1\n");

        int a = 0;
        for( ; a < FD_SIZE; a++){
          if(accept_fds[a] == -1){
            curpos = a;
            break;
          }
        }

        if(a == FD_SIZE){
          printf("the connection is full!\n");
          continue;
        }

        int addr_len = sizeof( struct sockaddr_in );
        accept_fd = accept(socket_fd, (struct sockaddr *)&remote_addr, &addr_len); //创建一个新连接的fd

        int flags = fcntl(accept_fd, F_GETFL, 0); //取出新连接的 fd 的相关选项
        fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK); //设置为非阻塞

        accept_fds[curpos] = accept_fd;

        if(curpos+1 > maxpos){
          maxpos = curpos + 1; 
        }

        if(accept_fd > max_fd){
          max_fd = accept_fd; 
        }

        printf("new connection fd:%d, curpos = %d \n",accept_fd, curpos);
      }

      for(int j=0; j < maxpos; j++ ){
        if( (accept_fds[j] != -1) && FD_ISSET(accept_fds[j], &fd_sets)){ //有事件时
          printf("accept event :%d, accept_fd: %d\n",j, accept_fds[j]);
          char in_buf[MESSAGE_SIZE];
          memset(in_buf, 0, MESSAGE_SIZE);
          int ret = recv(accept_fds[j], &in_buf, MESSAGE_SIZE, 0);
          if(ret == 0){
            close(accept_fds[j]);
            accept_fds[j] = -1;
          } 

          printf( "receive message:%s\n", in_buf );
          send(accept_fds[j], (void*)in_buf, MESSAGE_SIZE, 0);
        }
      }
    }
  }

  printf("quit server...\n");
  close(socket_fd);

  return 0;
}

 

epoll方式

 

代码例子,如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8111

#define FD_SIZE 20
#define MAX_EVENTS 20
#define TIME_OUT 500

#define MESSAGE_SIZE 1024

#define NB_PROCESS 4


int main(){

  int ret = -1;

  int socket_fd = -1;
  int accept_fd = -1;

  int flags = 1;
  int backlog = 10;

  struct sockaddr_in local_addr,remote_addr;

  struct epoll_event ev, events[FD_SIZE];
  int epoll_fd = -1; 
  int event_number = 0;

  int pid;
  int status;
  int max_subprocess = NB_PROCESS;

  //creat a tcp socket
  socket_fd = socket(AF_INET, SOCK_STREAM, 0);
  if ( socket_fd  == -1 ){
    perror("create socket error");
    exit(1);
  }

  //set REUSERADDR
  ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&flags, sizeof(flags)); 
  if ( ret == -1 ){
    perror("setsockopt error");
  }

  //set NONBLOCK
  flags = fcntl(socket_fd, F_GETFL, 0);
  fcntl(socket_fd, F_SETFL, flags|O_NONBLOCK);

  //set address
  local_addr.sin_family = AF_INET;
  local_addr.sin_port = htons(PORT);
  local_addr.sin_addr.s_addr = INADDR_ANY;
  bzero(&(local_addr.sin_zero),8);

  //bind addr
  ret = bind(socket_fd, (struct sockaddr *)&local_addr, sizeof(struct sockaddr_in));
  if( ret == -1 ) {
    perror("bind error");
    exit(1);
  }

  if (listen(socket_fd, backlog) == -1 ){
    perror("listen error");
    exit(1);
  }

  //fork some subprocess
  for(int a=0; a < max_subprocess; a++){
    if(pid !=0){
      pid = fork(); 
    }
  }

  //child process
  if(pid == 0) {

    printf("create an new child process...");
    //create epoll
    epoll_fd = epoll_create(256);//the size argument is ignored
    ev.data.fd=socket_fd;
    ev.events=EPOLLIN;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev); //将socket_fd 添加到epoll中
    for(;;){
      //events 表示一共有多少事件被侦听
      //MAX_EVENTS 表示在events个事件中,本次调用最多能返回多少个被解发的事件
      //TIME_OUT 表示本次调用最多等多长时间
      //event_number 表示本次调用真正有多少事件被解发
      event_number = epoll_wait(epoll_fd, events, MAX_EVENTS, TIME_OUT);
      for(int i=0; i < event_number; i++){

        if(events[i].data.fd == socket_fd){ // 如果是侦听端口的事件

          printf("listen event... \n");

          int addr_len = sizeof( struct sockaddr_in );
          accept_fd = accept(socket_fd, (struct sockaddr *)&remote_addr, &addr_len);

          //将新创建的socket设置为 NONBLOCK 模式
          flags = fcntl(accept_fd, F_GETFL, 0);
          fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);

          ev.data.fd=accept_fd;
          ev.events=EPOLLIN | EPOLLET;
          epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);

          printf("new accept fd:%d\n",accept_fd);

        } else if(events[i].events & EPOLLIN){

          //printf("accept event :%d\n",i);

          char in_buf[MESSAGE_SIZE];
          memset(in_buf, 0, MESSAGE_SIZE);

          //receive data
          ret = recv( events[i].data.fd, &in_buf, MESSAGE_SIZE, 0 );
          if(ret == MESSAGE_SIZE ){
            printf("maybe have data....");
          }

          if(ret <= 0){

            switch (errno){
              case EAGAIN:
                ret = recv(events[i].data.fd, &in_buf, MESSAGE_SIZE, 0);
                break;
              case EINTR:
                printf("recv EINTR... \n");
                break;
              default:
                printf("the client is closed, fd:%d\n", events[i].data.fd);
                epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &ev); 
                close(events[i].data.fd);
                ;
            }
          }

          printf(">>>receive message:%s\n", in_buf);
          send(events[i].data.fd, &in_buf, ret, 0);
        }
      }
    }

  }else {// pid == 0
    //wait child process to quit 
    wait(&status);
  }

  return 0;
}

 

对应客户端的代码:

如下:

#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#define MAX 80
#define PORT 9876
#define SA struct sockaddr
void func(int sockfd)
{
	char buff[MAX];
	int n;
	for (;;) {
		bzero(buff, sizeof(buff));
		printf("Enter the string : ");
		n = 0;
		while ((buff[n++] = getchar()) != '\n')
			;
		write(sockfd, buff, sizeof(buff));
		bzero(buff, sizeof(buff));
		read(sockfd, buff, sizeof(buff));
		printf("From Server : %s", buff);
		if ((strncmp(buff, "exit", 4)) == 0) {
			printf("Client Exit...\n");
			break;
		}
	}
}

int main()
{
	int sockfd, connfd, ret;
	struct sockaddr_in servaddr, cli;

	// socket create and varification
	sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd == -1) {
		printf("socket creation failed...\n");
		exit(0);
	}
	else
		printf("Socket successfully created..\n");
	bzero(&servaddr, sizeof(servaddr));

	// assign IP, PORT
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	servaddr.sin_port = htons(PORT);

	// connect the client socket to server socket
	ret = connect(sockfd, (SA*)&servaddr, sizeof(servaddr));
	if (ret != 0) {
		printf("connection with the server failed...\n");
		exit(0);
	}
	else
		printf("connected to the server..\n");

	// function for chat
	func(sockfd);

	// close the socket
	close(sockfd);
}


 

 

利用IO事件处理库

epoll使用起来比较复杂,很多开源项目对epoll进行了封装,如libevent、libuv等,这都是比较著名的开源事件处理库,

 

 

 

 

总结

epoll的优点:

(1)epoll监视的文件描述符不受限制,内存越大其值越大,而select最多是2048个;

(2)epoll不会随着监视的数量增大,而效率降低。

 

参考:

 

Linux IO模式及 select、poll、epoll详解,这是一个非常好的文章,详细介绍了I/O阻塞和I/O非阻塞,I/O同步和I/O异步的区别。

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值