Linux网络编程系列之服务器编程——非阻塞IO模型

news2024/11/29 14:52:55

Linux网络编程系列  (够吃,管饱)

        1、Linux网络编程系列之网络编程基础

        2、Linux网络编程系列之TCP协议编程

        3、Linux网络编程系列之UDP协议编程

        4、Linux网络编程系列之UDP广播

        5、Linux网络编程系列之UDP组播

        6、Linux网络编程系列之服务器编程——阻塞IO模型

        7、Linux网络编程系列之服务器编程——非阻塞IO模型

        8、Linux网络编程系列之服务器编程——多路复用模型

        9、Linux网络编程系列之服务器编程——信号驱动模型

一、什么是非阻塞IO模型

        服务器非阻塞IO模型是一种服务器处理客户端连接请求的方式。在这种模型下,服务器会采用异步IO方式,即在一个线程中进行非阻塞IO操作,以此来处理多个客户端连接请求。当一个连接请求到来时,服务器会采用非阻塞的方式进行IO操作,这样就不会阻塞其他请求的处理,从而提高服务器的并发处理能力。另外,非阻塞IO模型可以避免复杂的多线程或多进程并发模型,降低服务器编程的复杂度。

二、特性

        1、异步IO方式

        采用异步IO方式进行IO操作,不会阻塞其他客户端连接请求的处理。

        2、单线程处理

        整个服务器只使用一个线程进行客户端连接请求处理,降低了线程切换和上下文切换的开销。

        3、事件驱动

        采用事件驱动的方式,只有在有客户端连接请求到来时才进行处理,节省了CPU资源消耗。

        4、高并发

        由于使用了非阻塞IO模型,使得服务器能够同时处理大量的客户端连接请求,提高了服务器的并发处理能力。

        5、低延迟

        使用非阻塞IO模型可以减少等待IO操作完成的时间,降低了请求的延迟。

        6、简单易用

        非阻塞IO模型可以避免复杂的多线程或多进程并发模型,降低服务器编程的复杂度。

三、使用场景

        1、高并发场景

        服务器需要处理大量的客户端连接请求,需要提高服务器的并发处理能力。

        2、低延迟场景

        对于需要快速响应的应用场景,比如实时通信、游戏等,可以采用非阻塞IO模型以减少请求的延迟。

        3、资源受限场景

        对于资源受限的服务器,比如嵌入式设备、单片机等,采用非阻塞IO模型可以节省CPU和内存资源,提高服务器的性价比。

        4、长连接场景

        对于需要维持长时间连接的应用,比如推送消息、物联网等,采用非阻塞IO模型可以减少连接的等待时间。

        5、单机多线程场景

        对于使用多线程模型的服务器应用,由于线程切换和上下文切换的开销,可能会导致性能瓶颈,采用非阻塞IO模型可以降低这种开销。

四、模型框架(通信流程)

        1、建立套接字。使用socket()

        2、设置端口复用。使用setsockopt()

        3、绑定自己的IP和端口号。使用bind()

        4、设置监听。使用listen()

        5、设置套接字为非阻塞状态。使用fcntl()

        6、轮询,接收连接请求。使用accept()

        7、轮询,查看活跃的客户端是否有数据到达。使用recv()

        8、关闭套接字。使用close()

五、相关函数API接口

         TCP通信流程常规的API那些在本系列的TCP协议里有大量展示,这里省略,详情可以点击本文开头的链接查看

        1、设置套接字为非阻塞状态

 // 5、设置监听套接字为非阻塞
int status = fcntl(sockfd, F_GETFL);    // 获取文件描述符状态
status |= O_NONBLOCK;    // 添加非阻塞状态
fcntl(sockfd, F_SETFL, status);     // 设置文件描述符状态

六、案例

        完成非阻塞IO模型结合TCP协议完成服务器通信演示,使用nc命令模拟客户端

// 服务器非阻塞IO的案例

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>       
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <fcntl.h>

#define MAX_LISTEN  50  // 最大能处理的连接数
#define SERVER_IP   "192.168.64.128"    // 记得改为自己IP
#define SERVER_PORT 20000   // 不能超过65535,也不要低于1000,防止端口误用

// 定义客服端管理类
struct ClientManager
{
    int client[MAX_LISTEN];     // 存储客户端的套接字
    char ip[MAX_LISTEN][20];    // 客户端套接字IP
    uint16_t port[MAX_LISTEN];  // 客户端套接字端口号
    int active_client_number;   // 活跃的客户端数量
};

