网络I/O模型代码实现

  将之前letflysite.com文章搬运过来。

  下面将使用c写几个之前网络模型代码。下面都是服务端代码,客户端简略运行telnet localhost 10003。

1. 阻塞型网络编程模型

4步:1.创建码头;2.连接码头;3.读取数据;4.关闭。

#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>


int main() {
  // 1. 创建码头
  // int socket(int domain , int type , int protocol);
  // 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
  // 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
  // socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误>,
  // socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
  int ss = socket(AF_INET, SOCK_STREAM, 0);
  // 捕获异常
  if (ss == -1) {
    printf("create socket failed, errno is %d\n", errno);
    return -1;
  }
  // n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字>节顺序
  struct sockaddr_in server_ip, clien_ip;
  server_ip.sin_family = AF_INET; // 协议族
  server_ip.sin_port = htons(10003);
  server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主>机地址,方便可移>植性
  memset(server_ip.sin_zero, 0, 8); // 8个字节
  int err = bind(ss, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr));
  if (err == -1) {
    printf("bind error, errno is %d\n", errno);
    return -1;
  }
  // 最大连接数
  err = listen(ss, 100);
  if (err == -1) {
    printf("listen error, errno is %d\n", errno);
    close(ss);
    return -1;
  }

  // 2.连接码头
  socklen_t clien_len = sizeof(struct sockaddr);
  int s = accept(ss, (struct sockaddr *)(&clien_ip), &clien_len);
  if (s == -1) {
    printf("accept error, errno is %d\n", errno);
    close(ss);
    return -1;
  }

  // 3.读取数据
  char buf[1024];
  read(s, buf, 100);
  printf("buf is %s\n", buf);

  // 4.关闭
  close(s);
  close(ss);
  return 0;
}

3.非阻塞的服务端模型

#include <cstdio>
#include <cstring>
#include <errno.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
  // 1. 创建码头
  // int socket(int domain , int type , int protocol);
  // 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
  // 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
  // socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误>,
  // socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
  int server_s = socket(AF_INET, SOCK_STREAM, 0);
  // 捕获异常
  if (server_s == -1) {
    printf("create socket failed, errno is %d\n", errno);
    return -1;
  }
  // n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字>节顺序
  struct sockaddr_in server_ip, clien_ip;
  server_ip.sin_family = AF_INET; // 协议族
  server_ip.sin_port = htons(10003);
  server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主>机地址,方便可移>植性
  memset(server_ip.sin_zero, 0, 8); // 8个字节
  int err = bind(server_s, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr));
  if (err == -1) {
    printf("bind error, errno is %d\n", errno);
    return -1;
  }
  // 最大连接数
  err = listen(server_s, 100);
  if (err == -1) {
    printf("listen error, errno is %d\n", errno);
    close(server_s);
    return -1;
  }


  // fcntl()函数,处理多路复用I/O
  int flags, client_s;
  if ((flags = fcntl(server_s, F_GETFL, 0)) < 0) perror("fcntl F_GETFL");
  flags |= O_ASYNC; // O_NONBLOCK为非阻塞I/O,O_ASYNC为信号驱动I/O
  if (fcntl(server_s, F_SETFL, flags) < 0) perror("fcntl F_SETFL");
  while (1) {
    // 2.连接码头
    socklen_t clien_len = sizeof(struct sockaddr);
    client_s = accept(server_s, (struct sockaddr *)(&clien_ip), &clien_len);
    if (client_s == -1) {
      printf("accept error, errno is %d\n", errno);
      close(server_s);
      return -1;
    }

    // 3.读取数据
    char read_buf[1024];
    if (read(client_s, read_buf, 1024) < 0) {
      perror("read");
      close(client_s);
      close(server_s);
      return -1;
    }
    printf("read_buf is %s\n", read_buf);
  }

  // 4.关闭
  close(client_s);
  close(server_s);
  return 0;
}

4. 基于select()接口的事件驱动服务端模型

  在这之前我们认识两个函数send和recv。send的函数模型如下,ssize_t send(int sockfd, const void *buf, size_t len, int flags);recv的函数模型如下,ssize_t recv(int sockfd, void *buf, size_t len, int flags)。它们比read和write多了flags标志位。这两个函数要在我们tcp建立连接之后才可以写的函数。我们看看它有哪些flags函数。

