您现在的位置是:主页 > news > 哈尔滨专业建网站哪家好/广告联盟平台挂机赚钱

哈尔滨专业建网站哪家好/广告联盟平台挂机赚钱

admin2025/6/13 11:05:25news

简介哈尔滨专业建网站哪家好,广告联盟平台挂机赚钱,如何用phpstudy做网站,百度小程序如何做网站文章目录1.IO过程2.典型IO模型2.1阻塞IO2.2非阻塞IO2.3信号驱动IO2.4异步IO3.多路转接IO模型3.1select模型3.2poll模型3.3epoll模型3.3.1epoll的使用介绍3.3.2poll对文件描述符就绪事件的触发方式3.3.2.1水平触发EPOLLLT(LT模式)3.3.2.1边缘触发(边沿触发)EPOLLET(ET模式)1.IO过…

哈尔滨专业建网站哪家好,广告联盟平台挂机赚钱,如何用phpstudy做网站,百度小程序如何做网站文章目录1.IO过程2.典型IO模型2.1阻塞IO2.2非阻塞IO2.3信号驱动IO2.4异步IO3.多路转接IO模型3.1select模型3.2poll模型3.3epoll模型3.3.1epoll的使用介绍3.3.2poll对文件描述符就绪事件的触发方式3.3.2.1水平触发EPOLLLT(LT模式)3.3.2.1边缘触发(边沿触发)EPOLLET(ET模式)1.IO过…

文章目录

  • 1.IO过程
  • 2.典型IO模型
    • 2.1阻塞IO
    • 2.2非阻塞IO
    • 2.3信号驱动IO
    • 2.4异步IO
  • 3.多路转接IO模型
    • 3.1select模型
    • 3.2poll模型
    • 3.3epoll模型
      • 3.3.1epoll的使用介绍
      • 3.3.2poll对文件描述符就绪事件的触发方式
        • 3.3.2.1水平触发EPOLLLT(LT模式)
        • 3.3.2.1边缘触发(边沿触发)EPOLLET(ET模式)

1.IO过程

任何IO过程,都要包含两个步骤,第一是等待, 第二是拷贝. 而且在实际的应用场景中, 等待消耗的时间往往都远远高于拷贝的时间. 让IO更高效, 最核心的办法就是让等待的时间尽量少

等待IO就绪:相当于想要获取的资源已经准备好了,可以进行操作

读 recv/recvfrom: 等待接收缓冲区当中有数据来(等待IO过程)、接收缓冲区之中有了数据(等待IO就绪)
写 send/sendto:发送缓冲区当中有空间(等待IO过程)、发送缓冲区之中有了空间(等待IO就绪)

拷贝数据到缓冲区中:
读: recv/recvfrom(sockfd,buff,size,0): 将接收缓冲区的数据拷贝到自己准备的空间(buff)之中
写: send/sendto:将应用层数据,拷贝到发送缓冲区之中

2.典型IO模型

2.1阻塞IO

资源不可用的情况下,IO请求一直被阻塞,直到资源可用,就叫做阻塞IO,所有的套接字, 默认都是阻塞方式,阻塞IO是最常见的IO模型

实现流程:
在这里插入图片描述

特点:

1.发起IO调用后,等待的时间取决于内核

2.在等待的过程之中,执行流是被挂起的,对CPU的利用率是非常低

3.在IO就绪到拷贝数据之间,实时性是非常高的(有鱼咬立马起杠提钩)

2.2非阻塞IO

资源不可用的时候,IO请求不会阻塞,而是直接返回,返回当前资源不可用,并且返回EWOULDBLOCK错误码

如果当前资源不可用,IO请求返回之后,表示本次IO请求没有真正完成,所以想要完成IO请求,非阻塞需要搭配循环使用,直至完成IO请求

非阻塞IO往往需要程序员循环的方式反复尝试读写文件描述符, 这个过程称为轮询. 这对CPU来说是较大的浪费, 一般只有特定场景下才使用

实现流程:

在这里插入图片描述

特点:

1.非阻塞IO对CPU的利用率比阻塞IO高