// 初始化客户端管理类
void client_manager_init(struct ClientManager *manager)
{
    for(int i = 0; i < MAX_LISTEN; i++)
    {
        manager->client[i] = -1;
        manager->port[i] = 0;
        memset(manager->ip, 0, sizeof(manager->ip));
    }

    manager->active_client_number = 0;
}


int main(int argc, char *argv[])
{
    // 1、建立套接字,指定IPV4网络地址,TCP协议
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd == -1)
    {
        perror("socket fail");
        return -1;
    }

    // 2、设置端口复用(推荐)
    int optval = 1; // 这里设置为端口复用,所以随便写一个值
    int ret = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    if(ret == -1)
    {
        perror("setsockopt fail");
        close(sockfd);
        return -1;
    }

    // 3、绑定自己的IP地址和端口号(不可以省略)
    struct sockaddr_in server_addr = {0};
    socklen_t addr_len = sizeof(struct sockaddr);
    server_addr.sin_family = AF_INET;   // 指定协议为IPV4地址协议
    server_addr.sin_port = htons(SERVER_PORT);  // 端口号
    server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); // IP地址
    
    ret = bind(sockfd, (struct sockaddr*)&server_addr, addr_len);
    if(ret == -1)
    {
        perror("bind fail");
        close(sockfd);
        return -1;
    }

    // 4、设置监听
    ret = listen(sockfd, MAX_LISTEN);
    if(ret == -1)
    {
        perror("listen fail");
        close(sockfd);
        return -1;
    }

    // 5、设置监听套接字为非阻塞
    int status = fcntl(sockfd, F_GETFL);    // 获取文件描述符状态
    status |= O_NONBLOCK;    // 添加非阻塞状态
    fcntl(sockfd, F_SETFL, status);     // 设置文件描述符状态

    int i;
    uint16_t port = 0;  // 新的客户端的端口号
    char ip[20] = {0};  // 新的客户端的IP
    char recv_msg[128] = {0};   // 接收数据缓冲区
    struct sockaddr_in client_addr; // 客户端的地址

    struct ClientManager manager;
    client_manager_init(&manager);  // 初始化一个管理类

    printf("wait client connect...\n");

    while(1)
    {
        // 6、接受连接请求
        int new_client_fd = accept(sockfd, (struct sockaddr*)&client_addr, &addr_len);
        if(new_client_fd != -1)
        {
            memset(ip, 0, sizeof(ip));
            strcpy(ip, inet_ntoa(client_addr.sin_addr));
            port = ntohs(client_addr.sin_port);
            printf("[%s:%d] connect\n", ip, port);

            // 新连接的套接字也要设置为非阻塞状态
            status = fcntl(new_client_fd, F_GETFL);     // 获取文件描述符状态
            status |= O_NONBLOCK;   // 添加非阻塞状态
            fcntl(new_client_fd, F_SETFL, status);  // 设置文件描述符状态

            // 把连接上来的客户端套接字加入管理类
            manager.client[manager.active_client_number] = new_client_fd;
            manager.port[manager.active_client_number] = port;
            strcpy(manager.ip[manager.active_client_number], ip);
            manager.active_client_number++;
        }

        // 7、轮询方式查看连接上来的客户端是否有数据到达,非阻塞方式,不会等待
        for(i = 0; i < manager.active_client_number;)
        {
            // 尝试接收数据
            memset(recv_msg, 0, sizeof(recv_msg));
            ret = recv(manager.client[i], recv_msg, sizeof(recv_msg), 0);

            // 客户端断开
            if(ret == 0)
            {
                printf("[%s:%d] disconnet\n", manager.ip[i], manager.port[i]);
                for(int j = i + 1; j < manager.active_client_number; j++)
                {
                    // 把活跃的套接字往前移一位
                    if(manager.client[j] != -1)
                    {
                        manager.client[j-1] = manager.client[j];
                        manager.port[j-1] = manager.port[j];
                        strcpy(manager.ip[j-1],  manager.ip[j]);
                    }
                }
                // 更新活跃的套接字数量,注意不需要i++
                manager.active_client_number--;
                // 最后一个要清空
                manager.client[manager.active_client_number] = -1;
                manager.port[manager.active_client_number] = 0;
                memset(manager.ip[manager.active_client_number], 0, sizeof(ip));
            }
            else if(ret > 0)
            {
                printf("[%s:%d] send data: %s\n", manager.ip[i], manager.port[i], recv_msg);
                i++; // 这需要i++;,上面不用
            }
            else
            {
                i++;    // 这里也需要i++,没有数据时,需要询问下一个
            }
        }
    }

    // 7、关闭套接字
    close(sockfd);

    return 0;
}