flags 说明 recv send
MSG DONIROUTE 不查路由表   yes
MSG DONTWAIT 本操作不阻塞 yes yes
MSG OOB 发送或接收带外数据 yes yes
MSG WAITALL 查看外来消息 yes  
MSG PEEK 等待所有数据 yes  

1,MSG DONIROUTE这通常是在我们发送数据的时候,比如说是在一个局域网里面,它不需要通过我们的网关到达我们的公网,只在当前这个局域网里面发送数据。

2,DONTWAIT,I/O是阻塞式还是非阻塞式

3,OOB,这个用到的不多,主要是在我们发收数据的时候,它会把最后一个字节,或最后一点点数据,通过我们的内核函数,在我们的应用程序里面不需要,一般设置为0。

  跟前面创建码头一样,从第2步连接码头开始不一样

#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>

int main() {
  // 1. 创建码头
  // int socket(int domain , int type , int protocol);
  // 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
  // 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
  // socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,
  // socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
  int ss = socket(AF_INET, SOCK_STREAM, 0);
  // 捕获异常
  if (ss == -1) {
    printf("create socket failed, errno is %d\n", errno);
    return -1;
  }
  // n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字节顺序
  struct sockaddr_in server_ip, clien_ip;
  server_ip.sin_family = AF_INET; // 协议族
  server_ip.sin_port = htons(10003);
  server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主机地址,方便可移植性
  memset(server_ip.sin_zero, 0, 8); // 8个字节
  if (bind(ss, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr)) == -1) {
    printf("bind error, errno is %d\n", errno);
    return -1;
  }
  // 最大连接数
  if (listen(ss, 100) == -1) {
    printf("accept error, errno is %d\n", errno);
    close(ss);
    return -1;
  }

  // 2.连接码头
  fd_set global_readfs, current_readfs;
  FD_ZERO(&global_readfs);
  FD_SET(ss, &global_readfs);
  int maxfd = ss;
  while (1) {
    // int select(int nfds, fd_set, *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)
    // fd_set 设置我的套接字进入为可读可写异常, timeout它超时了怎么办
    // 套接字有三种方式,1.不阻塞,调用一次返回一次,timeout是0;2.永远等待,直到下一个套接字来了
    // 此时timeout是null;3.介于中间我给它一定的时间等待
    // fd_set底层实现是数组的结构,比如说我要关注1024个套接字,它就是1024/32=32个数组,我就可以监听1024个套接字
    current_readfs = global_readfs;
    if (select(maxfd + 1, &current_readfs, NULL, NULL, NULL) < 0) {
      // void FD_CLR(int fd, fd_set *set)
      // int FD_ISSET(int fd, fd_set *set)
      // void FD_SET(int fd, fd_set *set)
      // void FD_ZERO(fd_set *set)
      // 我们可以调用这些宏,第一个参数都是套接字,增删改查
      perror("select error.\n");
      return -1;
    }
    for (int i = 0; i <= maxfd; ++i) {
      // 判断是不是内核通知我的可读状态
      if (FD_ISSET(i, &current_readfs))
        if (ss == i) {
          socklen_t clien_len = sizeof(struct sockaddr);
          int clien_s = accept(ss, (struct sockaddr *)(&clien_ip), &clien_len);
          if (clien_s == -1) {
            printf("accept error, errno is %d\n", errno);
            close(ss);
            return -1;
          }
          printf("clien_s: %d\n", clien_s);
          FD_CLR(i, &current_readfs);
          maxfd = maxfd > clien_s ? maxfd : clien_s;
          FD_SET(clien_s, &global_readfs);
        } else {

          printf("read clien_s:%d\n", i);
          // 3.读取数据
          char read_buf[1024];
          int bytes = recv(i, read_buf, 1024, 0);
          if (bytes < 0) {
            perror("recv error.\n");
            return -1;
          }
          if (bytes == 0 ) {
            // 清除套接字集
            FD_CLR(i, &global_readfs);
            // 4.关闭
            close(i);
            continue;
          }
          printf("read_buf is %s\n", read_buf);
          send(i, read_buf, strlen(read_buf), 0);

        }
    }
  }

  close(ss);
  return 0;
}

