多人聊天室实现v2.0

Python实现网络聊天室(支持多人聊天与私聊) 实验名称: 网络聊天室 功能: i. 掌握利用Socket进行编程的技术 ii. 掌握多线程技术,保证双方可以同时发送 iii. 建立聊天工具 iv. 可以和单人聊天 v. 可以和多个人同时进行聊天 vi. 使用图形界面,显示双方的语录 vii. 程序可以在一定程度上进行错误识别 概述 实验通过聊天室可以完成单人或多人之间的聊天通信,功能的实现主要是通过Socket通信来实现。本次实验采用客户端/服务器(C/S)架构模式,通过Python语言来编写服务器端与客户端的程序。运用多线程可完成多点对多点的聊天。 阅读详情

多人聊天室

功能实现:

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

1. 预备知识

1.1 select()

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

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

1.1.1 基本语法
#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()宏来获取可读的文件描述符。

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 的周期:

struct timeval{
    int tv_sec; // 秒
    int tv_usec; // 微秒
}

1.2 epoll

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

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

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

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

/* 
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 监控的事件中已经发生的事件。

// 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 代码实现

// 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 编译运行

# 编译
gcc -o multiChat_demo multiChat.c

在同一台终端实验:

# 运行
./multiChat_demo

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


# 退出
quit

3. 多人聊天室 v2.0

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

3.1 代码实现

#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 编译运行

# 编译
g++ -o multiChat02_demp multiChat02.c
# 运行
./multiChat02

3.3 测试代码

#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 连接测试

# 将服务器的输出重定向到 日志文件中
./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
【linux】多人聊天室实现 一、设计思路 二、 阅读详情

相关推荐

JAVA多人聊天室

JAVA多人聊天室 第一次写自己的程序,写的多为潦草,有兴趣的伙伴简单看看就好 首先是 ChatServerSocket类 import javax.swing.*; import java.awt.*; import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; public class ChatServerSoc

t010107的博客 473

Linux C多人网络聊天室

Linux C多人网络聊天室 程序完整,确保能用

qq_36863664的博客 8352

TCP 聊天室v2 实现多人匿名聊天 C++,linux系统下

使用select IO复用实现多人匿名聊天室 功能:用户可发送消息,并同时能看到其他用户发的消息,用户输入over退出 Server: #include <sys/socket.h> #include <string> #include <set> #include <sys/select.h> #include <netinet/in...

qq_33500066的博客 848

基于socket实现的多人聊天室项目(python)

最近做了一个多人聊天室项目,主要目的是练习和巩固socket编程。 在开始之前,可以先了解一下socket的TCP三次握手和四次挥手。 参考:https://blog.csdn.net/luoyoub/article/details/82348863?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522159722038919724848337464%2522%252C%2522scm%2522%253A%252220140713.130102

qq_16594593的博客 8645

C++在线多人聊天室

基于SOCKET套接字的聊天程序 当初没有考虑要和别人一起开发什么,所以没有注释, 我还是很相信自己码风的,你肯定看得懂 虽然努力了但还是BUG多多,仅供参考,下次更新就是在暑假了 窝还是一个可怜的高中OIer就不要在意那么多细节嘛 服务端(cmd界面) #include <winsock2.h> #include <windows.h> #include <thr...

saxiy的博客 7508

Java多人实时聊天室设计与实现

Java在网络编程方面提供了丰富的API和框架,这使得开发健壮、高效、跨平台的网络应用程序成为可能。Java的java.net包提供了底层网络协议如TCP和UDP的支持,并且允许开发者在较高的抽象层面进行网络通信。这些API不仅简化了网络编程的复杂性,还提供了安全性、多线程处理等高级特性。客户端-服务器模型是网络应用程序开发中的一种核心架构,它将应用程序的功能分为两个部分:客户端和服务器端。客户端是用户界面,负责展示信息给用户并接收用户输入;服务器端则处理业务逻辑、数据存储以及响应客户端请求。

weixin_35509395的博客 805

linux程序多人聊天室,【linux】多人聊天室实现

一、设计思路服务器接收来自客户端的连接请求,当有客户端发送过来数据时,服务器将数据保存到全局缓冲区,并将数据循环发送给已经连接的客户端二、代码展示1. 服务器端charroom_server_01.c#include #include #include #include #include #include #include #include #include //for INADDR_ANY...

weixin_36384964的博客 1969

全面构建多人在线聊天室 Chat Room V1.0

在HTML5和WebSocket的结合使用下,创建一个实时通信的聊天室应用变得相对简单。开发者可以利用WebSocket提供的API来建立连接、发送和接收消息,并将实时更新推送到前端页面。通过这样的集成,可以为用户提供一个高效、实时的沟通体验。

weixin_42515392的博客 1023

拿来主义——WebSocket多人聊天室和聊天列表实现

当有用户在聊天室发送消息时,消息正常推送,同步调用聊天室列表推送消息,通过所有房间在线人数和用户所属聊天室关系过滤,找出对应聊天室需要推送未读消息的在线用户。最近领导让做一个多人聊天功能,小编抠脑壳硬是半天没写出来个所以然,于是网上找了个多人聊天室Demo改出来一款基于SpringBoot实现的实用性较强的聊天室功能(也适用于单人聊天)。:总共用两个连接,一个用于聊天室消息监听,一个用于监听聊天室列表未读消息。调用顺序:先连接聊天室列表,再连接聊天室。roomId -> 聊天室id。

weixin_56772904的博客 1337

RC&B V1.0聊天室:PHP+MYSQL二次开发增强版

本文还有配套的精品资源,点击获取 简介:“RC&B V1.0聊天室”是由居辰工作室在原有版本基础上,使用PHP和MYSQL技术进行改进和二次开发的聊天应用。它修复了之前版本存在的问题,并且引入了多项管理功能,旨在提升用户体验和系统的稳定性。PHP处理用户交互和后端逻辑,而MYSQL存储用户信息和聊天记录。二次开发中,开发团队增强了性能和安全性,引入了用户管理、数据备...

weixin_42608318的博客 890

Anthropic API接入指南:连接失败、403鉴权与Claude Code集成排查

在AI应用开发中,API接入与鉴权机制是开发者绕不开的基础工程问题。无论是调用云端模型服务还是自建网关,网络连通性、HTTP状态码语义、模型路由映射都会直接影响服务可用性。本文从通用的网络诊断与鉴权原理出发,梳理了从TCP连接失败到403权限拒绝的典型链路,并深入解析网关模型路由匹配机制。在此基础上,进一步说明如何通过环境变量完成Claude Code与VS Code的本地集成,以及一套基于Python的可复现接入方案。内容涵盖Anthropic API高频报错定位、curl冒烟测试、SDK工程化实践,适合

weixin_28743405的博客 468

c#的Socket实现多人聊天室(附源码)

在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据 套接字之间的连接过程可以分为三个步骤 : (1)服务器监听。 (2)客户端请求。 (3)连接确认 。 c#的Socket通讯实现聊天室,支持消息与图片发送 c#实现的主要步骤: 1:第一步创建一个开始监听的Socket 2:第二步创建Ip地址和端口号对象 3:第三步让监听的Socket绑定Ip地址跟端口号 4:第四步

qq_43254236的博客 7746

DSP28377内存不够用?手把手教你合并RAM/FLASH块,解决CCS20链接器报错

本文详细介绍了在CCS20开发环境中解决DSP28377内存不足问题的方法,通过合并RAM/FLASH块优化CMD文件配置,有效解决链接器报错。文章包含内存布局对比、改写步骤、FreeRTOS集成注意事项及调试技巧,帮助开发者提升内存利用率与项目性能。

weixin_42524762的博客 143

Linux-Socket实现模拟群聊(多人聊天室)

在linux系统上使用c语言开发简单多人聊天服务器

tua__的博客 3586

对工时评估的思考:当AI颠覆了coding效率

在AI时代,“人日”作为软件工时度量衡已然失效。开发的价值核心正从“编码实现”转向“深度思考”与“精准沟通”——即如何将人类的模糊需求精确地传达给AI。当最有价值的“思想工作”变得最难量化时,传统的项目管理范式随之动摇。我们必须超越对过程的算计,开始探索衡量思想、沟通与业务价值的新标尺。

架构师刘7的博客 1017

LLaMA Factory添加新模型template的实战解析

本文主要介绍了LLaMA Factory添加新模型template的实战解析,希望对使用大语言模型的同学们有所帮助。 文章目录 1. 前言 2. 实战解析 2.1 检查代码 2.2 修改代码

herosunly的博客 19万+

Python实现网络图形化界面多人聊天室

网络图形化界面多人聊天室 在Python实现网络多人聊天室基础上,添加图形化界面,实现网络图形化界面多人聊天室。 代码结构: chatroom├── client.py├── server.py└── settings.py 思路: server.py   首先,在主进程(__main__)中启动两个进程,一个处理与客户端的连接和消息接收以及和图形化界面的信息传输,在终端中打...

weixin_30586257的博客 1259

【YOLO11改进 - C3k2融合】C3k2融合DWRSeg二次创新C3k2_DWR:扩张式残差分割网络,提高特征提取效率和多尺度信息获取能力,助力小目标检测

【YOLO11改进 - C3k2融合】C3k2DWRSeg二次创新C3k2_DWR:扩张式残差分割网络,提高特征提取效率和多尺度信息获取能力,助力小目标检测

专注于图像领域,主要研究内容包括计算机视觉和深度学习,特别是在图像分类、目标检测和图像生成等方面有深入的研究和实践经验。 1056

计算机网络 -- 多人聊天室

这是基于UDP协议实现的一个网络程序,主要功能是这个程序由一台服务器和n个客户端组成,服务器扮演了一个接受信息和分发信息的角色,将信息发送给所有已知的用户主机。

sushhsishdgsusk的博客 2759

2万字大厂面经:小米嵌入式面试题及参考答案(持续更新)

BIOS (Basic Input/Output System) 是一种存储在ROM中的固件程序,它为计算机提供了最基础的硬件初始化和服务。BIOS主要存在于传统的x86架构计算机中。嵌入式系统的启动流程取决于具体的硬件平台和所采用的操作系统。复位阶段:当系统通电或复位时,CPU进入复位状态。硬件初始化:CPU和必要的硬件设备(如内存控制器)被初始化。加载引导加载程序:引导加载程序(如U-Boot)被加载到内存中。执行引导加载程序:引导加载程序开始执行,初始化更多的硬件设备,并检测存储介质。

大模型大数据攻城狮的专栏 2529

3.1 快速启动Flink集群

在本实战中,我们将快速启动Apache Flink 1.13.0集群,并在Hadoop集群环境中提交作业。首先,我们会在本地下载Flink安装包并上传至云主机,解压并配置环境变量。接着,我们将启动Flink集群,通过访问Web UI进行监控管理。然后,我们会配置集群,将Flink分发到从节点,并在集群上提交作业。最后,我们将通过命令行提交作业,并在Web UI上查看任务节点的输出结果。这个过程涵盖了Flink集群的部署、配置和作业提交,为大数据处理提供了一个完整的实战经验。

howard2005的专栏 727

使用 Socket.IO 实现实时通信:构建一个多人聊天室

在本教程中,我们构建了一个基于 Socket.IO 的多人聊天室应用,学会了如何使用 Socket.IO 实现实时双向通信。创建聊天室:通过和socket.on实现客户端与服务器之间的消息传递。广播与私聊:实现了消息广播到所有客户端以及通过指定用户 ID 向特定用户发送私人消息。用户加入与离开提示:监听客户端的连接和断开事件,并通过广播的方式通知所有客户端。

一名热衷于技术的全栈开发者,专注于前端与后端的全面技术探索。在这里,我将分享我在技术领域的学习与成长,助力更多开发者的进步。 1788

KEPServerEX 6 之 EX 6.11.718.0 发布说明

KEPServerEX 6.11.718.0 发布说明

-雲飛兄-博客 2882

用Node.js编写多人实时在线聊天室

在Mike和Marc合著的《Node.js实战》书中提供了一个聊天室demo,但书中所用Node版本太旧,代码已不能运行。所以本示例代码基于Node.js当前最新版本6.11.0修改而成,代码地址:https://github.com/foupwang/chatrooms 代码运行方法:1、下载代码到本地,进入根目录,先安装第三方模块,命令:npm install 2、启动服务器,命令...

donghou2257的博客 553

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

基于vue+springboot+websocket的多人聊天室

本项目是基于vue+springboot+websocket的多人聊天室,包括登陆注册,群聊通信,文件图片上传,创建群聊,加入退出群聊,个人信息展示和修改。

m0_72742226的博客 931

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

多人聊天室简易实现(完善中)

多人聊天室的简易实现,基本功能已经实现,其他功能待完善中......

echo47377的博客 199
上一篇: 多人聊天室简易实现(完善中)
下一篇: 时间与日期(Boost)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值