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

深入了解linux下TCP并发服务器和IO模型的实现

一、整体框架

在网络编程中,服务器的架构可以根据需求不同而有所不同。主要有以下几种框架:

1. 单循环服务器:同一时刻只处理一个客户端的请求,通常使用传统的阻塞式编程模型。这种模型简单易实现,但处理能力有限,无法有效应对多个客户端的并发请求。

2. 并发服务器:能够处理多个客户端的请求。通过引入多线程或多进程等技术来实现并发处理,从而提高服务器的处理能力和响应速度。并发服务器可以分为以下几种实现方式:
   - 多进程:每个客户端连接由一个独立的进程处理。常用于需要隔离处理环境的场景。
   - 多线程:每个客户端连接由一个独立的线程处理。适用于需要较高资源共享的场景。
   - IO多路复用:使用单个进程/线程处理多个连接,通过高效的IO多路复用机制来管理多个并发连接。适用于需要高并发处理的场景。

 二、服务器

2.1 单循环服务器 vs 并发服务器

- 单循环服务器:
  - 处理一个客户端的请求时,其他客户端的请求必须等待,导致处理效率低。
  - 简单易实现,但不适合高并发场景。

- 并发服务器:
  - 可以同时处理多个客户端的请求。通过创建多个进程或线程来处理不同的客户端连接,从而提高服务器的并发处理能力。
  - UDP协议由于是无连接的,天然支持并发处理。每个数据报独立处理,不需要建立持久连接。
  - TCP协议是面向连接的,传统上一个TCP服务器只能处理一个客户端连接。但通过多进程或多线程的方式,可以实现TCP并发服务器。

三、TCP并发服务器

3.1 多进程

多进程模型的服务器在接收到连接请求时,会创建一个新的进程来处理每一个客户端连接。以下是基本的执行流程:

1. socket():创建一个新的套接字。
2. bind():将套接字绑定到特定的IP地址和端口号。
3. listen():将套接字设置为监听模式,等待客户端的连接请求。
4. accept():接受客户端的连接请求,返回一个新的套接字用于与客户端通信。
5. fork():创建一个子进程来处理新的客户端连接。父进程继续监听新的连接请求。

3.2 多线程

多线程模型的服务器在接收到连接请求时,会创建一个新的线程来处理每一个客户端连接。以下是一个示例代码:


