多客户端-服务器(select,poll)

多客户端-服务器结构总结

一、普通CS架构的局限性

  • 核心问题:单线程中accept(阻塞等待连接)与read(阻塞读取数据)函数互相干扰,无法同时处理多客户端。
  • 本质原因:阻塞型函数需独立执行,若顺序调用会导致流程停滞(如先执行accept会阻塞后续read,反之亦然)。

二、多线程与多进程解决方案

1. 多线程服务器

  • 原理:为每个客户端连接创建独立线程,主线程负责监听连接,子线程处理客户端数据交互。
  • 优势:避免主线程阻塞,可同时维护多个客户端。

2. 多进程服务器

  • 原理:与多线程类似,通过创建新进程维护每个客户端连接,进程间资源独立。
  • 优势:进程隔离性强,单个进程崩溃不影响整体服务。

三、多路IO复用技术

1. 核心目标

  • 单线程中高效处理多个阻塞IO操作(如readaccept),实现描述符间无干扰响应。

2. 工作机制

  • 监视列表:用户手动添加需监控的套接字/文件描述符。
  • 激活列表:系统自动识别并记录“可读/可写”的描述符,用户仅需处理激活的描述符,避免阻塞。

四、select模型详解

1. 核心结构

  • 监视列表类型fd_set(本质为栈空间数组,容量固定且无法动态扩容)。
  • 核心操作函数
    • FD_SET(fd, &set):添加描述符到监视列表。
    • FD_CLR(fd, &set):从列表移除描述符。
    • FD_ISSET(fd, &set):判断描述符是否激活。
    • FD_ZERO(&set):初始化列表。

2. 监视函数select

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
  • 参数
    • nfds:最大描述符值+1。
    • readfds/writefds/exceptfds:分别监视“可读/可写/异常”事件的列表。
    • timeout:阻塞超时时间(NULL表示永久阻塞)。
  • 特点:监视列表与激活列表共用同一块内存,激活后会覆盖原列表,需手动备份。

3. 优缺点

  • 优点:逻辑简单直观,易于实现。
  • 缺点
    • 监视列表容量固定,无法动态扩容。
    • 需额外维护描述符数组,遍历效率低(双重循环)。
    • 每次调用需复制列表,浪费系统资源。

五、poll模型详解

1. 核心结构

  • 监视列表类型struct pollfd数组(可动态扩容,栈/堆空间均可)。
    struct pollfd {
    int fd; // 目标描述符
    short events; // 监控事件(如POLLIN=可读,POLLOUT=可写)
    short revents; // 激活事件(0=未激活,POLLIN/POLLOUT=对应事件激活)
    };

2. 监视函数poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
  • 参数
    • fdsstruct pollfd数组(同时作为监视列表和激活列表)。
    • nfds:数组长度(用户自行管理)。
    • timeout:超时时间(毫秒,-1=永久阻塞,0=非阻塞)。
  • 特点:激活事件通过revents标记,不会覆盖原列表,无需额外备份。

3. 优缺点

  • 优点
    • 监视列表可动态扩容,灵活性高。
    • 无需反复复制列表,资源开销低。
  • 缺点:仍需遍历整个列表判断激活状态,效率较低(未解决select的遍历问题)。

六、作业要求

1. 服务器端

  • 使用select模型实现:
    • 接受多个客户端连接。
    • 转发客户端间的聊天消息。

2. 客户端

  • 使用poll模型实现:
    • 同时处理“读取服务器消息”(read)和“键盘输入”(scanf)。
    • 限制:不允许开启额外线程或进程。
  • hesd.h
#ifndef HEAD_H
#define HEAD_H#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#include <poll.h>#define PORT 8888        // 服务器端口
#define MAX_CLIENTS 10   // 最大客户端数
#define BUF_SIZE 1024    // 消息缓冲区大小// 客户端数组操作函数声明
void insert_client(int arr[], int newclient, int* len);
void remove_client(int arr[], int client, int* len);#endif

server.c

