Socket的同步阻塞IO和IO多路复用对比

同步阻塞模式

在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或*发生错误)

IO多路复用

  • 多路网络连接复用一个IO线程
  • 单个线程通过记录跟踪每一个Sock(I/O流)的状态来同时管理多个I/O流

测试结果

同步阻塞IO:

server-同步-阻塞

可见连接1、连接2、连接3是依次执行的,当其中一个连接出现阻塞,比如连接1阻塞5秒,那么下一个连接2也会被阻塞了5秒才能被执行。

IO多路复用:

server-IO复用 说明:由于是交替执行的,所以不好区分是哪个连接,不过可以通过下图来对比

连接1-阻塞5秒:

client1-阻塞5秒

连接2:

client2

连接3:

client3

可以看到连接1每次都阻塞了5秒才继续执行的,但是连接2和连接3却并没有受到阻塞的影响,马上就执行完了。

client端代码:

/**
 * 同步阻塞模式
 * 在这个模式中,用户空间的应用程序执行一个系统调用,并阻塞,直到系统调用完成为止(数据传输完成或*
 * 发生错误),在结果没有返回时,程序什么也不做
 */
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#define SERVPORT 1234
#define MAXDATASIZE 100
#define IPADDRESS "127.0.0.1"

int main(void) {
  time_t rawtime;
  struct tm *timeinfo;
  int sockfd, recvbytes;
  char rcv_buf[MAXDATASIZE]; /*./client 127.0.0.1 hello */
  char snd_buf[MAXDATASIZE];

  struct sockaddr_in server_addr;

  server_addr.sin_family = AF_INET;
  server_addr.sin_port = htons(SERVPORT);
  inet_pton(AF_INET, IPADDRESS, &server_addr.sin_addr);
  memset(&(server_addr.sin_zero), 0, 8);

  for (int z = 0; z < 2; z++) {
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
      perror("socket:");
      exit(1);
    }

    /* create the connection by socket
     * means that connect "sockfd" to "server_addr"
     * 同步阻塞模式
     */
    if (connect(sockfd, (struct sockaddr *)&server_addr,
                sizeof(struct sockaddr)) == -1) {
      perror("connect");
      exit(1);
    }
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    printf("connect:%d     %d:%d\n", sockfd, timeinfo->tm_min,
           timeinfo->tm_sec);

    sprintf(snd_buf, "Hello%d", z);

    // sleep(5);

    /* 同步阻塞模式  */
    if (send(sockfd, snd_buf, sizeof(snd_buf), 0) == -1) {
      perror("send:");
      exit(1);
    }
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    printf("send:%s     %d:%d\n", snd_buf, timeinfo->tm_min, timeinfo->tm_sec);

    /* 同步阻塞模式  */
    if ((recvbytes = recv(sockfd, rcv_buf, MAXDATASIZE, 0)) == -1) {
      perror("recv:");
      exit(1);
    }

    rcv_buf[recvbytes] = '\0';
    printf("recv:%s     %d:%d\n\n", rcv_buf, timeinfo->tm_min,
           timeinfo->tm_sec);

    close(sockfd);
  }
  return 0;
}

server端同步阻塞代码:

/**
 * server端同步阻塞
 */
#include <arpa/inet.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#define SERVPORT 1234
#define BACKLOG 10 // max numbef of client connection
#define MAXDATASIZE 100