/*************************************************************************
    > File Name: pthread.c
    > Author: yas
    > Mail: rage_yas@hotmail.com
    > Created Time: Tue 27 Aug 2024 02:48:41 PM
 ************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

void *doSth(void *arg) {
    int connfd = *((int *)arg); 
    while (1) {
        char buff[1024] = {0};
        ssize_t size = recv(connfd, buff, sizeof(buff), 0);
        if (size <= 0) {
            break;
        }
        printf("cli--------->%s\n", buff);
        strcat(buff, "----->ok!");
        send(connfd, buff, strlen(buff), 0);
    }
    close(connfd);
    return NULL;
}

int conct(const char *ip, unsigned short port) {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(port);
    ser.sin_addr.s_addr = inet_addr(ip);
    int ret = bind(sockfd, (struct sockaddr *)&ser, sizeof(ser));
    if (ret == -1) {
        perror("fail bind");
        return -1;
    }
    ret = listen(sockfd, 120);
    if (ret == -1) {
        return -1;
    }
    return sockfd;
}

int main(int argc, char *argv[]) {
    int connfd = 0;
    pthread_t tid;
    int sockfd = conct("192.168.1.112", 60000);
    if (sockfd == -1) {
        return -1;
    }
    while (1) {
        connfd = accept(sockfd, NULL, NULL);
        if (connfd == -1) {
            return -1;
        }
        pthread_create(&tid, NULL, doSth, &connfd);
        pthread_detach(tid);
    }
    return 0;
}
 

在这个示例中:
- `pthread_create` 用于创建新的线程来处理客户端请求。
- `pthread_detach` 用于分离线程,使其在完成后自动回收资源。

3.3 IO多路复用

IO多路复用技术允许单个进程/线程处理多个IO操作,常用于高并发场景。主要实现方式有:
- select:检查多个文件描述符的状态,判断是否可以进行读写操作。
- poll:与`select`类似,但支持更多的文件描述符。
- epoll:高效的IO多路复用机制,适用于大规模文件描述符的场景。

四、IO模型

4.1 阻塞IO

在阻塞IO模型中,系统调用会阻塞直到有数据可用或操作完成。例如,`fgets`、`scanf`、`read`、`recv`等。

特点:
- CPU占有率低:由于阻塞等待,CPU不会频繁进行上下文切换。
- 执行效率低:处理效率低下,特别是在处理大量连接时。

4.2 非阻塞IO

非阻塞IO模型允许系统调用立即返回,无论是否有数据可用。需要通过轮询来检查数据的到达。

特点:
- CPU占有率高:由于轮询机制,CPU会不断检查IO状态,导致较高的占用率。
- 实现复杂:需要处理数据是否可用的逻辑。

实现步骤:
1. 获取文件描述符的属性。
2. 增加非阻塞属性。
3. 设置新属性。

示例代码:


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>

void handle(int signo) {
    char buf[1024] = {0};
    fgets(buf, sizeof(buf), stdin);
    printf("STDIN : %s\n", buf);
}

int main(int argc, char *argv[]) {
    signal(SIGIO, handle);
    char buf[1024] = {0};
    mkfifo("./fifo", 0666);
    int fd = open("./fifo", O_RDONLY);
    int flag = fcntl(0, F_GETFL);
    flag = flag | O_ASYNC;
    fcntl(0, F_SETFL, flag);
    fcntl(0, F_SETOWN, getpid());
    while (1) {
        memset(buf, 0, sizeof(buf));
        read(fd, buf, sizeof(buf));
        sleep(1);
        printf("%s\n", buf);
    }
    close(fd);
    return 0;
}
 

 4.3 信号驱动IO

信号驱动IO模型通过信号机制来通知数据的到达。可以减少CPU的占用率,因为系统会在数据到达时发送信号。

特点:
- 异步通知:当IO操作准备好时,系统会发送信号通知进程。
- 效率高:适合处理少量IO操作的场景。

实现步骤:
1. 增加异步属性 `O_ASYNC`。
2. 关联信号和当前进程。
3. 注册信号处理函数。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • C++:list篇
  • 【60天备战软考高级系统架构设计师——第四天:需求获取与初步分析】
  • 站长神器,AI批量生成原创文章工具免费用还能自动发布到站点
  • Mysql-redo logs,binlog以及undo logs的作用及区别
  • llm 是泡沫?
  • 软件测试工程师必备的技术能力
  • 【通过h5作为中转页跳转到微信小程序】
  • LMDeploy 量化部署进阶实践
  • c++中的匿名对象及内存管理及模版初阶
  • 【自用16.】C++类
  • 组合式API-reactive和ref函数,computed计算属性,watch函数
  • Linux和Unix的区别及为什么鸿蒙系统不用Unix的原因
  • 排序算法(冒泡、插入、选择、快排、归并)原理动画及Python、Java实现
  • 进程、线程的区别
  • identYwaf:一款基于盲推理识别技术的WAF检测工具
  • [数据结构]链表的实现在PHP中
  • 2017 前端面试准备 - 收藏集 - 掘金
  • ERLANG 网工修炼笔记 ---- UDP
  • tweak 支持第三方库
  • vagrant 添加本地 box 安装 laravel homestead
  • Vue官网教程学习过程中值得记录的一些事情
  • 分布式事物理论与实践
  • 基于Android乐音识别(2)
  • 什么软件可以提取视频中的音频制作成手机铃声
  • 实习面试笔记
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 阿里云重庆大学大数据训练营落地分享
  • 进程与线程(三)——进程/线程间通信
  • 整理一些计算机基础知识!
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​queue --- 一个同步的队列类​
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (阿里云万网)-域名注册购买实名流程
  • (八)c52学习之旅-中断实验
  • (读书笔记)Javascript高级程序设计---ECMAScript基础
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (每日一问)计算机网络:浏览器输入一个地址到跳出网页这个过程中发生了哪些事情?(废话少说版)
  • (面试必看!)锁策略
  • (三)Honghu Cloud云架构一定时调度平台
  • (三)Hyperledger Fabric 1.1安装部署-chaincode测试
  • (算法)Game
  • (已解决)什么是vue导航守卫
  • (转载)微软数据挖掘算法:Microsoft 时序算法(5)
  • *上位机的定义
  • .htaccess配置常用技巧
  • .net core 客户端缓存、服务器端响应缓存、服务器内存缓存
  • .NET Core引入性能分析引导优化
  • .net之微信企业号开发(一) 所使用的环境与工具以及准备工作
  • /usr/lib/mysql/plugin权限_给数据库增加密码策略遇到的权限问题
  • ?.的用法
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题