Linux网络编程---多路I/O转接服务器(一)

多路I/O转接服务器

多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接,取而代之由内核替应用程序监视文件。

主要使用的方法有三种:select、poll、epoll 

一、select多路IO转接

让内核去监听客户端连接(lfd),当有客户端进行连接时 它会让server去调用accetp(当有连接时才去立即调用,而不是一直阻塞等待)得到一个用于通信的cfd,最后让内核监管着lfd和所有cfd

即(原理):借助内核, select 来监听, 客户端连接、数据通信事件。

函数解析

1. 底层原理: 

文件描述符表:前三个默认被系统占用

fd_set集合:传入的是文件描述符,传出所有监听集合(读、写、异常)中满足对应事件的总数

fd_set集合的本质:位图(二进制位存放文件描述符的状态),默认都为0,若发生变化就置1

2. 语法: 

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeva l *timeout);

参数

  • nfds:监听的所有文件描述符中,最大文件描述符+1;
  • readfds:读 文件描述符监听集合。传入传出参数
  • writefds:写 文件描述符监听集合。传入传出参数,通常传NULL
  • exceptfds:异常 文件描述符监听集合。传入传出参数,通常传NULL
  • timeout:大于0表示设置监听时长,NULL表示阻塞监听,0表示非阻塞监听 while轮询

返回值

  • 大于0:所有监听集合(读、写、异常)中满足对应事件的总数
  • 0:没有满足监听条件的文件描述符
  • -1:error

3. 监听集合对应函数:

1.void FD_ZERO(fd_set *set); ---清空一个文件描述符集合             

fd_set rset; FD_ZERO(&rset); //将rset集合清空

2.void FD_SET(int fd, fd_set *set); ---将待监听的文件描述符添加到监听集合中

FD_SET(3,&rset);FD_SET(5,&rset); //将文件描述符3和5加到rset集合中

3.void FD_CLR(int fd, fd_set *set); ---将一个文件描述符从监听集合中移除

4.int FD_ISSET(int fd, fd_set *set); ---判断一个文件描述符是否在该集合中

4. 思路分析: 

代码实现:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int listenfd, connfd;               // connect fd
    char buf[BUFSIZ];         /* #define INET_ADDRSTRLEN 16 */

    struct sockaddr_in clie_addr, serv_addr;
    socklen_t clie_addr_len;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);  

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    bzero(&serv_addr, sizeof(serv_addr));

    serv_addr.sin_family= AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port= htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    Listen(listenfd, 128);

    fd_set rset,allset;//定义读集合,备份集合allset
    int ret,maxfd = 0,n;
    maxfd = listenfd;//最大文件描述符

    FD_ZERO(&allset);//清空监听集合
    FD_SET(listenfd,&allset);//将监听fd添加到监听集合中

    while(1)
    {
        rset = allset;//备份
        select(maxfd+1,&rset,NULL,NULL,NULL);//使用select监听
        if (ret < 0)
        {
            perr_exit("select error");
        }

        if (FD_ISSET(listenfd,&rset))//listenfd满足监听的读事件
        {
            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd,(struct sockaddr *)&clie_addr,&clie_addr_len);//建立连接---不会阻塞
                        
            FD_SET(connfd,&allset);//将新产生的fd添加到监听集合中监听数据读事件
            if (maxfd < connfd)//修改maxfd
            maxfd = connfd;
                        
            if (ret = 1)//说明select只返回一个,并且是listenfd,后续执行无需执行
                continue;    
        }

        for (int i = listenfd+1;i <= maxfd;i++)//处理 满足读事件的fd
        {
            if (FD_ISSET(i,&rset))//找到满足读事件的fd
            {
                n = read(i,buf,sizeof(buf));
                if (n == 0)//检测到客户端已经关闭连接
                {
                    Close(i);
                    FD_CLR(i,&allset);//将关闭的fd移除出监听集合
                }
                else if (n == -1)
                {
                    perr_exit("read error");
                }

                for (int j = 0;j<n;j++)
                {
                    buf[j] = toupper(buf[j]);
                }
                write(i,buf,n);
                write(STDOUT_FILENO,buf,n);
            }
        }
    }

        Close(listenfd);
        return 0;
}

 select优缺点:

