C语言TCP服务器模型 : select + 多线程与双循环单线程阻塞服务器的比较

news2024/11/29 2:56:55

观察到的实验现象:

启动三个客户端:

使用双循环阻塞服务器:只能accept后等待收发,同时只能与一个客户端建立连接,必须等已连接的客户端多次收发 明确断开后才能与下个客户端连接

使用IO多路复用select:可以同时接收所有的连接请求,并且连接状态一直是存活的,直到客户端关闭连接

select + 多线程服务器创作灵感:

本来是想 接收,发送 全用select

但是如果每个连接都要求处理大量数据,则响应时间不确定

最重要的,select判断依据是内核缓存是否有足够空间可写,而不是数据是否准备好

所以为了数据准备好再发送 

我使用了 接收多路复用+分线程处理数据+处理完毕在线程内直接发送 的模型

什么样的场景收发都适合用select?

IO密集型转发服务器

用于对比的双循环阻塞服务器工作原理:

进入外循环, accept后 再进入内循环 收 发 ,当客户端结束连接时 内层循环结束(使用break)

代码走完 重新进入外层循环 accept阻塞等待一个新连接

注意事项: ip地址修改为符合 你网络规范的ip 运行环境:unix-like系统 gnu_c库

select + 多线程服务器,欢迎指正:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>

#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012
// 此结构体用于线程参数
struct t_args
{
    int fd;
    char data[1024];
};
// 用于accept返回的fd的容器
int client_sockfds[1024] = {0};
// 计数器可以理解为指针,每次用完向后挪1位
int count = 0;
// 线程执行函数
void *start_routine(void *p)
{
    // 解析参数
    struct t_args ta = *((struct t_args *)p);
    // fd后面要用
    int fd = ta.fd;
    // 数据打印出来表示已经获取,可以进行后续处理
    printf("%s\n", ta.data);
    // 模拟数据处理
    sleep((rand() % 3) + 1);
    // 这是处理完的结果
    char res_data[128] = "yes yes done done done";
    ssize_t send_bytes;
    // 声明写监控集
    fd_set writefds;
    // 清空重置
    FD_ZERO(&writefds);
    // 将这个fd加入写监控
    FD_SET(fd, &writefds);
    // 如果select返回,说明此fd写就绪
    int r = select(fd + 1, NULL, &writefds, NULL, NULL);
    if (r == -1)
    {
        perror("select");
    }
    if (r > 0)
    {
        // 如果写就绪
        if (FD_ISSET(fd, &writefds))
        {
            // 就把处理好的数据发送回去
            send_bytes = send(fd, res_data, strlen(res_data), 0);
            if (send_bytes == -1)
            {
                perror("send");
            }
            if (send_bytes > 0)
            {
                printf("%s\n", res_data);
            }
        }
    }
    free(p);
    pthread_exit(NULL);
}
void handler(void *p)
{
    pthread_t tid;
    // 创建线程,传参fd
    if (pthread_create(&tid, NULL, start_routine, p))
    {
        perror("pthread_create");
    }
    // 分离
    if (pthread_detach(tid))
    {
        perror("pthread_detach");
    }
}

