对端的异常状况

避免对端挂掉, 本端 read 一直阻塞:

  • read 设置超时
  1. struct timeval tv;
  2. tv.tv_sec = 5;
  3. tv.tv_usec = 0;
  4. setsockopt(connfd, SOL_SOCKET, SO_RCVTIMEO, (const char *) &tv, sizeof tv);
  5. while (1) {
  6. int nBytes = recv(connfd, buffer, sizeof(buffer), 0);
  7. if (nBytes == -1) {
  8. if (errno == EAGAIN || errno == EWOULDBLOCK) {
  9. printf("read timeout\n");
  10. onClientTimeout(connfd);
  11. } else {
  12. error(1, errno, "error read message");
  13. }
  14. } else if (nBytes == 0) {
  15. error(1, 0, "client closed \n");
  16. }
  17. ...
  18. }
  • 添加对连接是否正常的检测。如果连接不正常,需要从当前 read 阻塞中返回并处理。
    • 应用层心跳
  • 多路复用自带的超时能力
struct timeval tv;
tv.tv_sec = 5;
tv.tv_usec = 0;

FD_ZERO(&allreads);
FD_SET(socket_fd, &allreads);
for (;;) {
    readmask = allreads;
    int rc = select(socket_fd + 1, &readmask, NULL, NULL, &tv);
    if (rc < 0) {
      error(1, errno, "select failed");
    }
    if (rc == 0) {
      printf("read timeout\n");
      onClientTimeout(socket_fd);
    }
 ...   
}

缓冲区处理

程序中可能出现的漏洞

第一个例子

char Response[] = "COMMAND OK";
char buffer[128];

while (1) {
    int nBytes = recv(connfd, buffer, sizeof(buffer), 0);
    if (nBytes == -1) {
        error(1, errno, "error read message");
    } else if (nBytes == 0) {
        error(1, 0, "client closed \n");
    }

    buffer[nBytes] = '\0';
    if (strcmp(buffer, "quit") == 0) {
        printf("client quit\n");
        send(socket, Response, sizeof(Response), 0);
    }

    printf("received %d bytes: %s\n", nBytes, buffer);
}

容量超出限制:

char buffer[128];
buffer[128] = '\0';

修改:

int nBytes = recv(connfd, buffer, sizeof(buffer)-1, 0);

第二个例子

对报文长度保持警惕:

size_t read_message(int fd, char *buffer, size_t length) {
    u_int32_t msg_length;
    u_int32_t msg_type;
    int rc;

    rc = readn(fd, (char *) &msg_length, sizeof(u_int32_t));
    if (rc != sizeof(u_int32_t))
        return rc < 0 ? -1 : 0;
    msg_length = ntohl(msg_length);

    rc = readn(fd, (char *) &msg_type, sizeof(msg_type));
    if (rc != sizeof(u_int32_t))
        return rc < 0 ? -1 : 0;

    if (msg_length > length) {
        return -1;
    }

    /* Retrieve the record itself */
    rc = readn(fd, buffer, msg_length);
    if (rc != msg_length)
        return rc < 0 ? -1 : 0;
    return rc;
}

第三个例子

recv 函数的效率比较低:

size_t readline(int fd, char *buffer, size_t length) {
    char *buf_first = buffer;

    char c;
    while (length > 0 && recv(fd, &c, 1, 0) == 1) {
        *buffer++ = c;
        length--;
        if (c == '\n') {
            *buffer = '\0';
            return buffer - buf_first;
        }
    }

    return -1;
}

改进:

size_t readline(int fd, char *buffer, size_t length) {
    char *buf_first = buffer;
    static char *buffer_pointer;
    int nleft = 0;
    static char read_buffer[512];
    char c;

    while (length-- > 0) {
        if (nleft <= 0) {
            int nread = recv(fd, read_buffer, sizeof(read_buffer), 0);
            if (nread < 0) {
                if (errno == EINTR) {
                    length++;
                    continue;
                }
                return -1;
            }
            if (nread == 0)
                return 0;
            buffer_pointer = read_buffer;
            nleft = nread;
        }
        c = *buffer_pointer++;
        *buffer++ = c;
        nleft--;
        if (c == '\n') {
            *buffer = '\0';
            return buffer - buf_first;
        }
    }
    return -1;
}

再次改进:

size_t readline(int fd, char *buffer, size_t length) {
    char *buf_first = buffer;
    static char *buffer_pointer;
    int nleft = 0;
    static char read_buffer[512];
    char c;

    while (--length> 0) {
        if (nleft <= 0) {
            int nread = recv(fd, read_buffer, sizeof(read_buffer), 0);
            if (nread < 0) {
                if (errno == EINTR) {
                    length++;
                    continue;
                }
                return -1;
            }
            if (nread == 0)
                return 0;
            buffer_pointer = read_buffer;
            nleft = nread;
        }
        c = *buffer_pointer++;
        *buffer++ = c;
        nleft--;
        if (c == '\n') {
            *buffer = '\0';
            return buffer - buf_first;
        }
    }
    return -1;
}