网络编程7.12

实现2个客户端之间互相聊天

服务器要求:使用 select 模型实现接受多个客户端连接,以及转发消息

客户端要求:使用 poll 模型解决 技能够 read 读取服务器发来的消息,又能够scanf读取键盘输入的信息 客户端服务器不允许开启额外线程和进程

服务器代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

// 最大客户端数量
#define MAX_CLIENTS 10
// 缓冲区大小
#define BUF_SIZE 1024

// 向客户端数组中插入新客户端
void insert_client(int client_arr[], int *len, int new_fd) {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_arr[i] == -1) { 
            client_arr[i] = new_fd; 
            (*len)++;          
            break;            
        }
    }
}

// 从客户端数组中移除客户端
void remove_client(int client_arr[], int *len, int target_fd) {
    for (int i = 0; i < MAX_CLIENTS; i++) {
        if (client_arr[i] == target_fd) { 
            client_arr[i] = -1; 
            for (int j = i; j < MAX_CLIENTS - 1; j++) {
                client_arr[j] = client_arr[j + 1]; 
            }
            (*len)--; 
            break; 
        }
    }
}

int main(int argc, const char *argv[]) {
    // 检查参数
    if (argc < 2) {
        printf("请输入: %s 端口号\n", argv[0]);
        return 1;
    }
    short port = atoi(argv[1]); 

    // 1. 创建服务器套接字
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("socket error");
        return 1;
    }

    // 2. 绑定地址
    struct sockaddr_in server_addr = {0};
    server_addr.sin_family = AF_INET;        
    server_addr.sin_port = htons(port);      
    server_addr.sin_addr.s_addr = inet_addr{"0.0.0.0"}; 

    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("bind error");
        close(server_fd);
        return 1;
    }

    // 3. 监听连接
    if (listen(server_fd, 10) == -1) {
        perror("listen error");
        close(server_fd);
        return 1;
    }
    printf("服务器启动,端口: %d,等待客户端连接...\n", port);

    // select 相关变量
    fd_set read_fds, temp_fds; 
    FD_ZERO(&read_fds);        
    FD_SET(server_fd, &read_fds); 
    FD_SET(STDIN_FILENO, &read_fds); 
    int max_fd = server_fd; 

    // 客户端管理数组
    int client_fds[MAX_CLIENTS];
    for (int i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = -1; 
    }
    int client_count = 0; 

    while (1) {
        // 每次 select 前复制集合(select 会修改集合)
        temp_fds = read_fds;

        // 4. 等待事件发生
        int activity = select(max_fd + 1, &temp_fds, NULL, NULL, NULL);
        if (activity == -1) {
            perror("select error");
            break;
        }

        // 5. 处理服务器套接字(新客户端连接)
        if (FD_ISSET(server_fd, &temp_fds)) {
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);

            int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);
            if (client_fd == -1) {
                perror("accept error");
                continue;
            }

            printf("客户端连接: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

            // 将新客户端加入监听集合和管理数组
            FD_SET(client_fd, &read_fds); 
            if (client_fd > max_fd) {
                max_fd = client_fd; 
            }
            insert_client(client_fds, &client_count, client_fd); 
        }
        // 处理标准输入(服务器手动发消息,可选逻辑)
        else if (FD_ISSET(STDIN_FILENO, &temp_fds)) {
            char buf[BUF_SIZE] = {0};
            if (fgets(buf, BUF_SIZE, stdin) != NULL) {
                // 去掉换行符
                buf[strcspn(buf, "\n")] = '\0';
            }
        }
        // 处理客户端消息
        else {
            for (int i = 0; i < client_count; i++) {
                int curr_fd = client_fds[i];
                if (curr_fd == -1) continue; 

                if (FD_ISSET(curr_fd, &temp_fds)) {
                    char buf[BUF_SIZE] = {0};
                    int len = recv(curr_fd, buf, BUF_SIZE, 0);

                    // 客户端断开
                    if (len <= 0) {
                        printf("客户端断开: %d\n", curr_fd);
                        FD_CLR(curr_fd, &read_fds); 
                        close(curr_fd);             
                        remove_client(client_fds, &client_count, curr_fd); 
                        continue;
                    }

                    // 简单转发:发给其他所有客户端
                    printf("收到客户端消息: %s (来自 fd: %d)\n", buf, curr_fd);
                    for (int j = 0; j < client_count; j++) {
                        int target_fd = client_fds[j];
                        if (target_fd != -1 && target_fd != curr_fd) { 
                            send(target_fd, buf, len, 0);
                        }
                    }
                }
            }
        }
    }

    // 关闭资源
    for (int i = 0; i < client_count; i++) {
        close(client_fds[i]);
    }
    close(server_fd);
    return 0;
}

 客户端代码:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

// 缓冲区大小
#define BUF_SIZE 1024

int main(int argc, const char *argv[]) {
    // 检查参数
    if (argc < 3) {
        printf("请输入: %s 服务器IP 端口号\n", argv[0]);
        return 1;
    }
    const char *server_ip = argv[1];
    short port = atoi(argv[2]); 

    // 1. 创建客户端套接字
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("socket error");
        return 1;
    }

    // 2. 连接服务器
    struct sockaddr_in server_addr = {0};
    server_addr.sin_family = AF_INET; 
    server_addr.sin_port = htons(port); 

    if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
        perror("inet_pton error");
        close(sock_fd);
        return 1;
    }

    if (connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("connect error");
        close(sock_fd);
        return 1;
    }
    printf("已连接服务器: %s:%d\n", server_ip, port);

    // poll 相关设置
    struct pollfd fds[2]; 
    // 监听套接字
    fds[0].fd = sock_fd;        
    fds[0].events = POLLIN;     
    fds[0].revents = 0;         
    // 监听标准输入
    fds[1].fd = STDIN_FILENO;  
    fds[1].events = POLLIN;     
    fds[1].revents = 0;         

    while (1) {
        // 3. 等待事件(套接字或键盘输入)
        int ret = poll(fds, 2, -1); 
        if (ret == -1) {
            perror("poll error");
            break;
        }

        // 4. 处理套接字消息(服务器发来的数据)
        if (fds[0].revents & POLLIN) {
            char buf[BUF_SIZE] = {0};
            int len = recv(sock_fd, buf, BUF_SIZE, 0);

            // 服务器断开
            if (len <= 0) {
                printf("服务器断开连接\n");
                close(sock_fd);
                return 0;
            }

            printf("服务器说: %s\n", buf);
        }

        // 5. 处理标准输入(客户端发消息给服务器)
        if (fds[1].revents & POLLIN) {
            char buf[BUF_SIZE] = {0};
            if (fgets(buf, BUF_SIZE, stdin) != NULL) {
                // 去掉换行符
                buf[strcspn(buf, "\n")] = '\0';
                // 发送给服务器
                send(sock_fd, buf, strlen(buf), 0);
            }
        }
    }

    // 关闭套接字
    close(sock_fd);
    return 0;
}

最终效果:

网络编程7.12_第1张图片

你可能感兴趣的:(网络编程7.12)