优点:跨平台。win、linux、macOS、Unix、类Unix、mips

缺点:监听上限受文件描述符限制。 最大 1024.

           检测满足条件的fd, 自己添加业务逻辑提高小。 提高了编码难度。

*二、poll多路IO转接(半成品):在实际开发过程中用处不大,了解即可,重点是epoll

函数解析:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

参数:

        fds:监听的文件描述符【数组】

        struct pollfd

        {                
                int fd:待监听的文件描述符     
                short events:待监听的文件描述符对应的监听事件

                            取值:POLLIN、POLLOUT、POLLERR

                short revnets:传入时, 给0。如果满足对应事件的话, 返回 非0 --> POLLIN、POLLOUT、POLLERR
            }

        nfds: 监听数组的,实际有效监听个数。

        timeout:  > 0:  超时时长。单位:毫秒。

                      -1:    阻塞等待

                      0:  不阻塞

返回值:

        返回满足对应监听事件的文件描述符 总个数。

思路分析:

代码实现:

/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h"

#define MAXLINE 80
#define SERV_PORT 6666
#define OPEN_MAX 1024

int main(int argc, char *argv[])
{
	int i, j, maxi, listenfd, connfd, sockfd;
	int nready;
	ssize_t n;
	char buf[MAXLINE], str[INET_ADDRSTRLEN];
	socklen_t clilen;
	struct pollfd client[OPEN_MAX];
	struct sockaddr_in cliaddr, servaddr;

	listenfd = Socket(AF_INET, SOCK_STREAM, 0);

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(SERV_PORT);

	Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

	Listen(listenfd, 20);

	client[0].fd = listenfd;
	client[0].events = POLLIN; 					/* listenfd监听普通读事件 */
    for (i = 1; i < OPEN_MAX; i++)
		client[i].fd = -1; 							/* 用-1初始化client[]里剩下元素 */
	maxi = 0; 										/* client[]数组有效元素中最大元素下标 */

	for ( ; ; ) {
		nready = poll(client, maxi+1, -1); 			/* 阻塞 */
		if (client[0].revents & POLLIN) { 		/* 有客户端链接请求 */
			clilen = sizeof(cliaddr);
			connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
			printf("received from %s at PORT %d\n",
					inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
					ntohs(cliaddr.sin_port));
			for (i = 1; i < OPEN_MAX; i++) {
				if (client[i].fd < 0) {
					client[i].fd = connfd; 	/* 找到client[]中空闲的位置,存放accept返回的connfd */
					break;
				}
			}

			if (i == OPEN_MAX)
				perr_exit("too many clients");

			client[i].events = POLLIN; 		/* 设置刚刚返回的connfd,监控读事件 */
			if (i > maxi)
				maxi = i; 						/* 更新client[]中最大元素下标 */
			if (--nready <= 0)
				continue; 						/* 没有更多就绪事件时,继续回到poll阻塞 */
		}
		for (i = 1; i <= maxi; i++) { 			/* 检测client[] */
			if ((sockfd = client[i].fd) < 0)
				continue;
			if (client[i].revents & POLLIN) {
				if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
					if (errno == ECONNRESET) { /* 当收到 RST标志时 */
						/* connection reset by client */
						printf("client[%d] aborted connection\n", i);
						Close(sockfd);
						client[i].fd = -1;
					} else {
						perr_exit("read error");
					}
				} else if (n == 0) {
					/* connection closed by client */
					printf("client[%d] closed connection\n", i);
					Close(sockfd);
					client[i].fd = -1;
				} else {
		                for (j = 0; j < n; j++)
						    buf[j] = toupper(buf[j]);
						    Writen(sockfd, buf, n);
				       }
				if (--nready <= 0)
					break; 				/* no more readable descriptors */
			}
		}
	}
	return 0;
}

read 函数返回值:      
    > 0:实际读到的字节数

    =0: socket中,表示对端关闭。close()

    -1: 如果 errno == EINTR   被异常终端。 需要重启。

            如果 errno == EAGIN 或 EWOULDBLOCK 以非阻塞方式读数据,但是没有数据。 需要,再次读。

           如果 errno == ECONNRESET  说明连接被 重置。 需要 close(),移除监听队列。

           错误。 