2.代码复杂,流程控制复杂,因为需要循环的缘故

3.需要搭配循环调用,直至IO请求完成

4.IO准备就绪到拷贝数据之间不够实时

while{
非阻塞IO调用  //资源还未准备好,返回错误码
此时资源已经准备就绪,但是还需要指向下面代码,因此实时性比较差
//代码1
//代码2
}

区分阻塞IO和非阻塞IO:只需要关心IO调用是否立即返回即可,没有立即返回说明是阻塞的,直接返回说明是非阻塞的

非阻塞接口函数

  int fcntl(int fildes, int cmd, ...);cmd:获得/设置文件状态标记(cmd=F_GETFL或者F_SETFL)

实现非阻塞函数

void SetNoBlock(int fd)
{int fl=fcntl(fd,F_GETFL);// F_GETFL将当前文件描述符fd的属性取出来(当前属性是一个位图)if(fl < 0){cerr<<"fctnl error"<<endl;exit(0);}fcntl (fd,F_SETFL,fl|O_NONBLOCK);//F_SETFL设置属性,在原有属性上增加非阻塞属性(O_NONBLOCK)
}

2.3信号驱动IO

实现流程:

1.自定义一个IO信号(SIGIO)的处理函数,在处理函数当中发起IO调用

2.内核将数据准备好的时候, 使用SIGIO信号通知应用程序进行IO操作

3.程序收到一个IO信号,内核就会调用自定义的处理函数,内核调用了自定义的处理函数,在自定义处理函数中发起了IO调用

在这里插入图片描述

特点:

1.IO准备就绪,到拷贝数据之间,实时性增强了

2.代码更加复杂,流程控制更加困难,因为引入了信号

3.好处是不用再重复发起IO调用,但是需要在代码当中增加自定义信号的逻辑

2.4异步IO

由内核在数据拷贝完成时, 通知应用程序(而信号驱动是告诉应用程序何时可以开始拷贝数据).

实现流程:

1.自定义信号处理函数 -> 通知数据拷贝完成

2.发起一个异步IO调用,并且异步IO调用直接返回

3.异步IO调用返回之后,执行流可以执行用户代码,由操作系统内核等待IO就绪和数据拷贝

4.数据拷贝完成之后,内核通过信号通知调用者
在这里插入图片描述

3.多路转接IO模型

作用:IO多路转接可以完成大量文件描述符的监控,监控的时间包括:可读事件、可写事件、异常事件

监控文件描述符:那么个文件描述符就绪,就处理哪一个文件描述符

3.1select模型

select系统调用是用来让我们的程序监视多个文件描述符的状态变化的,程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变

select、poll、epoll都只负责一件事情~等,由于等待的是多个文件描述符,而IO主要是两个过程,等待和拷贝。多路转接,一次性等待多个文件描述符,等待的效率提高了,所以IO效率提高了

实现流程:

1.将用户关心的文件描述符拷贝到内核之中,由内核进行监控

2.如果内核监控到某个文件描述符就绪,则返回该描述符

3.用户针对返回的描述符进行操作

接口:
在这里插入图片描述

代码演示1:不带超时时间

