epoll 的原理不费劲叙述了, 如果未来有时间再写吧.

说说网上找到的代码的问题

代码如下.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <assert.h>


#define CHECK_RET(ret, errstr)  \
  if ((ret) == -1) {            \
    perror(errstr); exit(1);    \
  }

#define LISTEN_IP   "0.0.0.0"
#define LISTEN_PORT 8080
#define LISTEN_BACKLOG 3
#define NUM_EVENTS 10000
#define BUNDLE_BUFSZ 256
struct conn_bundle {
  char buf[BUNDLE_BUFSZ];
  int bufsz;
  int fd;
};


int listen_on(const char* ip, int port) {
  // create socket
  int sockfd = socket(AF_INET, SOCK_STREAM, 0);
  CHECK_RET(sockfd, "socket");
  // create sockaddr
  struct sockaddr_in addr;
  bzero(&addr, sizeof(addr));
  addr.sin_family = AF_INET;
  inet_pton(AF_INET, ip, &addr.sin_addr);
  addr.sin_port = htons(port);
  // set sockaddr reuse
  int ena = 1;
  int err = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,  &ena, sizeof(ena));
  CHECK_RET(err, "setsockopt(SO_REUSEADDR)");
  // bind socket to addr
  err = bind(sockfd, (struct sockaddr*) &addr, sizeof(addr));
  CHECK_RET(err, "bind");
  // listen
  err = listen(sockfd, LISTEN_BACKLOG);
  CHECK_RET(err, "listen");
  return sockfd;
}


void handle_events(struct epoll_event* events, int n_events,
    int listenfd, int efd) {
  for (int i = 0; i < n_events; i++) {
    struct epoll_event* ev = events + i;
    struct conn_bundle* bundle = (struct conn_bundle*) ev->data.ptr;

    if (bundle->fd == listenfd) {
      if (ev->events & EPOLLIN) {
        // new incoming conn, get connfd
        int connfd = accept(listenfd, NULL, NULL);
        if (connfd == -1) continue; // ignore error
        // create event
        struct conn_bundle* bundle = malloc(sizeof(struct conn_bundle));
        memset(bundle, 'x', sizeof(struct conn_bundle));
        if (bundle == NULL) { // allocation failure, don't serve client
          close(connfd);
          free(bundle);
          printf("> refuse to serve client\n");
        } else {
          struct epoll_event event;
          event.events = EPOLLIN | EPOLLET;
          event.data.ptr = bundle;
          bundle->fd = connfd;
          epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
          printf("> got new client\n");
        }
      } // else: exception happened on listenfd. Now ignore that.
      continue; // exclusive with latter case
    }

    if (ev->events & EPOLLIN) { // some connfd readable
      // XXX: don't consider partial reads
      int n_read = read(bundle->fd, bundle->buf, BUNDLE_BUFSZ);
      if (n_read == 0) {
        printf("> client premature close\n");
        free(ev->data.ptr);
        epoll_ctl(efd, EPOLL_CTL_DEL, bundle->fd, NULL);
      } else if (n_read > 0) {
        bundle->bufsz = n_read;
        for (int i = 0; i < bundle->bufsz; i++) putchar(bundle->buf[i]);
        printf("> hexdump:\n");
        for (int i = 0; i < bundle->bufsz; i++) printf("%02x ", bundle->buf[i]);
        printf("\n");
        fflush(stdout);
        // now let epoll monitor for write
        ev->events = EPOLLOUT | EPOLLET;
        epoll_ctl(efd, EPOLL_CTL_MOD, bundle->fd, ev);
      } else { // error
        close(bundle->fd);
        free(bundle);
      }
    } 

    if (ev->events & EPOLLOUT) {
      // XXX: don't consider partial write
      write(bundle->fd, bundle->buf, bundle->bufsz);
      close(bundle->fd);
      free(bundle);
      printf("> finish client\n");
    }
  }
}


void main_loop(int listenfd) {
  // epoll setup
  int efd = epoll_create(1); // argument is ignored
  CHECK_RET(efd, "epoll_create");

  // monitor listenfd to become readable
  struct epoll_event event;
  event.events = EPOLLIN;   // don't use edge trigger for listenfd
  event.data.ptr = malloc(sizeof(struct conn_bundle));
  struct conn_bundle* bundle = (struct conn_bundle*) event.data.ptr;
  bundle->fd = listenfd;
  bundle->bufsz = 0;
  epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &event);

  // main loop
  while (1) {
    struct epoll_event events[NUM_EVENTS];
    int n_events = epoll_wait(efd, events, NUM_EVENTS, -1);
    if (n_events == -1 && errno == EINTR) continue;
    CHECK_RET(n_events, "epoll_wait"); // No possible error is expected here
    handle_events(events, n_events, listenfd, efd);
  }
}

int main() {
  int listenfd = listen_on(LISTEN_IP, LISTEN_PORT);
  main_loop(listenfd);
}

最后发现一个 telnet 的有意思的情况. 同样是做 TCP 连接, telnet 在我按下回车的时候发送的是 CR LF, 而 nc 只发送 LF. 并且要想让 telnet 只发送 LF 很难, 无法调整而且也不能用二进制发送. 我想古代人用 telnet 的时候也没想到连到的对面不是一个电传打字机的情况吧.

最大的感受就是, C 写网络编程真的麻烦, bind 一个 ip 都要弄好几行.