int main()
{
    int server_sockfd, client_sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);
    socklen_t server_sockaddr_len = sizeof(server_sockaddr);
    ssize_t recv_bytes;
    char recv_buf[1024] = {0};
    fd_set readfds;
    // 随机数种子
    srand(time(NULL));
    // 创建socket
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1)
    {
        perror("socket");
    }
    // 端口复用
    int optval = 1;
    if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
    {
        perror("setsockopt");
    }
    // 绑定地址端口
    inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
    server_sockaddr.sin_port = htons(SERVER_PORT);
    server_sockaddr.sin_family = AF_INET;
    if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1)
    {
        perror("bind");
    }
    // 监听
    if (listen(server_sockfd, 16) == -1)
    {
        perror("listen");
    }
    printf("server start...\n");
    // 服务器主循环
    while (1)
    {
        // 清空重置读集
        FD_ZERO(&readfds);
        // 将server_sockfd加入读集
        FD_SET(server_sockfd, &readfds);
        // 假设最大的fd是server_sockfd
        int fd_max = server_sockfd;
        int i;
        // count总是指向当前已填充fd的下一个位置
        for (i = 0; i < count; i++)
        {
            // client_sockfds[i]数组储存accept返回的fd ,> 0表示存在fd
            if (client_sockfds[i] > 0)
            {
                // 存在fd就加入读监控
                FD_SET(client_sockfds[i], &readfds);
                // 更新最大fd的值
                fd_max = fd_max > client_sockfds[i] ? fd_max : client_sockfds[i];
            }
        }
        // 此处select作用:从读集中选择读就绪
        int r = select(fd_max + 1, &readfds, NULL, NULL, NULL);
        if (r > 0)
        {
            // 如果server_sockfd是读就绪的
            if (FD_ISSET(server_sockfd, &readfds))
            {
                // 说明已经有连接在等待,则accept不会阻塞
                client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
                if (client_sockfd == -1)
                {
                    perror("accept");
                }
                // count++先读取count的值 后++,把返回的client_sockfd存到数组
                client_sockfds[count++] = client_sockfd;
                // 当连接数达到1024时,变得无法处理且有重大安全漏洞
                if (count == 1024)
                {
                    kill(getpid(), SIGKILL);
                }
            }
            // 此循环用于检查client_sockfds数组已填充部分
            for (i = 0; i < count; i++)
            {
                // 检查fd是否读就绪
                if (FD_ISSET(client_sockfds[i], &readfds))
                {
                    // 接收消息
                    recv_bytes = recv(client_sockfds[i], recv_buf, sizeof(recv_buf), 0);
                    if (recv_bytes < 0)
                    {
                        perror("recv");
                    }
                    else if (recv_bytes == 0)
                    {
                        printf("close by peer\n");
                        // 对面关我也关
                        close(client_sockfds[i]);
                        // 将数组上的fd清空
                        client_sockfds[i] = 0;
                    }
                    else
                    {
                        // 向线程传参
                        struct t_args ta;
                        ta.fd = client_sockfds[i];
                        strncpy(ta.data, recv_buf, strlen(recv_buf));
                        // 为每个线程参数动态分配内存空间
                        struct t_args *p = (struct t_args *)malloc(sizeof(ta));
                        if (p == NULL)
                        {
                            return -1;
                        }
                        // 赋值
                        *p = ta;
                        // 传入处理函数
                        handler((void *)p);
                    }
                }
            }
        }
        else if (r == -1)
        {
            perror("select");
        }
    }
    close(server_sockfd);
    return 0;
}

双循环阻塞服务器:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012

int main()
{
    int server_sockfd, client_sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);
    ssize_t send_bytes, recv_bytes;
    char send_buf[1024] = "How can I help you today ?";
    char recv_buf[1024] = {0};

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1)
    {
        perror("socket");
    }

    int optval = 1;
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    server_sockaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
    server_sockaddr.sin_port = htons(SERVER_PORT);
    if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
    {
        perror("bind");
    }

    if (listen(server_sockfd, 16) == -1)
    {
        perror("listen");
    }

    printf("server start...\n");

    while (1)
    {
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
        if (client_sockfd == -1)
        {
            perror("accept");
        }
        while (1)
        {

            recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);
            if (recv_bytes == -1)
            {
                perror("recv");
            }
            else if (recv_bytes == 0)
            {
                printf("closed by peer\n");
                break;
            }
            else
            {
                printf("%s\n", recv_buf);
            }
            send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);
            if (send_bytes == -1)
            {
                perror("send");
            }
        }
    }
    close(server_sockfd);
    return 0;
}

赠送客户端:
 

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012

int main()
{
    int client_sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);
    ssize_t send_bytes, recv_bytes;
    char send_buf[1024] = {0};
    char recv_buf[1024] = {0};
    srand(time(NULL));
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sockfd == -1)
    {
        perror("socket");
    }
    inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
    server_sockaddr.sin_port = htons(SERVER_PORT);
    server_sockaddr.sin_family = AF_INET;
    if (connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
    {
        perror("connect");
    }
    getsockname(client_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
    snprintf(send_buf, sizeof(send_buf), "%u:he###llo s???ver !!!",
             ntohs(client_sockaddr.sin_port));
    while (1)
    {
        send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);
        if (send_bytes == -1)
        {
            perror("send");
        }
        recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);
        if (recv_bytes == -1)
        {
            perror("recv");
        }
        printf("%s\n", recv_buf);
        sleep(1);
    }

    close(client_sockfd);
    return 0;
}

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

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

相关文章

离散数学--谓词逻辑之复习与前束范式与谓词演算的推理理论

引子&#xff1a;在命题演算中&#xff0c;常常要化成规范形式&#xff0c;对于谓词的演算&#xff0c;可以化成与他等价的范式&#xff01; 前束范式定义&#xff1a; 一个公式&#xff0c;如果量词均非否定地在全式的开头&#xff0c;它们的作用域延伸到整个公式的末尾&…

八、大模型之Fine-Tuning(1)

1 什么时候需要Fine-Tuning 有私有部署的需求开源模型原生的能力不满足业务需求 2 训练模型利器Hugging Face 官网&#xff08;https://huggingface.co/&#xff09;相当于面向NLP模型的Github基于transformer的开源模型非常全封装了模型、数据集、训练器等&#xff0c;资源…

工作究竟是谁的?

在近两年的就业环境中&#xff0c;普遍存在着挑战与不确定性&#xff0c;许多人追求的是一种稳定的工作和收入来源。在这样的背景下&#xff0c;我们来探讨一个核心问题&#xff1a;工作的归属是谁的&#xff1f; 根据《穷爸爸富爸爸》中提出的ESBI四象限理论&#xff0c;我们可…

【经典算法】LeetCode14:最长公共前缀(Java/C/Python3实现含注释说明)

最长公共前缀 题目思路及实现方式一&#xff1a;横向扫描思路代码实现Java版本C语言版本Python3版本 复杂度分析 方式二&#xff1a;纵向扫描思路代码实现Java版本C语言版本Python3版本 复杂度分析 方式三&#xff1a;分治思路代码实现Java版本C语言版本Python3版本 复杂度分析…

单元测试mockito(一)

1.单元测试 1.1 单元测试的特点 ●配合断言使用(杜绝System.out) ●可重复执行 。不依赖环境 ●不会对数据产生影响 ●spring的上下文环境不是必须的 ●一般都需要配合mock类框架来实现 1.2 mock类框架使用场景 要进行测试的方法存在外部依赖(如db,redis,第三方接口调用等),为…

3.31学习总结

(本次学习总结,总结了目前学习java遇到的一些关键字和零碎知识点) 一.static关键字 static可以用来修饰类的成员方法、类的成员变量、类中的内部类&#xff08;以及用static修饰的内部类中的变量、方法、内部类&#xff09;&#xff0c;另外可以编写static代码块来优化程序性…

权限问题(Windows-System)

方法&#xff1a;用命令来写一个注册表的脚本 &#xff1f;System是最高级用户&#xff0c;但不拥有最高级权限 编写两文档&#xff1a;system.reg 和 remove.reg,代码如下&#xff1a; system.reg&#xff1a; Windows Registry Editor Version 5.00[-HKEY_CLASSES_ROOT\*…

YOLOv5改进 | 低照度检测 | 2024最新改进CPA-Enhancer链式思考网络(适用低照度、图像去雾、雨天、雪天)

一、本文介绍 本文给大家带来的2024.3月份最新改进机制&#xff0c;由CPA-Enhancer: Chain-of-Thought Prompted Adaptive Enhancer for Object Detection under Unknown Degradations论文提出的CPA-Enhancer链式思考网络&#xff0c;CPA-Enhancer通过引入链式思考提示机制&am…

Proteus 12V to 5V buck电路仿真练习及遇到的一些问题汇总

基础电路仿真实验记录贴&#xff01;&#xff01;&#xff01;如有写的不对的地方欢迎交流指正&#xff01;&#xff01;&#xff01; 平台&#xff1a;PC win10 软件&#xff1a;Proteus8.10 仿真目标&#xff1a;buck降压电路&#xff08;PWM控制输出电压&#xff09; 写在…

《Lost in the Middle: How Language Models Use Long Contexts》AI 解读

作者&#xff1a;明明如月学长&#xff0c; CSDN 博客专家&#xff0c;大厂高级 Java 工程师&#xff0c;《性能优化方法论》作者、《解锁大厂思维&#xff1a;剖析《阿里巴巴Java开发手册》》、《再学经典&#xff1a;《Effective Java》独家解析》专栏作者。 热门文章推荐&am…

MySQL核心命令详解与实战,一文掌握MySQL使用

文章目录 文章简介演示库表创建数据库表选择数据库删除数据库创建表删除表向表中插入数据更新数据删除数据查询数据WHERE 操作符聚合函数LIKE 子句分组 GROUP BY HAVINGORDER BY(排序) 语句LIMIT 操作符 分页查询多表查询-联合查询 UNION 操作符多表查询-连接的使用-JOIN语句编…

选择排序及其优化

目录 思想&#xff1a; 代码&#xff1a; 代码优化&#xff1a; 需要注意的特殊情况&#xff1a; 可能出现的所有特殊情况&#xff1a; 优化完成代码&#xff1a; 思想&#xff1a; 每一次遍历数组&#xff0c;选择出最大或最小的数&#xff0c;将其与数组末尾或首位进行…

Oracle Solaris 11.3开工失败问题处理记录

1、故障现像 起初是我这有套RAC有点问题&#xff0c;我想重启1个节点&#xff0c;结果发现重启后该节点的IP能PING通&#xff0c;但SSH连不上去&#xff0c;对应的RAC服务也没有自动启动。 操作系统是solaris 11.3。由于该IP对应的主机是LDOM&#xff0c;于是我去主域上telnet…

汇编语言第四版-王爽第2章 寄存器

二进制左移四位&#xff0c;相当于四进制左移一位。 debug命令实操&#xff0c;win11不能启动&#xff0c;需要配置文件 Windows64位系统进入debug模式_window10系统64位怎么使用debugger-CSDN博客

扫雷(蓝桥杯)

题目描述 小明最近迷上了一款名为《扫雷》的游戏。其中有一个关卡的任务如下&#xff0c; 在一个二维平面上放置着 n 个炸雷&#xff0c;第 i 个炸雷 (xi , yi ,ri) 表示在坐标 (xi , yi) 处存在一个炸雷&#xff0c;它的爆炸范围是以半径为 ri 的一个圆。 为了顺利通过这片土…

开源博客项目Blog .NET Core源码学习(13:App.Hosting项目结构分析-1)

开源博客项目Blog的App.Hosting项目为MVC架构的&#xff0c;主要定义或保存博客网站前台内容显示页面及后台数据管理页面相关的控制器类、页面、js/css/images文件&#xff0c;页面使用基于layui的Razor页面&#xff08;最早学习本项目就是想学习layui的用法&#xff0c;不过最…

网络安全 | 网络攻击介绍

关注wx&#xff1a;CodingTechWork 网络攻击 网络攻击定义 以未经授权的方式访问网络、计算机系统或数字设备&#xff0c;故意窃取、暴露、篡改、禁用或破坏数据、应用程序或其他资产的行为。威胁参与者出于各种原因发起网络攻击&#xff0c;从小额盗窃发展到战争行为。采用各…

C语言自定义类型

本篇文章主要介绍三种自定义类型&#xff0c;分别是&#xff1a;结构体、联合体、枚举。 一.结构体 1.结构体类型的声明 直接举一个例子&#xff1a; //一本书 struct s {char name[10]; //名称char a; //作者int p; //价格 }; 2.特殊的声明 结构体也可以不写结构体标…

NVIDIA Jetson Xavier NX入门-镜像为jetpack5(3)——pytorch和torchvision安装

NVIDIA Jetson Xavier NX入门-镜像为jetpack5&#xff08;3&#xff09;——pytorch和torchvision安装 镜像为jetpack5系列&#xff1a; NVIDIA Jetson Xavier NX入门-镜像为jetpack5&#xff08;1&#xff09;——镜像烧写 NVIDIA Jetson Xavier NX入门-镜像为jetpack5&#…

医院陪诊管理系统(源码+文档)

TOC) 文件包含内容 1、搭建视频 2、流程图 3、开题报告 4、数据库 5、参考文献 6、服务器接口文件 7、接口文档 8、任务书 9、功能图 10、环境搭建软件 11、十六周指导记录 12、答辩ppt模板 13、技术详解 14、前端后台管理&#xff08;管理端程序&#xff09; 15、项目截图 1…