网络编程7.12
实现2个客户端之间互相聊天
服务器要求:使用 select 模型实现接受多个客户端连接,以及转发消息
客户端要求:使用 poll 模型解决 技能够 read 读取服务器发来的消息,又能够scanf读取键盘输入的信息 客户端服务器不允许开启额外线程和进程
服务器代码:
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>// 最大客户端数量
#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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <poll.h>// 缓冲区大小
#define BUF_SIZE 1024int 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;
}
最终效果: