云计算百科
云计算领域专业知识百科平台

【Linux入门环境编程】百万并发的服务器

【Linux入门环境编程】百万并发的服务器

1 百万并发的服务器实现

tcp_server实现百万并发
  • 并发——一秒钟的请求数量(qps)
实现步骤:
  • 准备4个虚拟机,最小要求:

    • 至少一个4G内存,2核CPU,作为服务器——Server——128
    • 另外三个2G内存,1核CPU,作为客户端——Client——129、130、131
  • 服务器代码:tcp_server.c

    • 注意变化:每新增一个连接,新增一个端口

    //额外引入头文件

    #include <unistd.h>

  • 客户端测试代码:mul_port_client_epoll.c

    • 注意根据服务器能接受的端口数量,设置MAX_PORT的端口数量

    //额外引入头文件

    #include <unistd.h>

    #include <sys/time.h>

  • 代码实现
    tcp_server.c

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

    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <pthread.h>

    #include <errno.h>
    #include <fcntl.h>

    #include <sys/epoll.h>

    #include <unistd.h>

    #define BUFFER_LENGTH1024
    #define EPOLL_SIZE1024

    #define MAX_PORT100 // 最大可复用端口数量

    void *client_routine(void *arg) {

    int clientfd = *(int *)arg;

    while (1) {

    char buffer[BUFFER_LENGTH] = {0};
    int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    if (len < 0) {
    close(clientfd);
    break;
    } else if (len == 0) { // disconnect
    close(clientfd);
    break;
    } else {
    printf("Recv: %s, %d byte(s)\\n", buffer, len);
    }

    }

    }

    // 检查传入的文件描述符 fd 是否存在于一个文件描述符数组 fds 中
    int islistenfd(int fd, int *fds) {

    int i = 0;
    for (i = 0;i < MAX_PORT;i ++) {
    if (fd == *(fds+i)) return fd;
    }

    return 0;
    }

    // ./tcp_server

    int main(int argc, char *argv[]) {

    if (argc < 2) {
    printf("Param Error\\n");
    return -1;
    }

    int port = atoi(argv[1]); // start
    int sockfds[MAX_PORT] = {0}; // listen fd
    int epfd = epoll_create(1);

    int i = 0;
    for (i = 0;i < MAX_PORT;i ++) { // 每创建100个端口

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port+i); // 8888 8889 8890 8891 …. 8987
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
    perror("bind");
    return 2;
    }

    if (listen(sockfd, 5) < 0) {
    perror("listen");
    return 3;
    }
    printf("tcp server listen on port : %d\\n", port + i);

    //
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sockfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

    sockfds[i] = sockfd;
    }
    //

    #if 0

    while (1) {

    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(struct sockaddr_in));
    socklen_t client_len = sizeof(client_addr);

    int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);

    pthread_t thread_id;
    pthread_create(&thread_id, NULL, client_routine, &clientfd);

    }

    #else

    struct epoll_event events[EPOLL_SIZE] = {0};

    while (1) {

    int nready = epoll_wait(epfd, events, EPOLL_SIZE, 5); // -1, 0, 5
    if (nready == -1) continue;

    int i = 0;
    for (i = 0;i < nready;i ++) {

    int sockfd = islistenfd(events[i].data.fd, sockfds);
    if (sockfd) { // listen 2

    struct sockaddr_in client_addr;
    memset(&client_addr, 0, sizeof(struct sockaddr_in));
    socklen_t client_len = sizeof(client_addr);

    int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);

    fcntl(clientfd, F_SETFL, O_NONBLOCK);

    int reuse = 1;
    setsockopt(clientfd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));

    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = clientfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);

    } else {

    int clientfd = events[i].data.fd;

    char buffer[BUFFER_LENGTH] = {0};
    int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
    if (len < 0) {
    close(clientfd);

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = clientfd;
    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);

    } else if (len == 0) { // disconnect
    close(clientfd);

    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = clientfd;
    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);

    } else {
    printf("Recv: %s, %d byte(s), clientfd: %d\\n", buffer, len, clientfd);
    }

    }

    }

    }

    #endif

    return 0;
    }

    mul_port_client_epoll.c

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

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <errno.h>
    #include <netinet/tcp.h>
    #include <arpa/inet.h>
    #include <netdb.h>
    #include <fcntl.h>

    #include <unistd.h>
    #include <sys/time.h>

    #define MAX_BUFFER128
    #define MAX_EPOLLSIZE(384*1024)
    #define MAX_PORT100

    #define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec – tv2.tv_sec) * 1000 + (tv1.tv_usec – tv2.tv_usec) / 1000)

    int isContinue = 0;

    static int ntySetNonblock(int fd) {
    int flags;

    flags = fcntl(fd, F_GETFL, 0);
    if (flags < 0) return flags;
    flags |= O_NONBLOCK;
    if (fcntl(fd, F_SETFL, flags) < 0) return -1;
    return 0;
    }

    static int ntySetReUseAddr(int fd) {
    int reuse = 1;
    return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
    }

    int main(int argc, char **argv) {
    if (argc <= 2) {
    printf("Usage: %s ip port\\n", argv[0]);
    exit(0);
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);
    int connections = 0;
    char buffer[128] = {0};
    int i = 0, index = 0;

    struct epoll_event events[MAX_EPOLLSIZE];

    int epoll_fd = epoll_create(MAX_EPOLLSIZE);

    strcpy(buffer, " Data From MulClient\\n");

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);

    struct timeval tv_begin;
    gettimeofday(&tv_begin, NULL);

    while (1) {
    if (++index >= MAX_PORT) index = 0;

    struct epoll_event ev;
    int sockfd = 0;

    if (connections < 340000 && !isContinue) {
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
    perror("socket");
    goto err;
    }

    //ntySetReUseAddr(sockfd);
    addr.sin_port = htons(port+index);

    if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
    printf("port, index : %d, %d\\n", port, index);
    perror("connect");
    goto err;
    }
    ntySetNonblock(sockfd);
    ntySetReUseAddr(sockfd);

    sprintf(buffer, "Hello Server: client –> %d\\n", connections);
    send(sockfd, buffer, strlen(buffer), 0);

    ev.data.fd = sockfd;
    ev.events = EPOLLIN | EPOLLOUT;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);

    connections ++;
    }
    //connections ++;
    if (connections % 1000 == 999 || connections >= 340000) {
    struct timeval tv_cur;
    memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));

    gettimeofday(&tv_begin, NULL);

    int time_used = TIME_SUB_MS(tv_begin, tv_cur);
    printf("connections: %d, sockfd:%d, time_used:%d\\n", connections, sockfd, time_used);

    int nfds = epoll_wait(epoll_fd, events, connections, 100);
    for (i = 0;i < nfds;i ++) {
    int clientfd = events[i].data.fd;

    if (events[i].events & EPOLLOUT) {
    sprintf(buffer, "data from %d\\n", clientfd);
    send(sockfd, buffer, strlen(buffer), 0);
    } else if (events[i].events & EPOLLIN) {
    char rBuffer[MAX_BUFFER] = {0};
    ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
    if (length > 0) {
    printf(" RecvBuffer:%s\\n", rBuffer);

    if (!strcmp(rBuffer, "quit")) {
    isContinue = 0;
    }

    } else if (length == 0) {
    printf(" Disconnect clientfd:%d\\n", clientfd);
    connections –;
    close(clientfd);
    } else {
    if (errno == EINTR) continue;

    printf(" Error clientfd:%d, errno:%d\\n", clientfd, errno);
    close(clientfd);
    }
    } else {
    printf(" clientfd:%d, errno:%d\\n", clientfd, errno);
    close(clientfd);
    }
    }
    }

    usleep(1 * 1000);
    }

    return 0;

    err:
    printf("error : %s\\n", strerror(errno));
    return 0;

    }

    百万并发服务器出现问题:
    connection refused问题:

    sudo ./2_mul_port_client_epoll 192.168.21.128 80
    connect: Connection refused
    error : Connection refused

    • 服务器不允许链接,可能原因1:文件系统默认每个进程的fd个数只有1024个

      • 修改文件 /etc/security/limits.conf,加入

        * soft nofile 1048576
        * hard nofile 1048576

        修改完成后重启 sudo reboot

      • 也可以通过 ulimit -a进行修改:

        sudo su
        sudo ulimit -n 1048576

      • 出现问题:在 Ubuntu 20 中无法修改open files (-n)限制

        • 查找原因:Ubuntu 20 可能使用了systemd的资源控制功能

        • 进入/etc/systemd/system.conf检查是否有对LimitNOFILE(文件描述符限制)的设置,发现

          #DefaultLimitNOFILE=1024:52428

          找到原因,修改成:

          #DefaultLimitNOFILE=1024:1048576

        • 再次执行以上修改,修改成功!

    request address问题:
    • 客户端报错request address问题,在客户端连接数达到27000之前连接断开,可能问题:客户端端口地址耗尽
      • 端口地址只有65535个,如何开启多个端口实现监听
      • 解决方法:把服务器的监听端口增加到100个,针对不同的
    connection timed out问题
    • 客户端又报错connection timed out问题,在客户端连接数达到65000之前断开,可能问题: 文件系统达到最大值,防火墙连接达到最大值

      • 检查文件系统最大值 fs.file-max:

        cat /proc/sys/fs/file-max

        服务器返回:9223372036854775807,三个客户端返回:397322

      • 检查内核中防火墙对外连接最大值 nf_conntrack_max

        cat /proc/sys/net/netfilter/nf_ //用tab查找你想要的信息

        服务器返回:65535,三个客户端返回:65535

        如果没有nf_conntrack_max可以加载以下命令:

        sudo modprobe ip_conntrack

        内核中的参数设置都可以进入 /etc/sysctl.conf

        sudo vim /etc/sysctl.conf

        增加:

        fs.file-max = 1048576
        net.netfilter.nf_conntrack_max=1048576 //ubuntu20\\16可用

        命令生效:

        sudo sysctl -p

        如果出现报错:

        sysctl: cannot stat /proc/sys/net/netfilter/nf_conntrack max: No such file or directory

        可以加载以下命令:

        sudo modprobe ip_conntrack

    服务器内存崩溃,内存回收
    • 跑到79w左右时,服务器内存崩溃,CPU利用率达到100%

    • 在20w左右时,而且连接一开始时服务器内存就已经跑满,开始内存回收

    • 注意CPU和内存不要超过80%

    • 每次启动时都要重新执行 sudo sysctl -p

      net.ipv4.tcp_mem = 252144 524288 786432
      net.ipv4.tcp_wmem = 1024 1024 2048
      net.ipv4.tcp_rmem = 1024 1024 2048

      这是TCP协议栈的配置,其中:

      • tcp_mem = 252144 524288 786432 指TCP协议栈的总内存容量为 1G 2G 3G,即在内存12G之间时不做操作,在内存23G之间时减缓分配,在内存3G之上时停止分配;
      • tcp_wmem = 1024 1024 2048指socket发送缓冲区为 1K 1K 2K,分别是最小、默认、最大
      • tcp_rmem = 1024 1024 2048指socket接收缓冲区为 1K 1K 2K,分别是最小、默认、最大
    服务器CPU崩溃,CPU使用率占满
    • 一旦数据连接到80w(804453\\804125),CPU核心会全部突然占用100%(8个核心都不管用),然后进程崩溃

    https://github.com/0voice

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【Linux入门环境编程】百万并发的服务器
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!