当前位置: 首页 > news >正文

网络编程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;
}

最终效果:

http://www.dtcms.com/a/279691.html

相关文章:

  • 【elasticsearch9.0】【kibana】Docker安装
  • Java从入门到精通!第五天(面向对象(二))
  • JAR 包冲突排雷指南:原理、现象与 Maven 一站式解决
  • 深度解读virtio:Linux IO虚拟化核心机制
  • 评论设计开发
  • RedisJSON 技术揭秘`JSON.DEBUG MEMORY` 量化 JSON 键的内存占用
  • Python深浅拷贝全解析:从原理到实战的避坑指南
  • 深度解析:htmlspecialchars 与 nl2br 结合使用的前后端协作之道,大学毕业论文——仙盟创梦IDE
  • 工业场合需要千变万化的模拟信号,如何获取?
  • B4016 树的直径
  • 阿尔卡特ASM180TD181TD氦检漏器ALCATEL
  • 使用dify生成测试用例
  • 【第一章编辑器开发基础第二节编辑器布局_3间距控制(4/4)】
  • OpenCV C++ 中的掩码(Mask)操作
  • 微服务初步入门
  • 设计模式之适配器模式:让不兼容的接口协同工作的艺术
  • Unreal5从入门到精通之如何实现UDP Socket通讯
  • 【C++进阶】---- 多态
  • 解锁文档处理新体验:Python库Agentic Document Extraction
  • OneCode3.0 通信架构简介——MCPServer微内核设计哲学与实现
  • Web学习笔记4
  • 算法训练营day16 513.找树左下角的值、112. 路径总和、106.从中序与后序遍历序列构造二叉树
  • 探索 Sort.h:多功能排序算法模板库
  • [element-ui]el-table在可视区域底部固定一个横向滚动条
  • 智源全面开源RoboBrain 2.0与RoboOS 2.0:刷新10项评测基准,多机协作加速群体智能
  • MCP 第三波升级!Function Call 多步调用 + 流式输出详解
  • QWidget 和 QML 的本质和使用上的区别
  • 慢查询日志监控:定位性能瓶颈的第一步
  • 【抖音滑动验证码风控分析】
  • 小架构step系列14:白盒集成测试原理