七、总结

        非阻塞模型适用于资源有限的,需要高并发,低延迟的场景。非阻塞模型TCP服务器的通信流程跟普通的TCP服务器通信流程大致相同,区别在于不仅要设置服务器监听的套接字设置为非阻塞,而且要把客户端的套接字也设置为非阻塞。可以结合案例加深理解。

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

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

相关文章

echarts关于一次性绘制多个饼图 (基于vue3)

在echarts中&#xff0c;dataset 和 source 是用来配置数据的选项。 dataset 是一个包含数据相关配置的对象&#xff0c;用于指定数据的来源和格式。它可以包含多个维度的数据集&#xff0c;每个维度都可以有自己的名称和数据。 source 是 dataset 中的一个子项&#xff0c;用于…

图计算(林子雨慕课课程)

文章目录 13. 图计算13.1 图计算简介13.2 Pregel简介13.3 Pregel图计算模型13.3.1 有向图和顶点13.3.2 Pregel的计算过程13.3.2 Pregel实例 13.4 Pregel的C API13.4.1 定义Vertex基类13.4.2 消息传递机制和Combiner13.4.3 Aggregator、拓扑改变和输入输出 13.5 Pregel的体系结构…

【通过实验带你认识linux下的源码编译】

通过实验带你认识linux下的源码编译 01 初识项目编译02 编译过程03 完整的编译过程1、创建源代码文件2、创建configure脚本3、创建Makefile.am 源代码是相对目标代码和可执行代码而言的。源代码是用汇编语言和高级语言写出来的代码。 目标代码是指源代码经过编译程序产生的能被…

qml介绍

文章目录 qml简介对象一个风车的例子 qml简介 从 Qt 4.7 开始&#xff0c;Qt 引入了一种声明式脚本语言&#xff0c;称为 QML&#xff08;Qt Meta Language 或者 Qt Modeling Language&#xff09;&#xff0c;作为 C 语言的一种替代。而 Qt Quick 就是使用 QML 构建的一套类库…

(latex中appendix附录怎么写)以及(附录里面的图片表格之类的如何重新编号)

文章目录 初级&#xff1a;怎么写进阶&#xff1a;怎么重新编号进阶&#xff1a;怎么换成单栏格式 初级&#xff1a;怎么写 这个很简单&#xff0c;我一开始以为很复杂。 \begin{document} #这里是“正文”。 #这里是“引用”。 #下面开始是附录。 \appendix \section{Proofs…

英语——分享篇——每日100词——801-900

medical——adj.医疗的——me我(熟词)di弟(拼音)cal擦了(拼音) chief——n.酋长——thief小偷——小偷拜见酋长 pork——n.猪肉——p皮鞋(编码)or偶人(拼音)k机关枪(编码)——穿着皮鞋的偶人扛着机关枪挑猪肉 pie——n.馅饼&#xff0c;派——瞥——他无意瞥见一块馅饼 saus…

GB28181平台简介

产品简介 LiveMedia视频中间件是支持部署到本地服务器或者云服务器的纯软件服务&#xff0c;也提供服务器、GPU一体机全包服务&#xff0c;提供视频设备管理、无插件、跨平台的实时视频、历史回放、语音对讲、设备控制等基础功能&#xff0c;支持视频协议有海康、大华私有协议…

Golang学习记录:基础篇练习(一)

Golang学习记录&#xff1a;基础篇练习&#xff08;一&#xff09; 1、九九乘法表2、水仙花数3、斐波那契数列4、编写一个函数&#xff0c;求100以内的质数5、统计字符串里面的字母、数字、空格以及其他字符的个数6、二维数组对角线的和7、冒泡排序算法8、选择排序算法9、二分查…

JDK 19 协程新特性学习

目录 一、协程定义 二、协程发展史 &#xff08;一&#xff09;协程的基本发展史说明 &#xff08;二&#xff09;Java协程发展说明 三、JDK 19 协程的原理细节 &#xff08;一&#xff09;Thread.ofVirtual().start() &#xff08;二&#xff09;SocketChannel.write(…

没有前端如何测试后端跨域问题

一、问题 前段时间对项目中的跨域做了相关的处理&#xff0c;网上有很多跨域的解决方案。前端解决&#xff0c;后端解决&#xff0c;nginx代理解决。我采用的是在后端中使用Cors来解决跨域的问题。但是前端项目还没有搭建起来&#xff0c;并不知道Cors的解决方案是否会生效&am…

