服务器多客户端连接核心要点(1)

news2025/5/10 11:33:40

刷题

服务器多客户端连接核心要点


多进程服务器

实现原理

  • fork子进程:每次accept新客户端后,调用fork创建子进程。
  • 独立处理:子进程负责与客户端通信(如read/write),父进程继续监听新连接。

特点

  • 隔离性高:进程间资源独立,避免数据竞争。
  • 资源消耗大:频繁创建进程导致内存和CPU开销高。

多线程服务器

实现原理

  • 创建新线程accept后生成线程处理客户端请求。
  • 线程函数:在线程内执行read/write操作。

特点

  • 轻量级:线程共享进程资源,开销低于进程。
  • 需同步机制:需使用互斥锁(mutex)或信号量避免数据竞争。

多路I/O模型

核心问题

  • 阻塞冲突:传统acceptread相互阻塞,无法同时处理多客户端。
  • 解决方案:通过非阻塞模型(select/poll)统一监听所有描述符。

Select模型

流程
  1. 初始化监视列表
fd_set read_fds;
FD_ZERO(&read_fds);          // 清空列表
FD_SET(server_fd, &read_fds); // 添加服务端套接字
  1. 循环监听
    select(max_fd + 1, &read_fds, NULL, NULL, NULL); // 阻塞直至描述符激活
  2. 处理激活描述符
    • 遍历描述符,通过FD_ISSET判断是否激活。
    • 服务端套接字激活:调用accept接受新客户端。
    • 客户端套接字激活:调用read读取数据。
缺点
  • 效率低:每次调用需遍历所有描述符。
  • 监视列表上限:默认限制1024个描述符。

Poll模型

流程
  1. 初始化监视列表
struct pollfd fds[MAX_CLIENTS];
fds[0].fd = server_fd;
fds[0].events = POLLIN; // 监视可读事件
  1. 循环监听
    poll(fds, nfds, -1); // 永久阻塞直至事件触发
  2. 处理激活描述符
    • 遍历fds数组,检查revents是否为POLLIN
    • 服务端激活:accept新客户端并加入列表。
    • 客户端激活:read数据并响应。
优点
  • 无描述符数量限制:支持动态扩展。
  • 事件类型灵活:可同时监视读写和异常事件。

模型对比

特性SelectPoll
描述符上限1024(系统默认)无限制
效率O(n)遍历O(n)遍历
事件类型仅可读/可写/异常支持自定义事件(如POLLIN)
跨平台支持广泛支持Linux专用

