I/O复用

select系统调用

select:在一段时间内,监听用户感兴趣的文件描述符上可读、可写和异常等事件。

#include<sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数:
nfds:指定被监听的文件描述符的总数。通常为select监听的所有文件描述符中的最大值+1,因为文件描述符从0开始计数。
readfds、writefds和exceptfds:分别指向可读、可写和异常等事件对应的文件描述符集合。通过这3个参数传入关心的文件描述符。
使用下面的一系列宏来访问fd_set结构体:

#include<sys/select.h>
FD_ZERO(fd_set *fdset);					// 清除fdset的所有位
FD_SET(int fd, fd_set *fdset);			// 设置fdset的位fd
FD_CLR(int fd, fd_set *fdset);			// 清除fdset的位fd
int FD_ISSET(int fd, fd_set *fdset);	// 测试fdset的位fd是否被设置

timeout:设置select的超时时间。timeval结构类型的指针。

struct timeval
{
	long tv_sec;	// 秒数
	long tv_usec;	// 微秒数
}

例:利用select解决socket中的多客户问题

遍历文件描述符集合中的所有描述符,找出有变化的描述符。当系统底层某个或几个socket有数据来时,遍历找出有变化的描述符。
对于侦听的socket和数据处理要区别对待。
socket必须设置为非阻塞方式。

#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 8111
#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;
        }

        socklen_t 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系统调用

epoll是Linux特有的I/O复用函数。epoll使用一组函数来完成任务;epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,而无需像select那样每次调用都要重复传入文件描述符集或事件集。
但epoll需要一个额外的文件描述符,来唯一标识内核中的这个事件表。

#include<sys/epoll.h>

int epoll_create(int size);

参数:
size:现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。
返回值:
文件描述符,用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

参数:
epfd:epoll_create()创建的
op:操作类型

EPOLL_CTL_ADD:往事件表中注册fd上的事件
EPOLL_CTL_MOD:修改fd上的注册事件
EPOLL_CTL_DEL:删除fd上的注册事件

fd:要操作的文件描述符
event:指定事件,epoll_event结构指针类型

struct epoll_event
{
	__unit32_t events;	// epoll事件
	epoll_data_t data;	// 用户数据
}

typedef union epoll_data
{
	void *ptr;		// 指定与fd相关的用户数据
	int fd;			// 指定事件所从属的目标文件描述符
	uint32_t u32;
	uint64_t u64;
}epoll_data_t;

返回值:
成功返回0,失败返回-1

epoll_wait函数

int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

参数:
maxevents:指定最多监听多少个事件,必须大于0

epoll_wait如果检测到事件,就将所有就绪的事件从内核事件表中(由epfd指定)复制2到events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件。

#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/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


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;

  //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);
  }

  //create epoll 创建epoll文件描述符
  epoll_fd = epoll_create(256);//the size argument is ignored
  // ev.data.fd存储文件描述符,当有新连接来时,收到这个事件->ev->ev.data.fd知道是哪个文件描述符发生的
  ev.data.fd=socket_fd;
  ev.events=EPOLLIN;
  // 向epoll_fd中添加socket_fd,进行监听
  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");

        socklen_t 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;
        // 将accept_fd添加到epoll_fd中,这是与客户端通信的连接
        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: //说明暂时已经没有数据了,要等通知
                break;
              case EINTR: //被终断了,再来一次
                printf("recv EINTR... \n");
                ret = recv(events[i].data.fd, &in_buf, MESSAGE_SIZE, 0);
                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);
      }
    }
  }
  return 0;
}

Libevent高性能I/O框架库

event_base_new:创建一个事件处理框架

struct event_base *base;
base = event_base_new(); /* 初始化event_base */
if (!base) {
	puts("Couldn't open event base");
	return 1;
}

event_new:创建一个事件
event_add:将事件添加到时间处理框架上
event_base_dispatch:开始循环事件

event_base_dispatch(base);

event_base_free:释放资源

#include <event2/listener.h>
#include <event2/bufferevent.h>
#include <event2/buffer.h>
#include <event2/thread.h>

#include <arpa/inet.h>

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

static void
echo_read_cb(struct bufferevent *bev, void *ctx)
{
    /* 获取bufferevent中的读和写的指针 */
    /* This callback is invoked when there is data to read on bev. */
    struct evbuffer *input = bufferevent_get_input(bev);
    struct evbuffer *output = bufferevent_get_output(bev);
    /* 把读入的数据全部复制到写内存中 */
    /* Copy all the data from the input buffer to the output buffer. */
    evbuffer_add_buffer(output, input);
}

static void echo_event_cb(struct bufferevent *bev, short events, void *ctx)
{
    if (events & BEV_EVENT_ERROR)
        perror("Error from bufferevent");
    if (events & (BEV_EVENT_EOF | BEV_EVENT_ERROR)) {
        bufferevent_free(bev);
    }
}

static void accept_conn_cb(struct evconnlistener *listener,evutil_socket_t fd, struct sockaddr *address, int socklen,void *ctx)
{
    /* 初始化一个bufferevent用于数据的写入和读取,首先需要从Listerner中获取event_base */
    struct event_base *base = evconnlistener_get_base(listener);
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    /*设置buferevent的回调函数,这里设置了读和事件的回调函数*/
    bufferevent_setcb(bev, echo_read_cb, NULL, echo_event_cb, NULL);
    /* 启用该bufevent写和读 */
    bufferevent_enable(bev, EV_READ|EV_WRITE);
}

static void accept_error_cb(struct evconnlistener *listener, void *ctx)
{
    struct event_base *base = evconnlistener_get_base(listener);
    int err = EVUTIL_SOCKET_ERROR();
    fprintf(stderr, "Got an error %d (%s) on the listener. ""Shutting down.\n", err, evutil_socket_error_to_string(err));

    event_base_loopexit(base, NULL);
}

int main(int argc, char **argv)
{	
	// 
    struct event_base *base;
    struct evconnlistener *listener;
    struct sockaddr_in sin;

    int port = 8111;
    
    // 创建一个事件处理框架
    base = event_base_new(); /* 初始化event_base */
    if (!base) {
        puts("Couldn't open event base");
        return 1;
    }

    /*初始化绑定地址*/
    /*Clear the sockaddr before using it, 
     * in case there are extra platform-specific fields 
     * that can mess us up.
     */
    memset(&sin, 0, sizeof(sin));
    /*This is an INET address */
    sin.sin_family = AF_INET;
    /*Listen on 0.0.0.0*/
    sin.sin_addr.s_addr = INADDR_ANY;
    /*Listen on the given port.*/
    sin.sin_port = htons(port);

    /* 初始化evconnlistener(绑定地址、设置回调函数以及连接属性) */
    listener = evconnlistener_new_bind(base, accept_conn_cb, NULL, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,(struct sockaddr*)&sin, sizeof(sin));
    if (!listener) {
        perror("Couldn't create listener");
        return 1;
    }

    /* 设置Listen错误回调函数 */
    evconnlistener_set_error_cb(listener, accept_error_cb);

    /* 开始accept进入循环 */
    event_base_dispatch(base);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值