int main(void) {
  time_t rawtime;
  struct tm *timeinfo;
  int sockfd, client_fd, addr_size, recvbytes;
  char rcv_buf[MAXDATASIZE];
  char *val;
  struct sockaddr_in server_addr;
  struct sockaddr_in client_addr;
  int bReuseaddr = 1;

  char IPdotdec[20];

  /* create a new socket and regiter it to os .
   * SOCK_STREAM means that supply tcp service,
   * and must connect() before data transfort.
   */
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    perror("socket:");
    exit(1);
  }

  /* setting server's socket */
  server_addr.sin_family = AF_INET; // IPv4 network protocol
  server_addr.sin_port = htons(SERVPORT);
  server_addr.sin_addr.s_addr = INADDR_ANY; // auto IP detect
  memset(&(server_addr.sin_zero), 0, 8);

  setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (const char *)&bReuseaddr,
             sizeof(int));
  if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) ==
      -1) {
    perror("bind:");
    exit(1);
  }

  /*
   * watting for connection ,
   * and server permit to recive the requestion from sockfd
   */
  if (listen(sockfd, BACKLOG) ==
      -1) // BACKLOG assign thd max number of connection
  {
    perror("listen:");
    exit(1);
  }

  while (1) {
    addr_size = sizeof(struct sockaddr_in);

    /*
     * accept the sockfd's connection,
     * return an new socket and assign far host to client_addr
     */
    if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr,
                            &addr_size)) == -1) {
      /* Nonblocking mode */
      perror("accept:");
      continue;
    }

    /**
     * sin_port和sin_addr都必须是NBO,一般可视化的数字都是HBO(本机字节顺序)
     * 地址的表达格式通常是ASCII字符串,数值格式则是存放到套接字地址结构的二进制值
     *
     * const char * inet_ntop(int family, const void *addrptr, char *strptr,
     * size_t len);
     * 函数中p和n分别代表表达(presentation)和数值(numeric)。
     * 从数值格式(addrptr)转换到表达式(strptr)。
     * len参数是目标存储单元的大小,以免该函数溢出其调用者的缓冲区
     */
    inet_ntop(AF_INET, (void *)&client_addr, IPdotdec, 16);

    time(&rawtime);
    timeinfo = localtime(&rawtime);
    printf("adding client on fd %d     %d:%d\n\n", client_fd, timeinfo->tm_min,
           timeinfo->tm_sec);

    /**
     * 同步阻塞在单个连接上,直到接收到该连接数据
     */
    if ((recvbytes = recv(client_fd, rcv_buf, MAXDATASIZE, 0)) == -1) {
      perror("recv:");
      exit(1);
    }
    sleep(1);

    time(&rawtime);
    timeinfo = localtime(&rawtime);
    printf("serving client on fd %d     %d:%d\n\n", client_fd, timeinfo->tm_min,
           timeinfo->tm_sec);
    /**
     * 同步阻塞在单个连接上,直到可发送数据到该连接上
     */
    if (send(client_fd, rcv_buf, strlen(rcv_buf), 0) == -1) {
      perror("send:");
      exit(1);
    }

    close(client_fd);
    time(&rawtime);
    timeinfo = localtime(&rawtime);
    printf("removing client on fd %d     %d:%d\n\n", client_fd,
           timeinfo->tm_min, timeinfo->tm_sec);
  }

  close(sockfd);
  return 0;
}

server端异步非阻塞代码:

/**
 * server端异步非阻塞
 */
#include <arpa/inet.h>
#include <assert.h>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>

#define IPADDR "127.0.0.1"
#define PORT 1234
#define MAXLINE 1024
#define LISTENQ 5
#define SIZE 10

time_t rawtime;
struct tm *timeinfo;

typedef struct server_context_st {
  int cli_cnt;      /*客户端个数*/
  int clifds[SIZE]; /*客户端的个数*/
  fd_set allfds;    /*句柄集合*/
  int maxfd;        /*句柄最大值*/
} server_context_st;
static server_context_st *s_srv_ctx = NULL;
/*===========================================================================
 * ==========================================================================*/