#include "head.h"// 添加新客户端到数组
void insert_client(int arr[], int newclient, int* len) {arr[*len] = newclient;(*len)++;
}// 从数组中移除客户端
void remove_client(int arr[], int client, int* len) {for (int i = 0; i < *len; i++) {if (arr[i] == client) {for (int j = i; j < *len - 1; j++) {arr[j] = 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]);int client_arr[MAX_CLIENTS] = {0}; // 存储客户端套接字int client_count = 0;              // 当前客户端数量// 创建服务器套接字int server = socket(AF_INET, SOCK_STREAM, 0);if (server == -1) { perror("socket"); return 1; }// 绑定地址struct sockaddr_in addr = {0};addr.sin_family = AF_INET;addr.sin_port = htons(port);addr.sin_addr.s_addr = INADDR_ANY;if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind"); close(server); return 1;}// 开始监听if (listen(server, 10) == -1) { perror("listen"); close(server); return 1; }printf("服务器启动,监听端口 %d...\n", port);// 初始化select监视图(仅监控服务器套接字)fd_set list;FD_ZERO(&list);FD_SET(server, &list);int max_fd = server + 1; // select需要的最大fd+1while (1) {fd_set temp = list; // 复制监视图(select会修改temp)int ret = select(max_fd, &temp, NULL, NULL, NULL);if (ret == -1) { perror("select"); continue; }// 处理新客户端连接if (FD_ISSET(server, &temp)) {struct sockaddr_in client_addr;socklen_t client_len = sizeof(client_addr);int client = accept(server, (struct sockaddr*)&client_addr, &client_len);if (client == -1) { perror("accept"); continue; }FD_SET(client, &list);      // 加入监视图insert_client(client_arr, client, &client_count); // 加入数组printf("新客户端连接:%s:%d(套接字:%d)\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client);if (client + 1 > max_fd) max_fd = client + 1; // 更新最大fd}// 处理客户端消息(转发给其他客户端)for (int i = 0; i < client_count; i++) {int client = client_arr[i];if (FD_ISSET(client, &temp)) {char buf[BUF_SIZE] = {0};int res = read(client, buf, sizeof(buf));if (res == -1) { // 读出错perror("read");FD_CLR(client, &list);remove_client(client_arr, client, &client_count);close(client);continue;} else if (res == 0) { // 客户端断开printf("客户端 %d 断开连接\n", client);FD_CLR(client, &list);remove_client(client_arr, client, &client_count);close(client);} else { // 转发消息printf("转发消息:%s(来自客户端 %d)\n", buf, client);for (int j = 0; j < client_count; j++) {int target = client_arr[j];if (target != client) { // 排除自己write(target, buf, res);}}}}}}close(server);return 0;
}

client1/2

#include "head.h"int main() {// 连接服务器int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) { perror("socket"); return 1; }struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(PORT);server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("connect"); close(sockfd); return 1;}printf("连接服务器成功!\n");// poll 监控两个fd:服务器套接字 + 标准输入(0)struct pollfd fds[2];fds[0].fd = sockfd;   fds[0].events = POLLIN; // 监控服务器消息fds[1].fd = 0;        fds[1].events = POLLIN; // 监控键盘输入while (1) {int ret = poll(fds, 2, -1); // 阻塞等待事件if (ret == -1) { perror("poll"); continue; }// 处理服务器消息(fds[0])if (fds[0].revents & POLLIN) {char buf[BUF_SIZE] = {0};int res = read(sockfd, buf, sizeof(buf));if (res == -1) { perror("read"); break; }if (res == 0) { printf("服务器断开!\n"); break; }printf("收到消息:%s\n", buf);}// 处理键盘输入(fds[1])if (fds[1].revents & POLLIN) {char buf[BUF_SIZE] = {0};fgets(buf, sizeof(buf), stdin); // 读取一行输入buf[strcspn(buf, "\n")] = '\0'; // 去掉换行符if (strlen(buf) == 0) continue; // 空输入跳过write(sockfd, buf, strlen(buf)); // 发消息给服务器}}close(sockfd);return 0;
}

七、关键对比

特性select模型poll模型
监视列表容量固定(栈数组)动态(可堆数组)
列表操作需备份原列表(激活覆盖原列表)无需备份(revents标记激活)
效率双重循环,低单循环,中等(仍需遍历)
灵活性

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89006.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89006.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何使用postman做接口测试?