epoll接口

#include <cstdio>
#include <cstring>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>

int main() {
  // 1. 创建码头
  // int socket(int domain , int type , int protocol);
  // 首先,domain 需要被设置为 “AF_INET”,就像上面的struct sockaddr_in。
  // 然后,type参数告诉内核这个socket 是什么类型,“SOCK_STREAM”或是“SOCK_DGRAM”。最后,只需要把protocol 设置为0 。
  // socket()函数只是简单的返回一个你以后可以使用的套接字描述符。如果发生错误,
  // socket()函数返回 –1 。全局变量errno 将被设置为错误代码。(可以参考perror() 的manpages)
  int server_s = socket(AF_INET, SOCK_STREAM, 0);
  // 捕获异常
  if (server_s == -1) {
    printf("create socket failed, errno is %d\n", errno);
    return -1;
  }
  // n: network; h: host; s: short; l: long. 主机的无符号短整形数转换成网络字节顺序
  struct sockaddr_in server_ip, clien_ip;
  server_ip.sin_family = AF_INET; // 协议族
  server_ip.sin_port = htons(10003);
  server_ip.sin_addr.s_addr = htonl(INADDR_ANY); // 该宏默认为0,也就是你的主机地址,方便可移植性
  memset(server_ip.sin_zero, 0, 8); // 8个字节
  if (bind(server_s, (struct sockaddr *)(&server_ip), sizeof(struct sockaddr)) == -1) {
    printf("bind error, errno is %d\n", errno);
    return -1;
  }
  // 最大连接数
  if (listen(server_s, 100) == -1) {
    printf("listen error, errno is %d\n", errno);
    close(server_s);
    return -1;
  }

  // fcntl()函数,处理多路复用I/O
  int flags, client_s;
  if ((flags = fcntl(server_s, F_GETFL, 0)) < 0) perror("fcntl F_GETFL");
  flags |= O_ASYNC; // O_NONBLOCK为非阻塞I/O,O_ASYNC为信号驱动I/O
  if (fcntl(server_s, F_SETFL, flags) < 0) perror("fcntl F_SETFL");

  int epollfd = epoll_create(500);
  if (epollfd < 0) {
    perror("epoll_create err:");
    return -1;
  }
  struct epoll_event ev, events[500];
  ev.data.fd = server_s;
  ev.events = EPOLLIN;
  if (epoll_ctl(epollfd, EPOLL_CTL_ADD, server_s, &ev) == -1) {
    perror("epoll_ctl err:");
    return -1;
  }
  while (1) {
    int fds = epoll_wait(epollfd, events, 500, -1);
    if (fds < 0) {
      perror("epoll_wait err:");
      return -1;
    }

    // 轮询
    for (int i = 0; i < fds; ++i) {
      int current_s = events[i].data.fd;
      if (current_s == server_s) {
        // 2.连接码头
        socklen_t clien_len = sizeof(struct sockaddr);
        client_s = accept(server_s, (struct sockaddr *)(&clien_ip), &clien_len);
        if (client_s == -1) {
          printf("accept error, errno is %d\n", errno);
          close(server_s);
          continue;
        }

        ev.data.fd = server_s;
        ev.events = EPOLLIN | EPOLLET;
        epoll_ctl(epollfd, EPOLL_CTL_ADD, server_s, &ev);
      } else {
        // 3.读取数据
        char recv_buf[1024];
        int bytes;
        if ((bytes = recv(client_s, recv_buf, 1024, 0)) < 0) {
          perror("recv");
          close(client_s);
          epoll_ctl(epollfd, EPOLL_CTL_DEL, current_s, &ev);
          close(current_s);
          continue;
        }
        printf("recv_buf is %s\n", recv_buf);
        send(current_s, recv_buf, bytes, 0);
      }
    }

  }

  // 4.关闭
  close(client_s);
  close(server_s);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/u012332571/article/details/68934518