static int create_server_proc(const char *ip, int port) {
  int fd;
  struct sockaddr_in servaddr;
  fd = socket(AF_INET, SOCK_STREAM, 0);
  if (fd == -1) {
    fprintf(stderr, "create socket fail,erron:%d,reason:%s\n", errno,
            strerror(errno));
    return -1;
  }

  /*一个端口释放后会等待两分钟之后才能再被使用,SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/
  int reuse = 1;
  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
    return -1;
  }

  bzero(&servaddr, sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  inet_pton(AF_INET, ip, &servaddr.sin_addr);
  servaddr.sin_port = htons(port);

  if (bind(fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
    perror("bind error: ");
    return -1;
  }

  listen(fd, LISTENQ);

  return fd;
}

static int accept_client_proc(int srvfd) {
  struct sockaddr_in cliaddr;
  socklen_t cliaddrlen;
  cliaddrlen = sizeof(cliaddr);
  int clifd = -1;

ACCEPT:
  clifd = accept(srvfd, (struct sockaddr *)&cliaddr, &cliaddrlen);

  if (clifd == -1) {
    if (errno == EINTR) {
      goto ACCEPT;
    } else {
      fprintf(stderr, "accept fail,error:%s\n", strerror(errno));
      return -1;
    }
  }

  time(&rawtime);
  timeinfo = localtime(&rawtime);
  printf("adding client on fd %d     %d:%d\n\n", clifd, timeinfo->tm_min,
         timeinfo->tm_sec);

  //将新的连接描述符添加到数组中
  int i = 0;
  for (i = 0; i < SIZE; i++) {
    if (s_srv_ctx->clifds[i] < 0) {
      s_srv_ctx->clifds[i] = clifd;
      s_srv_ctx->cli_cnt++;
      break;
    }
  }

  if (i == SIZE) {
    fprintf(stderr, "too many clients.\n");
    return -1;
  }
}

static int handle_client_msg(int fd, char *buf) {
  assert(buf);

  sleep(1);
  time(&rawtime);
  timeinfo = localtime(&rawtime);
  printf("serving client on fd %d     %d:%d\n\n", fd, timeinfo->tm_min,
         timeinfo->tm_sec);

  write(fd, buf, strlen(buf) + 1);
  return 0;
}

static void recv_client_msg(fd_set *readfds) {
  int i = 0, n = 0;
  int clifd;
  char buf[MAXLINE] = {0};
  for (i = 0; i <= s_srv_ctx->cli_cnt; i++) {
    clifd = s_srv_ctx->clifds[i];
    if (clifd < 0) {
      continue;
    }
    /*判断客户端套接字是否有数据*/
    if (FD_ISSET(clifd, readfds)) {
      //接收客户端发送的信息
      n = read(clifd, buf, MAXLINE);
      if (n <= 0) {
        /*n==0表示读取完成,客户都关闭套接字*/
        FD_CLR(clifd, &s_srv_ctx->allfds);
        close(clifd);
        s_srv_ctx->clifds[i] = -1;
        continue;
      }
      handle_client_msg(clifd, buf);
    }
  }
}
static void handle_client_proc(int srvfd) {
  int clifd = -1;
  int retval = 0;
  fd_set *readfds = &s_srv_ctx->allfds;
  struct timeval tv;
  int i = 0;

  while (1) {
    /*每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦*/
    FD_ZERO(readfds);
    /*添加监听套接字*/
    FD_SET(srvfd, readfds);
    s_srv_ctx->maxfd = srvfd;

    tv.tv_sec = 30;
    tv.tv_usec = 0;
    /*添加客户端套接字*/
    for (i = 0; i < s_srv_ctx->cli_cnt; i++) {
      clifd = s_srv_ctx->clifds[i];
      /*去除无效的客户端句柄*/
      if (clifd != -1) {
        FD_SET(clifd, readfds);
      }
      s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
    }

    /**
     * 为什么第一个参数的值为所有文件描述符的最大值加1?
     * select是*轮询*监听处理服务端和客户端套接字(多个套接字)
     * 服务器套接字监听进来的请求(生成客户端套接字),客户端套接字监听请求是否发生改变
     * 所以select就需要传入 最大的文件描述符+1(0开始) 来进行*遍历*
     */
    retval = select(s_srv_ctx->maxfd + 1, readfds, NULL, NULL, &tv);
    if (retval == -1) {
      fprintf(stderr, "select error:%s.\n", strerror(errno));
      return;
    }
    if (retval == 0) {
      // fprintf(stdout, "select is timeout.\n");
      continue;
    }
    if (FD_ISSET(srvfd, readfds)) {
      /*监听客户端请求*/
      accept_client_proc(srvfd);
    } else {
      /*接受处理客户端消息*/
      recv_client_msg(readfds);
    }
  }
}

static void server_uninit() {
  if (s_srv_ctx) {
    free(s_srv_ctx);
    s_srv_ctx = NULL;
  }
}

static int server_init() {
  s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));
  if (s_srv_ctx == NULL) {
    return -1;
  }

  memset(s_srv_ctx, 0, sizeof(server_context_st));

  int i = 0;
  for (; i < SIZE; i++) {
    s_srv_ctx->clifds[i] = -1;
  }

  return 0;
}

int main(int argc, char *argv[]) {
  int srvfd;
  /*初始化服务端context*/
  if (server_init() < 0) {
    return -1;
  }
  /*创建服务,开始监听客户端请求*/
  srvfd = create_server_proc(IPADDR, PORT);
  if (srvfd < 0) {
    fprintf(stderr, "socket create or bind fail.\n");
    goto err;
  }
  /*开始接收并处理客户端请求*/
  handle_client_proc(srvfd);
  server_uninit();
  return 0;
err:
  server_uninit();
  return -1;
}
参考文件 http://blog.chinaunix.net/uid-26000296-id-3755264.html
  http://www.cnblogs.com/Anker/p/3258674.html