poll优缺点: 

优点:
        自带数组结构。 可以将 监听事件集合 和 返回事件集合 分离。

        拓展 监听上限。 超出 1024限制。

缺点:
        不能跨平台。 Linux

        无法直接定位满足监听事件的文件描述符, 编码难度较大。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java 网络编程之TCP(三):基于NIO实现服务端,BIO实现客户端

前面的文章&#xff0c;我们讲述了BIO的概念&#xff0c;以及编程模型&#xff0c;由于BIO中服务器端的一些阻塞的点&#xff0c;导致服务端对于每一个客户端连接&#xff0c;都要开辟一个线程来处理&#xff0c;导致资源浪费&#xff0c;效率低。 为此&#xff0c;Linux 内核…

边缘计算在视频监控领域的应用

一、边缘计算在视频监控领域的应用 运用边缘计算解决视频监控问题&#xff0c;可以带来许多优势。以下是一些具体的应用示例&#xff1a; 实时分析与处理&#xff1a;在视频监控系统中&#xff0c;边缘计算盒子可以实时处理和分析视频流&#xff0c;实现对监控画面的智能识别…

BGP选路实验(锐捷)---AS-PATH选路

实验拓扑图 基本配置如图所示 要求&#xff1a;R8上利用loopback口建立多个分段ip&#xff0c;利用bgp选路原则让双网段数据通过R6转发&#xff0c;单网段数据通过R7转发&#xff0c;这里添加as-path号建议添加自己的bgp所属的as号&#xff0c;以防止修改as-path后影响as-path…

❤️新版Linux零基础快速入门到精通——第二部分❤️

❤️新版Linux零基础快速入门到精通——第二部分❤️ 非科班的我&#xff01;Ta&#xff01;还是来了~~~2. Linux基础命令2.1 类Unix系统目录结构2.2 Linux目录结构2.2.1 Linux用户目录2.2.2 Linux目录练习 2.3 Linux 命令入门2.3.1 命令基础2.3.1.1 help2.3.1.2 man(manual)2.…

Windows Vscode ModuleNotFoundError: No module named

故障现象&#xff1a; Windows Vscode 经常会遇到模块路径查找失败的异常。 如运行2_from_import_test.py后&#xff0c;报错&#xff1a; 发生异常: ModuleNotFoundError No module named programmer File "D:\leolab\programmer\2_from_import_test.py", line 8…

虚拟机VMware下ROS Neotic(Ubuntu 20.04)下安装OpenCV

一、ROS安装 ROS的官方安装步骤&#xff1a; 1、noetic / Ubuntu 20.04 &#xff1a; http://wiki.ros.org/noetic/Installation/Ubuntu 2、melodic / Ubuntu 18.04&#xff1a; http://wiki.ros.org/melodic/Installation/Ubuntu 3、kinetic / Ubuntu 16.04&#xff1a; http:…

C语言:一维数组、二维数组、字符数组介绍

数组 介绍一维数组定义应用方法初始化 举例示例结果 二维数组定义应用方法初始化 举例示例结果 字符数组定义应用方法初始化 举例示例结果分析 介绍 在C语言中&#xff0c;数组是一种基本的数据结构&#xff0c;用于存储一系列相同类型的数据。数组可以是多维的&#xff0c;最…

phpstorm 设置变量,自动补全代码

效果 进入设置->实时模板->PHP->添加 添加动态模板->完善写法 定义->选择PHP->应用就行

什么是宏观经济的先行指标、同步指标与滞后指标

宏观经济波动是一种周期性的繁荣、衰退、萧条、复苏循环变化过程&#xff0c;在这种变动中&#xff0c;不同经济指标的变动并非总与宏观经济运行步调一致。按统计指标变动轨迹与宏观经济变动轨迹的时间关系,可以将其划分为先行指标、同步指标和滞后指标。 一、概念和作用 先行…

JetBrains CLion v2023.3.4 激活版 (C/C++ 集成开发IDE)

