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

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];    // 客户端套接字IPuint16_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};  // 新的客户端的IPchar 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.mzph.cn/news/106713.shtml

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

相关文章

将license验证加入到系统中

1.将ClientDemo下的cn文件夹的内容导入项目对应的java目录下。 2.将license-config.properties文件导入resources目录下。 3.在项目的pom.xml中添加如下依赖。 <properties><!-- Apache HttpClient --><httpclient>4.5.5</httpclient><!-- License…

MySQL 4 MySQL使用演示(包含基本操作命令~~~)MySQL5.7编码设置

目录 1 MySQL的使用演示&#xff08;8.0和5.7版本对比&#xff09; 1、查看所有的数据库 2、创建自己的数据库 3、使用自己的数据库 4、查看某个库的所有表格 5、创建新的表格 6、查看一个表的数据 7、添加一条记录 8、查看表的创建信息 9、查看数据库的创建信息 …

关于Vue+webpack使用unocss编写CSS,打包后CSS没加前缀

关于Vuewebpack使用unocss编写CSS&#xff0c;打包后CSS没加前缀&#xff0c;封装了一个插件去解决了这个问题 unocss-postcss-webpack-plugin unocss在vite中使用配置&#xff0c;关于unocss在vite中使用&#xff0c;自行查阅官网 https://unocss.dev/integrations/vite ,vi…

【LeetCode】24. 两两交换链表中的节点

1 问题 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xf…

Lua在计算时出现非法值,开启Debugger之后不再触发

1&#xff09;Lua在计算时出现非法值&#xff0c;开启Debugger之后不再触发 2&#xff09;从Gamma空间改为Linear空间会导致性能下降吗 3&#xff09;EXR格式在Unity中如何优化 4&#xff09;安卓游戏启动后提示“应用程序异常” 这是第355篇UWA技术知识分享的推送&#xff0c;…

如何抓取AJAX请求

在Python中&#xff0c;可以使用第三方库requests来抓取通过AJAX请求加载的数据。以下是一个示例代码&#xff1a; import requestsurl "https://example.com/ajax-endpoint"payload { "param1": "value1", "param2": "v…

基于VScode 使用plantUML 插件设计状态机

本文主要记录本人初次在VScode上使用PlantUML设计 本文只讲述操作的实际方法&#xff0c;假设java已安装成功 。 1. 在VScode下安装如下插件 2. 验证环境是否正常 新建一个文件夹并在目录下面新建文件test.plantuml 其内容如下所示: startuml hello world skinparam Style …

力扣 -- 1143. 最长公共子序列

解题步骤&#xff1a; 参考代码&#xff1a; class Solution { public:int longestCommonSubsequence(string s1, string s2) {int ms1.size();int ns2.size();s1 s1;s2 s2;vector<vector<int>> dp(m1,vector<int>(n1));for(int i1;i<m;i){for(int j1;j&…

SpringBoot采用集成多个Mybatis框架实现多JDBC数据源 + 多数据源本地事务

目录 1. 原理1.1 实现多数据源的原理1.2 多数据源本地事务的原理 2. Mysql数据准备3. pom.xml依赖4. application.properties配置5. 创建两个SqlSessionFactory5.1 DataSource1Config5.1 DataSource2Config 6. 动态数据源测试6.1 创建User类6.2 Mapper接口实现6.3 Mapper.xml实…

【狂神说】HTML详解

目录 1 HTML概述1.1 什么是HTML1.2 HTML发展史1.3 HTML5的优势1.4 W3C标准 2 网页2.1 网页基本信息2.2 网页基本标签2.2.1 标题标签2.2.2 段落标签2.2.3 换行标签2.2.4 水平线标签2.2.5 字体样式标签&#xff1a;粗体、斜体2.2.6 注释和特殊符号 2.3 图像标签2.4 链接标签邮箱链…

C#串口原理

串口实际有2种。主要是电压逻辑不一样。玩单片机的人指usb转TTL的串口&#xff1b;普通人指USB转DB9的串口&#xff1b;先看下他们的区别&#xff1a; https://doc.embedfire.com/module/module_tutorial/zh/latest/Module_Manual/port_class/serial_port.html 1. 串口外设总结…

PC网站微信扫码登录

PC站通过微信扫码登录网站。 参考文档&#xff1a;微信官方文档 1.通过在PC端打开以下链接&#xff1a; https://open.weixin.qq.com/connect/qrconnect?appidAPPID&redirect_uriREDIRECT_URI&response_typecode&scopeSCOPE&stateSTATE#wechat_redirect 用…

Flink的基于两阶段提交协议的事务数据汇实现

背景 在flink中可以通过使用事务性数据汇实现精准一次的保证&#xff0c;本文基于Kakfa的事务处理来看一下在Flink 内部如何实现基于两阶段提交协议的事务性数据汇. flink kafka事务性数据汇的实现 1。首先在开始进行快照的时候也就是收到checkpoint通知的时候&#xff0c;在…

15.项目讲解之前端页面的实现

项目讲解之前端页面的实现 本项目前端使用HBuilerX软件编写HBuilderX下载安装配置一键直达&#xff0c; uniapp框架uniapp官网&#xff0c; 使用Element-ui组件Element-ui组件网址进行前端页面的完成。 前端项目下载地址 前端项目 前端项目展示 首页 首页展示 echarts实现…

简单的数学运算如何改变算法

简单的数学运算如何影响事物 当你坐在无人驾驶汽车上行驶时&#xff0c;突然发现前面有一个问题。一个亚马逊快递司机将他们的货车开到了一辆双停的UPS卡车旁边&#xff0c;然后才意识到无法通过。现在他们卡住了&#xff0c;你也卡住了。 街道太窄&#xff0c;无法实现U型转弯…

CCF CSP认证 历年题目自练Day32

题目一 试题编号&#xff1a; 202209-1 试题名称&#xff1a; 如此编码 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 512.0MB 问题描述&#xff1a; 题目背景 某次测验后&#xff0c;顿顿老师在黑板上留下了一串数字 23333 便飘然而去。凝望着这个神秘数字&#xff0c;小…

C# Convert和BitConverter类学习

前言&#xff1a; C# Convert是一个比较好用的强制转换&#xff0c;相比我们之前用的(int)或者是类型.Parse()&#xff0c;Convert给我们提供了很多的选项&#xff0c;特别是对于有字节要求的变量&#xff0c;Convert简直就是C#编程的福音&#xff0c;BitConvert对于byte数组转…

linux下文件存储系统(inode/目录项/硬链接)

概念&#xff1a; 关键点&#xff1a; &#xff08;1&#xff09;inode 也叫做文件属性管理结构体 &#xff08;2&#xff09;目录项里面存两个东西 文件名和 inode号。通过inode号可以找到磁盘上的文件。 &#xff08;3&#xff09;给文件创建硬链接的时候&#xff0c;两个…

中国矿业大学-JAVA期末备考

JAVA里面&#xff0c;“”和“equals"的区别是什么呢&#xff1f; 1.""操作符用于比较两个对象的引用是否相等。也就是说&#xff0c;它会检查两个对象是否指向内存中的同一个地址。如果两个对象的引用完全相同&#xff0c;则""返回true&#xff1b;否…

uniapp 小程序实现图片宽度100%、高度自适应的效果

因为image组件默认是有宽度跟高度的&#xff0c;所以这个高度不怎么好写 通过load事件来控制图片的高度 话不多说&#xff0c;直接上代码&#xff0c; <image class"img" src"/static/image.png" :style"{ height: imgHeight px }"mode&q…