多人聊天室实现v2.0

多人聊天室

功能实现:

  1. 已实现功能
  • 输入quit退出
  • 无客户端连接(1分钟)后退出
  • 支持多语言输入
  • 高并发连接 (v2.0实现)
  1. 待完成功能
  • 可发送文件、图片等信息
  • 可进行语言翻译
  • 补充 ctrl+c 退出

1. 预备知识

1.1 select()

select()可同时监听多个 sockets,可通知程序哪些 sockets 有数据可以读取,哪些 sockets 可以写入。

类似的还有 poll()、epoll()

1.1.1 基本语法
c 复制代码
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

// 该函数通过 readfds、writefds、exceptfds 监听文件描述符的 sets
// 若可读取某个文件描述符,只需将 sockfd 新增到 readfds 中
// numfds 需设置为文件描述符的最高值加 1

int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

select()返回时,readfds 会被修改,用来反映设置的文件描述符中有哪些数据可以读取。可使用下述的FD_ISSET()宏来获取可读的文件描述符。

c 复制代码
FD_SET(int fd, fd_set *set); // 将 fd 新增到 set
FD_CLR(int fd, fd_set *set); // 从 set 中移除 fd
FD_ISSET(int fd, fd_set *set); // 若 fd 在 set 中,返回 true
FD_ZERO(fd_set *set); // 将 set 整个清为 0

select()同时可通过结构体struct timeval来设置 timeout 的周期:

c 复制代码
struct timeval{
    int tv_sec; // 秒
    int tv_usec; // 微秒
}

1.2 epoll

select()最多同时监听 1024 个fd,这是由宏_FD_SETSIZE决定的,虽然可以通过修改头文件再重新编译来扩大监听数目,但是治标不治本。并且随着 fd 数目的增加,其效率也会随之下降。

epoll采用红黑树管理文件描述符,不会随着监听 fd 数目的增长而降低效率。epoll有三个基本函数:

  1. epoll_create()
c 复制代码
// size - 指定监听数目大小
// 返回 epoll 专用的文件描述符
int epoll_create(int size);
  1. epoll_ctl()

epoll的事件注册函数,注册要监听的事件类型。

c 复制代码
/* 
epfd - epoll_create()函数的返回值
op - 表示动作,用三个宏来表示
    EPOLL_CTL_ADD - 注册新的 fd 到 epfd 中
    EPOLL_CTL_MOD - 修改已经注册的 fd 监听事件
    EPOLL_CTL_DEL - 从 epfd 中删除一个 fd
fd - 需要监听的文件描述符
event - 指定内核要监听什么事件
*/
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

events 可以是以下宏的集合:

  • EPOLLIN:表示对应的 fd 可以读
  • EPOLLOUT:表示对应的 fd 可以写
  • EPOLLPRI:表示对应的 fd 有紧急的数据可读
  • EPOLLERR:表示对应的 fd 发生错误
  • EPOLLHUP:表示对应的 fd 被挂断
  • EPOLLET:将 epoll 设为边缘触发模式
  • EPOLLONESHOT:只监听一次事件
  1. epoll_wait()

等待事件的产生,收集在 epoll 监控的事件中已经发生的事件。

c 复制代码
// maxevents - 告知内核有多少个 events
// timeout - 超时时间,单位为毫秒
// 成功返回 需要处理的事件数目,失败返回 -1,超时返回 0
int epoll_wait(int epfd, struct epoll_event *event, int maxevents, int timeout);

边缘触发和水平触发模式

水平触发(Level Trigger,LT):只要读缓冲区不为空,写缓冲区不满,那么epoll_wait()就会一直返回就绪。

边缘触发(Edge Trigger,ET):缓冲区的数据有变化,epoll_wait()就会返回就绪。使用 ET 模式,必须保证要一次性读完数据和写完数据

2. 简易多人聊天室 v1.0

2.1 代码实现

c 复制代码
// multiChat01.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#include <netdb.h>

#define PORT "9034"