前言 JetBrains CLion是一款跨平台的C/C集成开发环境&#xff0c;由JetBrains公司推出。其最新版本支持C14几乎完全&#xff0c;并初步支持C17&#xff0c;使得编写代码更加便捷。CLion还提供了Disassembly view&#xff08;反汇编视图&#xff09;&#xff0c;即使没有源代码…

《欢乐钓鱼大师》攻略:怎么在竞标赛中获得高分?

《欢乐钓鱼大师》锦标赛是游戏中的一项激动人心的钓鱼比赛活动&#xff0c;而在这场比赛中&#xff0c;如何获得高分成为了每位钓手追求的目标。在这篇攻略中&#xff0c;我们将为您详细介绍如何通过优化鱼竿、管理体力、利用buff和词条以及前期准备等方面来提高您在锦标赛中的…

信号分解 | RLMD(鲁棒性局部均值分解)-Matlab

分解效果 RLMD(鲁棒性局部均值分解) RLMD(鲁棒性局部均值分解)-Matlab 代码实现 % %% 清除所有变量 关闭窗口 clc clear all close all%% 导入数据 % data = xlsread(Data.xlsx);%% 输入信号%% RLMD分解 %参数进行设置 % options.display =

【React】CSS 局部样式

书写 CSS 的时候&#xff0c;如果 CSS 文件名包含 module&#xff0c;那么说明该 CSS 是一个局部 CSS 样式文件&#xff0c;类似于 vue 中的 scoped。 .avatarContainer {width: 40px;height: 40px;border-radius: 50%;background: rgb(213, 226, 226); }import styles from ..…

【Redis 开发】缓存雪崩和缓存击穿

缓存问题 缓存雪崩解决方案 缓存击穿互斥锁逻辑时间基于互斥锁解决缓存击穿问题基于逻辑过期方式解决缓存击穿问题 缓存雪崩 缓存雪崩是指在同一时间段&#xff0c;大量的缓存key同时失效或者Redis服务器宕机&#xff0c;导致大量请求到达数据库&#xff0c;带来巨大压力 解决…

游戏发行困境及OgGame云游戏解决方案简述

随着全球化浪潮的持续推进&#xff0c;中国游戏开发者们不再满足于国内市场的发展&#xff0c;而是开始将目光投向更为广阔的海外市场。这一趋势的崛起背后&#xff0c;是中国企业意识到国际化是其发展的必由之路&#xff0c;也是游戏行业突破国内困境的体现。本文将简要阐述游…

【1731】jsp 房租跟踪监控管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 房租跟踪监控管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysq…

为什么常用氢化物

知识星球&#xff08;星球名&#xff1a;芯片制造与封测社区&#xff09;里的学员问&#xff1a;diffusion工序&#xff0c;所需要的气体种类有哪些&#xff1f; Diffusion是什么工序&#xff1f; "Diffusion"工序是通过热能将掺杂剂原子扩散到硅片中&#xff0c;以形…

C++系列-输入输出

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” C输入和输出 我们都知道C语言的输出是用printf函数来实现的&#xff0c;那么C呢&#xff0c;它的实现逻辑是什么呢&#xff0c;让我们一起来看一下&#xff0c; #include<i…

【代码随想录刷题记录】LeetCode27移除元素

题目地址 1. 思路 1.1 基本思路及代码的初步实现 基本思路大体上和卡尔老师的想法是一致的&#xff0c;详见代码随想录&#xff1a;数组&#xff1a;移除元素&#xff0c;暴力法大家都能想到&#xff0c;我这里写一下算法时间复杂度为 O ( n ) O(n) O(n)时候的思路&#xff…

【深度学习】YOLOv5,烟雾和火焰,目标检测,防火检测,森林火焰检测

文章目录 数据收集和数据标注查看标注好的数据的脚本下载yolov5创建 dataset.yaml训练参数开始训练yolov5n训练训练后的权重下载gradio部署 数据收集和数据标注 搜集数据集2w张。 pip install labelme labelme 然后标注矩形框和类别。 下载数据请看这里&#xff1a; https:…