int main()7 {8   //设置事件集合9   fd_set readfds;//10 11   FD_ZERO(&readfds);//清空集合12   FD_SET(0,&readfds);//将0号文件描述符添加进去13 14   while(1)15   {16       int ret=select(1,&readfds,NULL,NULL,NULL);//阻塞监控17     if(ret < 0)18     {19       cerr<<"select error"<<endl;//监控出错20       exit(1);21     }22 23     if(FD_ISSET(0,&readfds)!=0)//判断0号文件描述符是否准备就绪24     {25       char buff[100]={0};26       read(0,buff,sizeof(buff)-1);27       printf("echo:%s",buff);28     }29   }30 31   return 0;32 }                   

在这里插入图片描述

代码演示2:带超时时间

int main()
{//设置事件集合fd_set readfds;//FD_ZERO(&readfds);//清空集合FD_SET(0,&readfds);//将0号文件描述符添加进去while(1){//设置超时时间struct timeval tv;tv.tv_sec=3;//超时时间设置为3秒tv.tv_usec=0;int ret=select(1,&readfds,NULL,NULL,&tv);//阻塞监控if(ret < 0){cerr<<"select error"<<endl;//监控出错exit(1);}if(ret==0)//超时了{cout<<"time out"<<endl;if(FD_ISSET(0,&readfds)==0)//超时了,select会去除没有就绪的文件描述符cout<<" 0 fd is not in readfds"<<endl;FD_SET(0,&readfds);//从新设置continue;}if(FD_ISSET(0,&readfds)!=0)//判断0号文件描述符是否准备就绪{char buff[100]={0};read(0,buff,sizeof(buff)-1);printf("echo:%s",buff);}}return 0;
}

在这里插入图片描述

select优缺点:

优点:
1.遵循的是posix标准,即可以跨平台使用
2.select超时时间可以精确到微秒
3.一次可以等待多个文件描述符,提高了等待的效率,即提高了IO的效率

缺点:
1.select的事件集合参数为输入输出型一体,因此每次调用select都需要再次设置事件集合
2.每次调用select都需要将参数拷贝至内核之中,就绪的文件描述符也需要从内核之中拷贝至用户,开销很大
3.select的文件描述符个数有上限,在centos 7 之中为1024,无法进行大量的监控

用select构建tcp服务器

构建流程:

在这里插入图片描述

构建代码:

#pragma once
#include <sys/select.h>
#include <iostream>
using namespace std;
#include <math.h>
#include <vector>class Select
{public:Select(){//初始化事件集合、最大文件描述符FD_ZERO(&readfds);_maxfd=-1;}void AddSet(int fd)//添加需要监控文件描述符{_maxfd=fmax(_maxfd,fd);//更新最大文件描述符FD_SET(fd,&readfds);}void DeleteSet(int fd)//删除监控的文件描述符{FD_CLR(fd,&readfds);//更新最大文件描述符if(fd==_maxfd){for(int i=fd;i>=0;i--)//从后往前寻找第一个就是最大的{if(FD_ISSET(i,&readfds)){_maxfd=i;break;}}}}bool SelectWait(vector<int>& arr)//监控接口{//设置延迟时间struct timeval tv;tv.tv_sec=2;tv.tv_usec=0;fd_set copy = readfds;//保存一份,返回后可以进行恢复int ret=select(_maxfd+1,&copy,NULL,NULL,NULL);if(ret < 0)//监控错误{cerr<<"select error"<<endl;return false;}else if(ret==0)//等待超时了{cout<<"time out"<<endl;return false;}//监控了多个文件描述符,但是不知道那个文件描述符已经就绪了for(int i=0;i<=_maxfd;i++){if(FD_ISSET(i,&copy))//为i的文件描述符,已经就绪了arr.push_back(i);//添加到准备就绪的集合之中}return true;}~Select(){}private:int _maxfd;//最大的文件描述符fd_set readfds;//可读事件集合
};
#ifndef _SERVER_HPP_
#define _SERVER_HPP_#include <iostream>
using namespace std;
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#endif
#define BACKLOCK 5class Server
{private:int port;int lsock;//监听套接字public:Server(int _port):port(_port),lsock(-1){}void Init()//初始化服务器{lsock=socket(AF_INET,SOCK_STREAM,0);if(lsock<0){cerr<<"socket fail"<<endl;exit(2);}struct sockaddr_in local;local.sin_family=AF_INET;//填充协议local.sin_port=htons(port);//填充端口,转换成网网络字节序local.sin_addr.s_addr=INADDR_ANY;//填写ipif(bind(lsock,(struct sockaddr*)&local,sizeof(local)) < 0)//进行绑定{cerr<<"bind error"<<endl;exit(3);}if(listen(lsock,BACKLOCK) < 0){cerr<<"listen error"<<endl;exit(4);}}int Task(int sock)//用链接的套接字去执行任务{char buff[200];size_t size=recv (sock,buff,sizeof(buff)-1,0);if(size>0)//读取到了{buff[size]='\0';//末尾添加\0cout<<"client:"<<buff<<endl;string str="ser echo:";str+=buff;send(sock,str.c_str(),str.size(),0);//不需要+1,\0是语言的标准,文件不认识return 0;}else return -1;//表示对方已经关闭了}int Stat()//用套接字去获取链接{sockaddr_in end_point;//获取客户结构体信息,和udp作用一样socklen_t len=sizeof(end_point);int sock=accept(lsock,(struct sockaddr*)&end_point,&len);if(sock < 0){cout<<"accept error"<<endl;return -1;}return sock;}int GetPid()//返回监听套接字{return lsock;}~Server(){close(lsock);}
};
#include "select.hpp"
#include "server.hpp"int main(int argc,char *argv[])
{if(argc!=2){cout<<"please enter your port"<<endl;exit(0);}Server se(atoi(argv[1]));se.Init();Select st;st.AddSet(se.GetPid());while(1){vector<int> arr;bool ret=st.SelectWait(arr);//进行监控if(ret==false)continue;for(size_t i=0;i < arr.size();i++){if(arr[i]==se.GetPid()){int sock=se.Stat();//获取链接if(sock>0){st.AddSet(sock);//将获取的链接添加至监控中cout<<"get a new link"<<endl;}}else{int ret=se.Task(arr[i]);if(ret==-1)//对方已经关掉了{st.DeleteSet(arr[i]);cout<<"link end"<<endl;}}}}return 0;
}

3.2poll模型

接口:
timeout =0 非阻塞监控
timeout =-1 阻塞监控
timeout>0 超时时间为多少

在这里插入图片描述

代码验证:

int main()
{struct pollfd arr[10];arr[0].fd=0;//关心0号文件描述符arr[0].events=POLLIN;//关心可读事件while(1){int ret=poll(arr,1,2000);//1个有效元素,超时时间为2000毫秒if(ret==0)//等待超时{cout<<"time out"<<endl;continue;}else if(ret < 0){cerr<<"poll error"<<endl;//poll失败exit(0);}else{char buff[100];for(int i=0;i<ret;i++){if(arr[i].events==POLLIN){int size=read(arr[i].fd,buff,sizeof(buff)-1);buff[size-1]=0;//会将换行符也读进来cout<<"echo:"<<buff<<endl;}}}
}return 0;}

在这里插入图片描述

特点:

1.poll和select相比,跨平台移植性不如select,与epoll相比,监控效率不如epoll

2.相较于select改进的点:

a.不限制文件描述符的个数了,由用户自己定义结构体数组的数量
b.相较于select之前的事件集合的方式,改进成为事件结构。事件结构告诉我们,关心的文件描述符是什么,关心的文件描述符发生事件是什么

优缺点:

优点:
1.采用了事件结构的方式,简化了代码的编写
2.不限制文件描述符的个数
3.不需要在二次监控的时候重新添加文件描述符,因为输入型参数和输出型参数分开了

缺点:
1.采用轮询遍历事件结构数组的方式,随着文件描述符增多,性能下降
2.不支持跨平台
3.也需要将事件结构拷贝到内核,再从内核拷贝用户空间

3.3epoll模型

3.3.1epoll的使用介绍

目前公认的在linux操作系统下,监控性能最高的

接口:

epoll_create:
在这里插入图片描述

epoll_ctl:
在这里插入图片描述

epoll_wait:

在这里插入图片描述

epoll原理:

在这里插入图片描述

使用epoll构建tcp服务器

构建流程:
1.epoll服务器初始化:创建epoll模型
2.epoll服务器开始运行:将监听套接字添加进去
3.进行等待,如果监听套接字准备就绪,就获取链接套接字,并且以读的方式添加至epoll模型之中,事件中给每个文件描述符一个独立的空间
4.如果链接套接字准备就绪,判断事件,再进行操作
5.如果链接套接字为可读事件,则进行读取,读满后,修改对应的事件为可写事件
6.写完之后、断开连接

构建代码

sock.h#pragma once
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
using namespace std;class Server
{public:static int Socket(){int sock =socket(AF_INET,SOCK_STREAM,0);if(sock < 0){cerr<<"socket error "<<endl;exit(1);}return sock;}static void Bind(int sock,int port){struct sockaddr_in local;local.sin_family=AF_INET;local.sin_port=htons(port);local.sin_addr.s_addr=htonl(INADDR_ANY);Setsockpot(sock);if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){cerr<<"bind error"<<endl;exit(2);}}static void Listen(int sock){if(listen(sock,5) < 0){cerr<<"listen error"<<endl;exit(3);}}static int Accept(int sock){struct sockaddr_in peer;socklen_t len=sizeof(peer);int fd=accept(sock,(struct sockaddr*)&peer,&len);if(fd < 0)cerr<<"accept error"<<endl;return fd;}static void Setsockpot(int sock)//设置短时间内可以再次绑定端口{int opt=1;setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));}};
EpollServer.h
#pragma once
#include "sock.h"
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>class EpollServer
{private:int _port;int _lsock;int _epfd;public:EpollServer(int port):_port(port),_lsock(-1),_epfd(-1){}~EpollServer(){close(_lsock);close(_epfd);}void EpollServerInit(){//创建套接字_lsock=Server::Socket();Server::Bind(_lsock,_port);Server::Listen(_lsock);//创建epoll模型_epfd=epoll_create(128);if(_epfd < 0){cerr<<"epoll create error"<<endl;exit(0);}}struct Block{int fd;//关心的文件描述符char buff[20];//每个文件描述符对应的缓冲区size_t  pos=0;//读取或者输出到什么位置了Block(int sock):fd(sock){memset(buff,0,sizeof(buff));//清空缓冲区}};void Add(int sock,uint32_t even)//添加事件{//创建事件结构 -> 并且进行填充struct epoll_event events;events.events=even;//对应的事件if(sock==_lsock)//监听套接字不需要文件缓冲区events.data.ptr=nullptr;elseevents.data.ptr=new Block(sock);//对应的缓冲区//添加至epoll模型之中(给对应的红黑树增加节点)epoll_ctl(_epfd,EPOLL_CTL_ADD,sock,&events);}void Delete(int sock)//删除节点{close(sock);//关闭文件描述符//从epoll模型中删除这个节点epoll_ctl(_epfd,EPOLL_CTL_DEL,sock,nullptr);}//事件填充的时候,是从下表0开始填充的void HandelEvent(struct epoll_event *ret,int num){for(int i=0;i < num;i++){uint32_t even =ret[i].events;//获取对应的事件if(ret[i].data.ptr==nullptr)//监听套接字{int sock=Server::Accept(_lsock);//获取链接套接字//将其以可读的方式,添加至epoll模型之中if(sock > 0){cout<<"get a new link"<<endl;Add(sock,EPOLLIN);}}else//获取来的链接{if(even==EPOLLIN)//可读事件{//读取内容至各自的缓冲区Block *bk=(Block*)ret[i].data.ptr;int size=recv(bk->fd,bk->buff+bk->pos,sizeof(bk->buff)-bk->pos,0);if(size > 0)//读到了数据{cout<<"client#"<<bk->buff+bk->pos<<endl;bk->pos+=size;if(bk->pos >= sizeof(bk->buff))//读满了{//输出后,将读事件修改为写事件struct epoll_event temp;bk->pos=0;temp.events=EPOLLOUT;temp.data.ptr=bk;epoll_ctl(_epfd,EPOLL_CTL_MOD,bk->fd,&temp);//修改事件为写事件}}else if(size ==0 )//对方关闭了链接{cout<<"perr  quit  me to"<<endl;Delete(bk->fd);//从红黑树中删除节点delete bk;}else //异常{cerr<< "exception"<<endl;exit(5);}}else if(even==EPOLLOUT)//可写事件{Block *bk=(Block*)ret[i].data.ptr;size_t size=send(bk->fd,bk->buff+bk->pos,sizeof(bk->buff)-bk->pos,0);bk->pos+=size;if(bk->pos >= sizeof(bk->buff))//全部发送过去了{Delete(bk->fd);//关闭链接delete bk;}}}}}void Start(){//将监听套接字添加至epoll模型之中Add(_lsock,EPOLLIN);//监听套接字可读事件struct epoll_event ret[20];//接收事件结构体数组while(1){int num=epoll_wait(_epfd,ret,20,-1);// timeout=-1 阻塞等switch(num){case 0://0超时cerr<<"wait out"<<endl;break;case -1://小于0,一般是-1 出错cerr<<"wait error"<<endl;break;default://否则有事件已经就绪HandelEvent(ret,num);break;}}}};
main.cpp#include "sock.h"
#include "EpollServer.hpp"int main(int argc,char *argv[])
{if(argc!=2){cerr<<"please enter your port"<<endl;exit(0);}int port=atoi(argv[1]);EpollServer *ep=new EpollServer(port);ep->EpollServerInit();ep->Start();return 0;}

实验效果
在这里插入图片描述

3.3.2poll对文件描述符就绪事件的触发方式

3.3.2.1水平触发EPOLLLT(LT模式)

满足条件就会一直触发 -> 比如你在打游戏,你的妈妈叫你去吃饭,你没去,她又会过来叫你
EPOLLLT -> epoll的默认工作方式,select和poll都是水平触发方式

可读事件:

只要接收缓冲区当中的数据大于低水位标记(1字节),就会一直触发可读事件,直到接收缓冲区当中没有数据可读(接收缓冲区当中的数据低于低水位标记)

可写事件:

只要发送缓冲区当中的空间大于低水位标记(1字节),就会一直触发可写事件,直到发送缓冲区当中没有空间可写(发送缓冲区当中的空间低于低水位标记)

3.3.2.1边缘触发(边沿触发)EPOLLET(ET模式)

满足条件后只会触发一次 -> 比如你在打游戏,你的爸爸叫你去吃饭,只会叫你一次

EPOLLET -> 只有epoll才拥有

设置:
设置文件描述符对应的事件结构的时候,只需要在事件结构当中的事件变量中按位或上EPOLLET即可

struct epoll_event et; 
ev.events = EPOLLIN|EPOLLET;

可读事件:

只有当新的数据到来的时候,才会触发可读,否则通知一次之后,就不再通知了 -> 每次到来一个新的数据,只会通知一次,如果应用程序没有将接收缓冲区的数据读完(没有读完的数据留在缓冲区之中,下次触发就从这里开始),也不会再次通知,直到新的数据到来,才会触发可读事件,因此需要尽量将数据读完

可写事件:

只有当发送缓冲区之中剩余空间从不可写变成可写的时候,才会触发一次可写事件就绪

对于ET模式而言,如果就绪事件产生,一定要把握好机会,对于可读事件,将数据读完,对于可写事件,将数据写完
ET模式结合了循环将数据进行读取和发送,不是频繁的进行通知,因此效率就比较高

构建ET细节注意点:

1.如何判断数据读完了 :

设size=期望读取的字节、ret为实际读取的字节
ret<size表示缓冲区之中一定没有数据了 -> 读完了

ret==size
此时有可能有数据,有可能没有 ->都需要再次进行读取
再次读取有可能会进入阻塞,因此需要将其更改为非阻塞状态

2.将数据发送出去:

同样需要构建循环进行发送,当缓冲区没有容量的时候,就循环发送,直至缓冲区有容量

3.将描述符设置为非阻塞接口介绍:

int fcntl(int fd, int cmd, … /* arg */ );
1.fd:要设置的文件描述符
2.cmd:操作方式
F_GETFL获取当前文件描述符的属性
F_SETFL将非阻塞属性设置到文件描述符的属性当中(O_NONBLOCK)

4.代码
1.对侦听套接字进行监控时,采用默认的方式
2.对链接的套接字进行监控时,采用ET模式
3.由于是ET模式,应该通知的时候就需要将所有的数据读取或者发送完毕,因此需要采用循环来处理
4.循环处理中,如果是阻塞的发送,那么最后一次处理会陷入阻塞的状态,因此需要改为非阻塞的状态