void *get_in_addr(struct sockaddr* sa)
{
        if (sa->sa_family == AF_INET)
        {
                return &(((struct sockaddr_in*)sa)->sin_addr);
        }
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

int main(void)
{
        fd_set master; // 主要的文件描述符set
        fd_set read_fds; // 暂存fd set

        struct addrinfo hints, *ai, *p;
        struct sockaddr_storage remoteaddr; // client addr

        int yes = 1;
        int rv;
        int listener;
        int fdmax;
        int newfd;
        int i, j;
        socklen_t addrlen;

        char buf[4096]; // 存储 client 数据的缓冲区
        int nbytes;

        char remoteIP[INET6_ADDRSTRLEN];

        int clients = 0;
        time_t idle_start;
        struct timeval tv;
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;

        // getaddrinfo
        if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
        {
                fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
                exit(1);
        }

        // 遍历 ai 中获取的地址信息
        for (p = ai; p != NULL; p = p->ai_next)
        {
                // socket
                if ((listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
                {
                        continue;
                }

                // 避开错误信息 "address already in use"
                setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

                // bind
                if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
                {
                        close(listener); // 避免文件描述符泄露
                        continue;
                }

                break;
        }

        if (p == NULL)
        {
                fprintf(stderr, "selectserver: failed to bind\n");
                exit(2);
        }

        freeaddrinfo(ai);  // 释放链表

        // listen
        if (listen(listener, 10) == -1)
        {
                perror("listen");
                exit(3);
        }

        // 将 listener 新增到 master set
        FD_SET(listener, &master);

        // 持续追踪最大的 fd
        fdmax = listener;

        idle_start = time(NULL);

        // 主要循环
        for( ; ; )
        {
                read_fds = master;
                tv.tv_sec = 1;
                tv.tv_usec = 0;
                if (select(fdmax + 1, &read_fds, NULL, NULL, &tv) == -1)
                {
                        perror("select");
                        exit(4);
                }

                // 定时器:无客户端连接超过60秒则关闭服务器
                if (clients == 0 && time(NULL) - idle_start >= 60)
                {
                        printf("selectserver:空闲超时,服务器关闭\n");
                        break;
                }
                // 在现存的连接中寻找需要读取的数据
                for (i = 0; i <= fdmax; ++i)
                {
                        if (FD_ISSET(i, &read_fds))
                        {
                                if (i == listener)
                                {
                                        // 有新连接进入
                                        addrlen = sizeof(remoteaddr);
                                        // accept
                                        newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);

                                        if (newfd == -1)
                                        {
                                                perror("accept");
                                        }
                                        else
                                        {
                                                FD_SET(newfd, &master);
                                                if (newfd > fdmax)
                                                {
                                                        fdmax = newfd;
                                                }
                                                printf("selectserver:新连接来自 %s,套接字 %d 已进入聊天室\n",
                                                                inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr),
                                                                        remoteIP, INET6_ADDRSTRLEN), newfd);
                                                clients++;
                                        }
                                }
                                else
                                {
                                        // 处理来自 client 的数据
                                        if ((nbytes = recv(i, buf, sizeof(buf), 0)) <= 0)
                                        {
                                                // 连接关闭
                                                if (nbytes == 0)
                                                {
                                                        printf("selectserver:套接字 %d 已退出聊天室\n", i);
                                                }
                                                else
                                                {
                                                        perror("recv");
                                                }
                                                close(i);
                                                FD_CLR(i, &master); // 从 master set 中移出
                                                clients--;
                                                if (clients == 0)
                                                        idle_start = time(NULL);
                                        }
                                        else
                                        {
                                                buf[strcspn(buf, "\r\n")] = 0;
                                                if (strcmp(buf, "quit") == 0)
                                                {
                                                        printf("selectserver:套接字 %d 已退出聊天室\n", i);
                                                        close(i);
                                                        FD_CLR(i, &master);
                                                        clients--;
                                                        if (clients == 0)
                                                                idle_start = time(NULL);
                                                        nbytes = sprintf(buf, "套接字 %d 已退出聊天室\n", i);
                                                }
                                                for (j = 0; j <= fdmax; ++j)
                                                {
                                                        if (FD_ISSET(j, &master))
                                                        {
                                                                if (j != listener && j != i)
                                                                {
                                                                        if (send(j, buf, nbytes, 0) == -1)
                                                                        {
                                                                                perror("send");
                                                                        }
                                                                }
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }

        return 0;
}

2.2 编译运行

bash 复制代码
# 编译
gcc -o multiChat_demo multiChat.c

在同一台终端实验:

bash 复制代码
# 运行
./multiChat_demo

# 开启其他窗口(至少两个,因为发送的消息并不在服务器端显示)
#      hostname   port
telnet 127.0.0.1 9034


# 退出
quit

3. 多人聊天室 v2.0

使用epoll()替换select(),实现高并发连接。

3.1 代码实现

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <time.h>
#include <netinet/in.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>

#define PORT "9034"
#define MAX_EVENTS 4096 // 最大时间数 
#define MAX_CLIENTS 4096

void *get_in_addr(struct sockaddr* sa)
{
        if (sa->sa_family == AF_INET)
        {
                return &(((struct sockaddr_in*)sa)->sin_addr);
        }
        return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

static void set_nonblocking(int fd)
{
        int flags = fcntl(fd, F_GETFL, 0);
        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

// 从 client_fds 数组中移除指定 fd
static void remove_client(int fd, int *client_fds, int *count)
{
        for (int k = 0; k < *count; k++)
        {
                if (client_fds[k] == fd)
                {
                        client_fds[k] = client_fds[--(*count)]; // 用尾元素替换,count 递减
                        return;
                }
        }
}

// 检查 fd 是否在 client_fds 中(防止同批次事件中已关闭的 fd 被重复处理)
static int client_exists(int fd, int *client_fds, int count)
{
        for (int k = 0; k < count; k++)
                if (client_fds[k] == fd)
                        return 1;
        return 0;
}


int main(void)
{
        int client_fds[MAX_CLIENTS]; // 客户端 fd 数组,替代 fd_set
        int client_count = 0;

        struct addrinfo hints, *ai, *p;
        struct sockaddr_storage remoteaddr; // client addr

        int yes = 1;
        int rv;
        int listener;
        int newfd;
        int i, j;
        socklen_t addrlen;

        char buf[4096]; // 存储 client 数据的缓冲区
        int nbytes;

        char remoteIP[INET6_ADDRSTRLEN];

        time_t idle_start;

        int epfd;
        struct epoll_event ev, events[MAX_EVENTS];

        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;
        hints.ai_flags = AI_PASSIVE;

        // getaddrinfo
        if ((rv = getaddrinfo(NULL, PORT, &hints, &ai)) != 0)
        {
                fprintf(stderr, "selectserver: %s\n", gai_strerror(rv));
                exit(1);
        }

        // 遍历 ai 中获取的地址信息
        for (p = ai; p != NULL; p = p->ai_next)
        {
                // socket
                if ((listener = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)
                {
                        continue;
                }

                // 避开错误信息 "address already in use"
                setsockopt(listener, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

                // bind
                if (bind(listener, p->ai_addr, p->ai_addrlen) < 0)
                {
                        close(listener); // 避免文件描述符泄露
                        continue;
                }

                break;
        }


        if (p == NULL)
        {
                fprintf(stderr, "selectserver: failed to bind\n");
                exit(2);
        }

        freeaddrinfo(ai);  // 释放链表

        // listen
        if (listen(listener, SOMAXCONN) == -1)
        {
                perror("listen");
                exit(3);
        }

        set_nonblocking(listener);

        idle_start = time(NULL);

        // epoll 初始化
        epfd = epoll_create1(0);
        ev.events = EPOLLIN;
        ev.data.fd = listener;
        epoll_ctl(epfd, EPOLL_CTL_ADD, listener, &ev);

        // 主要循环
        for( ; ; )
        {
                int nfds = epoll_wait(epfd, events, MAX_EVENTS, 1000);
                if (nfds == -1)
                {
                        perror("epoll_wait");
                        exit(4);
                }

                // 定时器:无客户端连接超过60秒则关闭服务器
                if (client_count == 0 && time(NULL) - idle_start >= 60)
                {
                        printf("selectserver:空闲超时,服务器关闭\n");
                        break;
                }

                // 在现存的活动连接中处理事件
                for (i = 0; i < nfds; ++i)
                {
                        if (events[i].data.fd == listener)
                        {
                                // 有新连接进入(循环 accept 以应对高并发连接风暴)
                                while (1)
                                {
                                        addrlen = sizeof(remoteaddr);
                                        newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);

                                        if (newfd == -1)
                                        {
                                                if (errno == EAGAIN || errno == EWOULDBLOCK)
                                                        break;
                                                perror("accept");
                                                break;
                                        }

                                        set_nonblocking(newfd);
                                        if (client_count >= MAX_CLIENTS)
                                        {
                                                printf("selectserver:连接数已达上限 %d,拒绝新连接\n", MAX_CLIENTS);
                                                close(newfd);
                                                break;
                                        }
                                        client_fds[client_count++] = newfd;
                                        ev.events = EPOLLIN;
                                        ev.data.fd = newfd;
                                        epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &ev);
                                        printf("selectserver:新连接来自 %s,套接字 %d 已进入聊天室\n",
                                                        inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr),
                                                                remoteIP, INET6_ADDRSTRLEN), newfd);
                                }
                        }
                        else
                        {
                                int client_fd = events[i].data.fd;

                                // 同一批次事件中可能已被前序事件关闭(fd 重用),跳过
                                if (!client_exists(client_fd, client_fds, client_count))
                                        continue;

                                // 处理来自 client 的数据
                                if ((nbytes = recv(client_fd, buf, sizeof(buf), 0)) <= 0)
                                {
                                        // 连接关闭
                                        if (nbytes == 0)
                                        {
                                                printf("selectserver:套接字 %d 已退出聊天室\n", client_fd);
                                        }
                                        else
                                        {
                                                perror("recv");
                                        }
                                        epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL);
                                        close(client_fd);
                                        remove_client(client_fd, client_fds, &client_count);
                                        if (client_count == 0)
                                                idle_start = time(NULL);
                                }
                                else
                                {
                                        buf[strcspn(buf, "\r\n")] = 0;
                                        if (strcmp(buf, "quit") == 0)
                                        {
                                                printf("selectserver:套接字 %d 已退出聊天室\n", client_fd);
                                                epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, NULL);
                                                close(client_fd);
                                                remove_client(client_fd, client_fds, &client_count);
                                                if (client_count == 0)
                                                        idle_start = time(NULL);
                                                nbytes = sprintf(buf, "套接字 %d 已退出聊天室\n", client_fd);
                                        }
                                        for (j = 0; j < client_count; ++j)
                                        {
                                                int target = client_fds[j];
                                                if (target != client_fd)
                                                {
                                                        int total = 0;
                                                        int retry = 0;
                                                        while (total < nbytes)
                                                        {
                                                                int sent = send(target, buf + total, nbytes - total, 0);
                                                                if (sent == -1)
                                                                {
                                                                        if ((errno == EAGAIN || errno == EWOULDBLOCK) && retry < 3)
                                                                {
                                                                        retry++;
                                                                        usleep(100);
                                                                        continue;
                                                                }
                                                                        if (errno != EPIPE)
                                                                                perror("send");
                                                                        break;
                                                                }
                                                                total += sent;
                                                                retry = 0;
                                                        }
                                                }
                                        }
                                }
                        }
                }
        }

        return 0;
}

3.2 编译运行

bash 复制代码
# 编译
g++ -o multiChat02_demp multiChat02.c
# 运行
./multiChat02

3.3 测试代码

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <netdb.h>
#include <fcntl.h>
#include <errno.h>
#include <time.h>
#include <signal.h>

#define PORT "9034"
#define MAX_CONN 5000
#define MSG "你好,这是一条中文测试消息"

static volatile int running = 1;

static void sig_handler(int sig) { (void)sig; running = 0; }

static void set_nonblocking(int fd)
{
        int flags = fcntl(fd, F_GETFL, 0);
        fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main(int argc, char *argv[])
{
        int total = MAX_CONN;
        int batch = 100;
        int delay_ms = 0;

        if (argc > 1) total = atoi(argv[1]);
        if (argc > 2) batch = atoi(argv[2]);
        if (argc > 3) delay_ms = atoi(argv[3]);

        printf("=== 高并发测试客户端 ===\n");
        printf("目标: %d 连接, 每批 %d 个, 间隔 %d ms\n", total, batch, delay_ms);

        signal(SIGPIPE, SIG_IGN);
        signal(SIGINT, sig_handler);

        struct addrinfo hints, *ai;
        memset(&hints, 0, sizeof(hints));
        hints.ai_family = AF_UNSPEC;
        hints.ai_socktype = SOCK_STREAM;

        int rv;
        if ((rv = getaddrinfo("127.0.0.1", PORT, &hints, &ai)) != 0)
        {
                fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
                return 1;
        }

        int *fds = calloc(total, sizeof(int));
        int connected = 0;
        int failed = 0;

        struct timeval start, end;
        gettimeofday(&start, NULL);

        // 分批建立连接
        for (int b = 0; b < total && running; b += batch)
        {
                int limit = (b + batch < total) ? batch : total - b;
                for (int k = 0; k < limit; k++)
                {
                        int idx = b + k;
                        fds[idx] = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
                        if (fds[idx] < 0) { failed++; continue; }

                        if (connect(fds[idx], ai->ai_addr, ai->ai_addrlen) < 0)
                        {
                                if (errno != EINPROGRESS)
                                {
                                        close(fds[idx]);
                                        fds[idx] = -1;
                                        failed++;
                                        continue;
                                }
                        }
                        set_nonblocking(fds[idx]);
                }

                // 等待这批连接完成(用 epoll 检测可写)
                int epfd = epoll_create1(0);
                struct epoll_event ev, events[batch];

                ev.events = EPOLLOUT;
                for (int k = 0; k < limit; k++)
                {
                        int idx = b + k;
                        if (fds[idx] < 0) continue;
                        ev.data.fd = fds[idx];
                        epoll_ctl(epfd, EPOLL_CTL_ADD, fds[idx], &ev);
                }

                int remain = limit;
                while (remain > 0 && running)
                {
                        int n = epoll_wait(epfd, events, batch, delay_ms > 0 ? delay_ms : 1000);
                        if (n == 0) break;
                        for (int i = 0; i < n; i++)
                        {
                                int fd = events[i].data.fd;
                                int err; socklen_t len = sizeof(err);
                                getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &len);
                                if (err == 0) connected++;
                                else          { failed++; close(fd); }
                                epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                                remain--;
                        }
                }
                close(epfd);

                if (b % 500 == 0 && b > 0)
                        printf("  进度: %d/%d 已连接\n", b, total);
                if (delay_ms > 0 && b + batch < total)
                        usleep(delay_ms * 1000);
        }

        gettimeofday(&end, NULL);
        double elapsed = (end.tv_sec - start.tv_sec) + (end.tv_usec - start.tv_usec) / 1000000.0;

        printf("\n--- 连接阶段 ---\n");
        printf("成功: %d, 失败: %d, 耗时: %.2f 秒\n", connected, failed, elapsed);

        // 等待服务器稳定
        sleep(1);

        if (connected == 0)
        {
                printf("没有可用连接,退出\n");
                free(fds);
                freeaddrinfo(ai);
                return 1;
        }

        // 少量客户端发送消息(避免广播风暴)
        printf("\n--- 消息发送阶段 (%d 个客户端发送) ---\n", connected < 20 ? connected : 20);
        int sent_ok = 0, sent_err = 0;
        int senders = connected < 20 ? connected : 20;
        for (int i = 0; i < senders && running; i++)
        {
                if (fds[i] < 0) continue;
                char msg[256];
                int len = snprintf(msg, sizeof(msg), "[客户端 %d] %s %d\n", i, MSG, rand() % 10000);
                if (send(fds[i], msg, len, 0) == len)
                        sent_ok++;
                else
                        sent_err++;
        }
        printf("发送成功: %d, 发送失败: %d\n", sent_ok, sent_err);

        // 接收一段时间
        printf("\n--- 接收阶段 (5秒) ---\n");
        int epfd = epoll_create1(0);
        struct epoll_event ev, events[256];

        ev.events = EPOLLIN;
        int active = 0;
        for (int i = 0; i < total; i++)
        {
                if (fds[i] < 0) continue;
                ev.data.fd = fds[i];
                epoll_ctl(epfd, EPOLL_CTL_ADD, fds[i], &ev);
                active++;
        }

        time_t recv_start = time(NULL);
        int total_bytes = 0;
        while (time(NULL) - recv_start < 5 && active > 0 && running)
        {
                int n = epoll_wait(epfd, events, 256, 100);
                if (n <= 0) continue;
                for (int i = 0; i < n; i++)
                {
                        char rbuf[4096];
                        int r = recv(events[i].data.fd, rbuf, sizeof(rbuf), 0);
                        if (r <= 0)
                        {
                                epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
                                close(events[i].data.fd);
                                active--;
                        }
                        else
                        {
                                total_bytes += r;
                        }
                }
        }
        close(epfd);
        printf("收到总字节: %d, 仍活跃: %d 连接\n", total_bytes, active);

        // 部分客户端发送 quit
        printf("\n--- 退出阶段: %d 个客户端发送 quit ---\n", connected / 4);
        for (int i = 0; i < connected / 4 && fds[i] >= 0 && running; i++)
        {
                if (fds[i] < 0) continue;
                send(fds[i], "quit\r\n", 6, 0);
        }

        // 等待剩下的连接接收广播
        sleep(3);

        // 清理
        printf("\n--- 清理剩余连接 ---\n");
        for (int i = 0; i < total; i++)
        {
                if (fds[i] >= 0) close(fds[i]);
        }
        free(fds);
        freeaddrinfo(ai);

        printf("测试完成\n");
        return 0;
}

3.4 连接测试

bash 复制代码
# 将服务器的输出重定向到 日志文件中
./multiChat02 > /tmp/server4k2.log 2>&1 & sleep 0.5

# 测试 4000 个连接接入
./bench_client 4000 200 10 2>&1 | grep -E "^(===|---|成功|发送|收到|目标|测试|  进度)" 

kill %1 2>/dev/null; wait 2>/dev/null

# 查看返回状态
echo "exit=$?"
echo "---"
tail -3 /tmp/server4k2.log
相关推荐
旖-旎1 小时前
QT系统篇(5)(下)
开发语言·c++·qt
99乘法口诀万物皆可变1 小时前
PcanToVectorXL_V01:打通 Vector 与 PCAN 的双向 CAN/CAN‑FD 桥梁
c++·学习
liulun2 小时前
C++ WinRT中的事件
开发语言·c++
whitelbwwww2 小时前
c++运行onnx模型
开发语言·c++
C路在脚下3 小时前
HSMS 连接总失败?排查这 5 个配置点
c++·嵌入式硬件
郝学胜_神的一滴3 小时前
Qt 高级编程 034:深耕QWidget底层内核—彻底吃透无边框窗口设计核心原理
c++·qt
QiLinkOS4 小时前
第三视觉理解徐玉生与他的商业活动(26)
大数据·c++·人工智能·算法·开源协议
chase_my_dream4 小时前
FAST-LIO src/IMU_Processing.hpp 完整详细讲解
c++·状态模式·slam
旖-旎5 小时前
《LeetCode 1137 第N个泰波那契数 和 LeetCode 三步问题》
c++·算法·leetcode·动态规划