揭秘元宇宙背后最炫科技风:数字经济时代,元宇宙发展解决方案及核心技术

文章目录 前言一、关于“元宇宙”业界趋势1.1、元宇宙的概念与发展历程1.2、行业应用体验向虚实融合和实时互动演进1.3、数字内容成为各行业 3D 数字世界入口 二、对于元宇宙发展的解决方案和实践2.1、MetaStudio 构建场景化全栈能力2.2、企业 3D 空间&#xff0c;围绕 4 类场景…

PyQt界面里如何加载本地视频以及调用摄像头实时检测(小白入门必看)

目录 1.PyQt介绍 2.代码实现 2.1实时调用摄像头 2.2 使用YOLOv5推理 2.3 代码中用到的主要函数 1.PyQt介绍 PyQt是一个用于创建桌面应用程序的Python绑定库&#xff0c;它基于Qt框架。Qt是一个跨平台的C应用程序开发框架&#xff0c;提供了丰富的图形界面、网络通信、数据…

Qt项目通过.pri文件将众多文件按功能模块分类显示,开发大型项目必备

Qt项目通过.pri文件将众多文件按功能模块分类显示&#xff0c;开发大型项目必备 Chapter1 Qt项目通过.pri文件将众多文件按功能模块分类显示&#xff0c;开发大型项目必备($$$)Chapter2 在Qt项目中添加pri文件前言创建pri文件的步骤一、创建Qt项目二、创建pri空文件三、调试 Ch…

SpringCloud学习笔记-Nacos服务分级存储模型

Nacos服务分级存储模型 一级是服务&#xff0c;例如userservice二级是集群&#xff0c;例如杭州或上海三级是实例&#xff0c;例如杭州机房的某台部署了userservice的服务器 微服务互相访问时&#xff0c;应该尽可能访问同集群实例&#xff0c;因为本地访问速度更快。当本集…

创新YOLOv8改进:结合全新可变形大核注意力(D-LKA Attention)实现多尺度目标涨点

🔥🔥🔥 提升多尺度目标检测,创新提升 🔥🔥🔥 🔥🔥🔥 捕捉图像特征和处理复杂图像特征 🔥🔥🔥 👉👉👉: 本专栏包含大量的新设计的创新想法,包含详细的代码和说明,具备有效的创新组合,可以有效应用到改进创新当中 👉👉👉: 🐤🐤�…

寻找AI时代的关键拼图,从美国橡树岭国家实验室读懂AI存力信标

超算&#xff0c;是计算产业的明珠&#xff0c;是人类探索未知的航船。超算的发展与变化&#xff0c;不仅代表着各个国家与地区间的科技竞争力&#xff0c;更将作为趋势风向标&#xff0c;影响整个数字化体系的走向。 在目前阶段&#xff0c;超算与AI计算的融合是大势所趋。为了…

C# GPEN-BFR 图像修复

效果 项目 代码 using Microsoft.ML.OnnxRuntime; using Microsoft.ML.OnnxRuntime.Tensors; using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Drawing.Imaging; using System.Windows.Forms;namespace 图像修复 {pu…

【部署】Linux Shell脚本部署java程序 (jar包)

文章目录 前言 前言 #!/usr/bin/env bash #可变参数变量#部署磁盘路径 baseDirPath/data/apps/java/smj-exchange-upload #jar包名称 packageNamesmj-exchange-upload.jar #命令启动包名 xx.jar的xxpid #进程pid#检测pid getPid(){echo "检测状态-----------------------…

Python爬虫:某书平台的Authorization参数js逆向

⭐️⭐️⭐️⭐️⭐️欢迎来到我的博客⭐️⭐️⭐️⭐️⭐️ 🐴作者:秋无之地 🐴简介:CSDN爬虫、后端、大数据领域创作者。目前从事python爬虫、后端和大数据等相关工作,主要擅长领域有:爬虫、后端、大数据开发、数据分析等。 🐴欢迎小伙伴们点赞👍🏻、收藏⭐️、…

深势科技基于 Serverless 容器为科研人员打造高效的开发平台

作者&#xff1a;李样兵、刘杉、木环、玖宇、鼎岳 云端的科学研究&#xff0c;AI for Science 新范式 以往科学家们的科研工作需要经过大量实验的重复验证、复杂数学计算&#xff0c;以及长年累月的不断试错和苦苦探索。云计算基础服务的发展和人工智能技术 AI 的兴起&#x…