&#x1f345; 点击文末小卡片 &#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 常用的接口测试工具主要有以下几种&#xff1a;Postman: 简单方便的接口调试工具&#xff0c;便于分享和协作。具有接口调试&#xff0c;接口集管理&#xff0c…

新型网络架构设计助力智慧医疗降本增效

随着智慧医疗的快速发展,越来越多的医院开始布局“互联网+医疗”服务,通过数字化手段提升医疗服务效率。然而,如何构建一个既稳定可靠又具备灵活扩展能力的医疗网络,成为医院数字化转型中的关键问题。本文以某智慧医疗项目为例,探讨传统网络与SD-WAN结合的最佳实践。 背景…

一文读懂现代卷积神经网络—使用块的网络(VGG)

目录 什么是使用块的网络&#xff08;VGG&#xff09;&#xff1f; 一、VGG 的核心思想&#xff1a;用块&#xff08;Block&#xff09;构建网络 二、VGG 的网络结构 三、VGG 的优势 1. 结构简洁统一 2. 强大的特征表达能力 3. 小卷积核的计算效率 4. 良好的迁移学习性…

Linux的相关学习

linux 1.文件权限怎么修改 chmod [权限模式] [文件或目录]1、**数字模式&#xff08;八进制&#xff09;**&#xff1a; chmod 755 myfile.sh # 所有者&#xff1a;rwx (7)&#xff0c;组&#xff1a;r-x (5)&#xff0c;其他用户&#xff1a;r-x (5) 7 rwx&#xff08;读写…

Kotlin集合接口

Kotlin 集合概述 Kotlin 集合提供了对数据进行各种操作的便捷方式。它们实现了接口&#xff0c;因此可以操作不同类型的数据。例如&#xff0c;你可以编写一个函数&#xff0c;同时打印 Set 和 List 的所有元素。我们来看看这是如何实现的。Iterable 接口 我们已经知道&#xf…

Git 常用操作与注意事项全攻略

1. 基本配置 git config --global user.name "你的名字" git config --global user.email "你的邮箱" git config --list # 查看当前配置建议全局配置用户名和邮箱&#xff0c;否则提交记录可能不规范2.仓库操作 初始化本地仓库 git init只在新建项目时使…

STM32-第五节-TIM定时器-1(定时器中断)

一、定时器原理&#xff1a;1.介绍&#xff1a;对指定输入时钟进行计数&#xff0c;并在计数值达到设定值时触发中断。分类&#xff1a;基本定时器&#xff0c;通用定时器&#xff0c;高级定时器频率&#xff1a;72MHZ2.框图&#xff1a; &#xff08;1&#xff09;基本定时器&…

【图像处理基石】什么是色盲仿真技术?

色盲仿真概述 色盲仿真是一种将正常色彩图像转换为色盲患者感知效果的技术。人类常见的色盲类型包括&#xff1a; 红色盲&#xff08;Protanopia&#xff09;&#xff1a;无法感知红色绿色盲&#xff08;Deuteranopia&#xff09;&#xff1a;无法感知绿色蓝黄色盲&#xff08;…

九、官方人格提示词汇总(中-3)

“参谋代写计划”功能输出欣赏&#xff0c;规则&#xff1a; 本部分统一使用 Gemini 2.5 Pro API。该 API 下的输出质量基本达到我的要求&#xff0c;已具备实用价值。严格等级均为“权衡有度&#xff08;L3&#xff09;”&#xff0c;创造力等级均为“趋势捕手&#xff08;L3…

华为MateBook D 16 SE版 2024款 12代酷睿版i5集显(MCLF-XX,MCLF-16)原厂OEM预装Win11系统

适用型号&#xff1a;MCLF-XX,MCLF-16链接&#xff1a;https://pan.baidu.com/s/1OkvUqZMdCSF98YtQfWAYXw?pwdq2gh 提取码&#xff1a;q2gh 华为开箱状态出厂Windows11系统自带所有驱动、出厂主题壁纸、系统属性联机支持标志、系统属性专属LOGO标志、Office办公软件、华为电脑…