实际应用场景

  • 作业要求
    • 服务器端:使用poll模型处理多客户端。
    • 客户端1select模型实现非阻塞通信。
    • 客户端2:多线程处理输入/输出。
  • 典型场景
    • 实时聊天系统:服务器通过poll管理多个客户端会话。
    • 高并发服务器select/poll适用于低并发场景,epoll适合高并发。
  • pollk.c
    #include "head.h"
    
    #define MAX_CLIENTS 10 // 最多10个客户端
    
    // 将一个拥有{描述符,监视方式,激活形式}的结构体变量fd,存入监视列表arr中,并且监视列表的长度len自增
    void insert_fd(struct pollfd arr[], struct pollfd newfd, int* len) {
        arr[*len] = newfd; // 将新的pollfd结构体存入数组指定位置
        (*len)++; // 监视列表长度加1
    }
    
    // 将要移除的描述符fd,从监视列表arr中删除,并且监视列表长度len自减1
    void remove_fd(struct pollfd* arr, int fd, int* len) {
        for (int i = 0; i < *len; i++) {
            if (arr[i].fd == fd) { // 查找要移除的描述符在数组中的位置
                for (int j = i; j < *len - 1; j++) {
                    arr[j] = arr[j + 1]; // 后面的元素向前移动,覆盖要移除的元素
                }
                break;
            }
        }
        (*len)--; // 监视列表长度减1
    }
    
    int main(int argc, const char *argv[]) {
        if (argc < 2) { // 检查命令行参数,确保输入了端口号
            printf("请输入端口号\n");
            return 1;
        }
    
        short port = atoi(argv[1]); // 将命令行输入的端口号字符串转换为short类型
    
        // 创建TCP类型的服务器套接字
        int server = socket(AF_INET, SOCK_STREAM, 0);
    
        struct sockaddr_in addr = {0};
        addr.sin_family = AF_INET; // 设置地址族为IPv4
        addr.sin_port = htons(port); // 将端口号转换为网络字节序
        addr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 监听所有可用的本地IP地址
    
        // 将套接字绑定到指定的地址和端口
        if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
            perror("bind");                                                                                                          
            return 1;
        }
    
        listen(server, MAX_CLIENTS); // 开始监听,设置最大等待连接数
    
        struct pollfd list[MAX_CLIENTS + 1] = {0}; // 定义pollfd结构体数组,用于存储要监视的描述符及其相关信息
        int list_len = 0; // 记录监视列表中描述符的数量
    
        // 准备要监视的服务器描述符(关注读事件)
        struct pollfd poll_server = {.fd = server,.events = POLLIN,.revents = 0};
    
        insert_fd(list, poll_server, &list_len); // 将服务器描述符加入监视列表
    
        printf("服务器启动,监听端口 %d...\n", port);
    
        while (1) {
            // 调用poll函数,阻塞等待监视列表中的描述符有事件发生
            poll(list, list_len, -1);
    
            for (int i = 0; i < list_len; i++) {
                int fd = list[i].fd;
                // 检查描述符的revents是否有读事件(POLLIN)发生
                if (list[i].revents & POLLIN) {
                    if (fd == server) { // 服务器描述符有读事件,说明有新客户端连接
                        struct sockaddr_in client_addr;
                        socklen_t client_addr_len = sizeof(client_addr);
                        int client = accept(server, (struct sockaddr *)&client_addr, &client_addr_len);
                        if (client == -1) {
                            perror("accept");
                            continue;
                        }
                        printf("新客户端连接: %d\n", client);
                        // 准备要监视的新客户端描述符(关注读事件)
                        struct pollfd poll_client = {.fd = client,.events = POLLIN,.revents = 0};
                        insert_fd(list, poll_client, &list_len); // 将新客户端描述符加入监视列表
                    } else { // 客户端描述符有读事件,说明客户端发送了数据
                        char buf[1024] = {0};
                        int res = read(fd, buf, sizeof(buf));
                        if (res <= 0) { // 处理客户端断开
                            if (res == 0) {
                                printf("客户端 %d 断开连接\n", fd);
                            } else {
                                perror("read");
                            }
                            close(fd);
                            remove_fd(list, fd, &list_len); // 从监视列表中移除客户端描述符
                        } else { // 转发数据给其他客户端
                            for (int j = 0; j < list_len; j++) {
                                if (list[j].fd != fd && list[j].fd != server) {
                                    write(list[j].fd, buf, res);
                                }
                            }
                        }
                    }
                }
            }
        }
        return 0;
    }
                                                                                                                                     
    

    selectk.c

    #include "head.h"
    
    int main(int argc, const char *argv[]) {
        if (argc < 2) {
            printf("请输入端口号\n");
            return 1;
        }
        int port = atoi(argv[1]);
    
        int client_fd = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in addr = {0};
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = INADDR_ANY;
    
        if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
            perror("connect");
            return 1;
        }
    
        fd_set rset;
        int max_fd = client_fd;
    
        while (1) {
            FD_ZERO(&rset);
            FD_SET(client_fd, &rset);
            FD_SET(0, &rset); // 监控标准输入
    
            int ret = select(max_fd + 1, &rset, NULL, NULL, NULL);
            if (ret == -1) {
                perror("select");
                continue;
            }
    
            if (FD_ISSET(0, &rset)) { // 处理键盘输入
                char buf[1024] = {0};
                fgets(buf, sizeof(buf), stdin);
                buf[strcspn(buf, "\n")] = 0; // 去除换行符                                
                write(client_fd, buf, strlen(buf));
            }
    
            if (FD_ISSET(client_fd, &rset)) { // 处理接收消息
                char buf[1024] = {0};
                int res = read(client_fd, buf, sizeof(buf));
                if (res <= 0) {
                    if (res == 0) {
                        printf("服务器断开连接\n");
                    } else {
                        perror("read");
                    }
                    close(client_fd);
                    return 1;
                }
                printf("收到消息: %s\n", buf);
            }
        }
    
        close(client_fd);
        return 0;
    }
                                                                                          
                                                                                          
                                                                                          
    

    xiank.c

    #include "head.h"
    
    void *send_msg(void *arg) {
        int client_fd = *(int *)arg;
        while (1) {
            char buf[1024] = {0};
            fgets(buf, sizeof(buf), stdin);
            buf[strcspn(buf, "\n")] = 0; // 去除换行符
            write(client_fd, buf, strlen(buf));
        }
    }
    
    void *recv_msg(void *arg) {
        int client_fd = *(int *)arg;
        while (1) {
            char buf[1024] = {0};
            int res = read(client_fd, buf, sizeof(buf));
            if (res <= 0) { // 处理服务器断开
                if (res == 0) {
                    printf("服务器断开连接\n");
                } else {
                    perror("read");
                }
                close(client_fd);
                pthread_exit(NULL);
            }
            printf("收到消息: %s\n", buf);
        }
    }
    
    int main(int argc, const char *argv[]) {
        if (argc < 2) {
            printf("请输入端口号\n");
            return 1;
        }
        int port = atoi(argv[1]);
    
        int client_fd = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in addr = {0};
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = INADDR_ANY;
    
        if (connect(client_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
            perror("connect");
            return 1;
        }
                                                                                                            
        pthread_t send_tid, recv_tid;
        pthread_create(&send_tid, NULL, send_msg, &client_fd); // 启动发送线程
        pthread_create(&recv_tid, NULL, recv_msg, &client_fd); // 启动接收线程
    
        pthread_join(send_tid, NULL);
        pthread_join(recv_tid, NULL);
    
        close(client_fd);
        return 0;
    }
                                                                                                            
                                                                                                            
                                                                                                            
                                                                                                            
                                                                                                            
    

总结:多进程/线程适合简单场景,多路I/O模型(select/poll)通过非阻塞监听提升效率,需根据并发需求选择模型。

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

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

相关文章

Stagehand:AI驱动的下一代浏览器自动化框架

Stagehand 是一个结合了 AI 代理、AI 工具和 Playwright 的浏览器自动化框架。核心理念是&#xff1a;让自动化任务既可控又智能。与传统工具不同&#xff0c;Stagehand 不仅仅依赖 AI 代理的“黑箱操作”&#xff0c;而是通过与 Playwright 的深度结合&#xff0c;赋予开发者对…

爱普生FA-238在车身控制模块中的应用

在汽车智能化、电子化飞速发展的当下&#xff0c;车身控制模块&#xff08;BCM&#xff09;作为车辆的 “智能管家”&#xff0c;肩负着协调和控制众多车身功能的重任&#xff0c;从车门的解锁与锁定、车窗的升降&#xff0c;到车灯的智能点亮与熄灭&#xff0c;再到雨刮器的自…

【A2A】管中窥豹,google源码python-demo介绍

前言 A2A&#xff08;Agent2Agent&#xff09;是 Google 推出的一项新协议&#xff0c;旨在解决多智能体&#xff08;Multi-Agent&#xff09;系统中跨平台、跨组织协作的难题。它为 AI 代理之间的通信、协作和任务分工提供了一个统一的标准&#xff0c;可以类比为网页世界的 H…

004-nlohmann/json 快速认识-C++开源库108杰

了解 nlohmann/json 的特点&#xff1b;理解编程中 “数据战场”划分的概念&#xff1b;迅速上手多种方式构建一个JSON对象&#xff1b; 1 特点与安装 nlohmann/json 是一个在 github 长期霸占 “JSON” 热搜版第1的CJSON处理库。它的最大优点是与 C 标准库的容器数据&#xf…

Matlab实现CNN-BiLSTM时间序列预测未来

Matlab实现CNN-BiLSTM时间序列预测未来 目录 Matlab实现CNN-BiLSTM时间序列预测未来效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CNN-BiLSTM时间序列预测未来&#xff1b; 2.运行环境Matlab2023b及以上&#xff0c;data为数据集&#xff0c;单变量时间序…

C语言| sizeof(array)占多少字节

C语言| 数组名作为函数参数 sizeof(数组名); 可以求出整个数组在内存中所占的字节数。 被调函数Array_Sum()中&#xff0c;数组array使用sizeof会得到多少&#xff1f; 实参数组a占32字节&#xff0c;实参a传给形参array&#xff0c;只占4字节。 原因如下&#xff1a; 数组名做…

【文件系统—散列结构文件】

文章目录 一、实验目的实验内容设计思路 三、实验代码实现四、总结 一、实验目的 理解linux文件系统的内部技术&#xff0c;掌握linux与文件有关的系统调用命令&#xff0c;并在此基础上建立面向随机检索的散列结构文件&#xff1b;## 二、实验内容与设计思想 实验内容 1.设…

World of Warcraft [CLASSIC][80][Deluyia] [Fragment of Val‘anyr]

瓦兰奈尔的碎片 [Fragment of Valanyr] 有时候下个班打个游戏&#xff0c;没想到套路也这么多&#xff0c;唉&#xff0c;何况现实生活&#xff0c;这一个片版本末期才1000G&#xff0c;30个&#xff0c;也就30000G&#xff0c;时光徽章等同月卡15000G&#xff0c;折合一下也就…

数组和指针典型例题合集(一维数组、字符数组、二维数组)

1.一维数组 数组名的理解 数组名是数组首元素&#xff08;第一个元素&#xff09;的地址 但是有两个例外&#xff1a; 1.sizeof &#xff08;数组名&#xff09;—— 数组名表示整个数组&#xff0c;就算的是整个数组的大小&#xff0c;单位是字节。 2.&数组名 —— 数…

地级市-机器人、人工智能等未来产业水平(2009-2023年)-社科数据

地级市-机器人、人工智能等未来产业水平&#xff08;2009-2023年&#xff09;-社科数据https://download.csdn.net/download/paofuluolijiang/90623814 https://download.csdn.net/download/paofuluolijiang/90623814 此数据集统计了2009-2023年全国地级市在机器人、人工智能等…

epub格式转txt格式工具,txt批量转PDF

epub格式转txt格式工具&#xff0c;功能如图&#xff1a; txt格式批量转PDF 参考原文&#xff1a;epub格式转txt格式工具&#xff0c;txt批量转PDF 轻轻一点就关注, 好运连连挡不住&#xff0c;点个关注吧。

电赛经验分享——模块篇

1、前言 打算在这一个专栏中&#xff0c;分享一些本科控制题电赛期间的经验&#xff0c;和大家共同探讨&#xff0c;也希望能帮助刚刚参加电赛的同学&#xff0c;了解一些基本的知识。一些见解和看法可能不同或有错误&#xff0c;欢迎批评指正。 在本文中&#xff0c;主要介绍笔…

JVM之内存管理(一)

部分内容来源&#xff1a;JavaGuide二哥Java 图解JVM内存结构 内存管理快速复习 栈帧&#xff1a;局部变量表&#xff0c;动态链接&#xff08;符号引用转为真实引用&#xff09;&#xff0c;操作数栈&#xff08;存储中间结算结果&#xff09;&#xff0c;方法返回地址 运行时…

鸿蒙编译boost整合linux跨平台应用

openharmony deveco 4.1支持armeabi-v7a deveco 5.0后不支持arm32位系统 boost编译 使用deveco的写cmake集成boost boost使用1.88的最新版本&#xff0c;带cmake工具链 https://github.com/boostorg/boost.git boost的源码都在sub_module中 deveco 4.1的版本sdk最高到9&am…

rabbitMQ消息问题与解决

rabbitMQ 消息顺序性、消息幂等性、消息不丢失、最终一致性、补偿机制、消息队列设计 1.消息顺序性 溯源&#xff1a; 消息队列中的若干消息如果是对同一个数据进行操作&#xff0c;这些操作具有前后的关系&#xff0c;必须要按前后的顺序执行&#xff0c;否则就会造成数据异常…

Java SE(10)——抽象类接口

1.抽象类 1.1 概念 在之前讲Java SE(6)——类和对象&#xff08;一&#xff09;的时候说过&#xff0c;所有的对象都可以通过类来抽象。但是反过来&#xff0c;并不是说所有的类都是用来抽象一个具体的对象。如果一个类本身没有足够的信息来描述一个具体的对象&#xff0c;而…

学习threejs,使用Physijs物理引擎

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️Physijs 物理引擎1.1.1 ☘️…

allure生成测试报告(搭配Pytest、allure-pytest)

文章目录 前言allure简介allure安装软件下载安装配置环境变量安装成功验证 allure运行流程allure装饰器函数基本说明装饰器函数使用allure.attach 命令行运行利用allure-pytest生成中间结果json 查看测试报告总览页面每个tab页的说明类别页面测试套图表页面时间刻度功能页面包 …

龙虎榜——20250509

上证指数今天缩量&#xff0c;整体跌多涨少&#xff0c;走势处于日线短期的高位~ 深证指数今天缩量小级别震荡&#xff0c;大盘股表现更好~ 2025年5月9日龙虎榜行业方向分析 一、核心行业方向 军工航天 • 代表个股&#xff1a;航天南湖、天箭科技、襄阳轴承。 • 驱动逻辑…

操作系统的初步了解

目录 引言&#xff1a;什么是操作系统&#xff1f; 一、设计操作系统的目的 二、操作系统是做什么的&#xff1a; 操作系统主要有四大核心任务&#xff1a; 1. 管理硬件 2. 运行软件 3. 存储数据 4. 提供用户界面 如何理解操作系统的管理呢&#xff1f; 1. 什么是操作…