Python自动化:每日销售数据可视化

这是手动执行sql分组查出的Linda奶茶店每日的销售数据,那么能否图形化展示方便对比近一个月每日的销售趋势呢。如果是做在网站里,前端可以集成echart或highchart生成柱状图或线状图。如果需要每天定时推送这些数据到邮箱或其他消息通知渠道,第一步肯定是需要先生成图片到服务…

scrapy项目开发流程

1.创建项目&#xff1a;scrapy startproject mySpider2.生成一个爬虫&#xff1a;scrapy genspider itcast itcast.cn3.提取数据&#xff1a;根据网站结构在spider中实现数据采集相关内容4.保存数据使用pipeline进行数据后续处理和保存1.创建项目items.py-->自己预计需要爬取…

堆排序以及其插入删除

堆排序首先介绍一下堆排序属于选择排序的一种类型。其次就是他有点依赖于顺序存储树判断其孩子以及父节点的概念&#xff0c;接下来复习一下。堆分为大根堆和小根堆① 若满⾜&#xff1a;L(i)≥L(2i)且L(i)≥L(2i1) &#xff08;1 ≤ i ≤n/2 &#xff09;—— ⼤根堆&#xff…

Spring Boot项目结构解析:构建高效、清晰的代码框架

在当今的软件开发领域&#xff0c;Spring Boot因其简洁性和强大的功能而备受青睐。它不仅简化了Spring框架的配置&#xff0c;还提供了一套高效的项目开发模式。本文将深入探讨Spring Boot项目结构中的关键组件&#xff0c;包括PO、Query、VO、Config等&#xff0c;旨在帮助开发…

多客户端 - 服务器结构-实操

实现2个客户端之间互相聊天 要求&#xff1a; 1、服务器使用 select 模型实现接受多个客户端连接&#xff0c;以及转发消息 2、客户端要求&#xff1a;使用 poll 模型解决 技能够 read 读取服务器发来的消息&#xff0c;又能够scanf读取键盘输入的信息 3、客户端服务器不允许开…

iOS高级开发工程师面试——Objective-C 语言特性

iOS高级开发工程师面试——Objective-C 语言特性 一、多态二、继承三、代理(Delegate)1. 代理为什么用 weak 修饰呢?block和代理的区别?四、通知(NSNotificationCenter)五、KVC (Key-value Coding)六、属性七、`@property` [ˈprɒpəti]的本质是什么?ivar 、 setter …

MMpretrain 中的 LinearClsHead 结构与优化

LinearClsHead 结构与优化 一、LinearClsHead 核心结构 在 MMPretrain 中&#xff0c;LinearClsHead 是一个简洁高效的分类头&#xff0c;其核心结构如下&#xff1a; class LinearClsHead(BaseModule):def __init__(self,num_classes, # 类别数量in_channels, # 输入…

Spring 学习笔记

1.Spring AOP 怎么实现的AOP 即面向切面编程&#xff0c;是通过代理实现的&#xff0c;主要分为静态代理和动态代理&#xff0c;静态代理就是在程序运行前就已经指定并声明了代理类和增强逻辑&#xff0c;运行时就已经被编译为字节码文件了&#xff0c;而动态代理则是在运行过程…

【CVPR2024】计算机视觉|InceptionNeXt:速度与精度齐飞的CNN架构

论文地址&#xff1a;http://arxiv.org/pdf/2303.16900v3 代码地址&#xff1a;https://github.com/sail-sg/inceptionnext 关注UP CV缝合怪&#xff0c;分享最计算机视觉新即插即用模块&#xff0c;并提供配套的论文资料与代码。 https://space.bilibili.com/473764881 摘要…

7.15 窗口函数 | 二分 | 位运算 | 字符串dp

lc3316. 字符串dpdp多开一行一列后&#xff0c;注意原字符串下标映射dp[n][m] &#xff08; n 是source长度&#xff0c; m 是pattern长度&#xff09;两重循环填表for i 1-nfor j 0-m三种状态转移1.不选 dp i jdp i-1 j2.不选if tag, dp[i][j]3.if(s ip j) 选&#xff0c;dp i…