五大IO模型
Posted ych9527
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了五大IO模型相关的知识,希望对你有一定的参考价值。
文章目录
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调用是否立即返回即可,没有立即返回说明是阻塞的,直接返回说明是非阻塞的
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这里等待,直到被监视的文件描述符有一个或多个发生了状态改变
实现流程:
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超时时间可以精确到微秒
缺点:
1.select采用轮询遍历,监控的效率会随着文件描述符的增多而下降
2.select所能监控的文件描述符是有上限的(1024),取决于内核的FD_SETSIZE宏值
3.select监控文件描述符的时候,需要将集合拷贝到内核之中,select发现有事件就绪之后(返回值大于0,表示发现),同时需要将事件从内核拷贝到用户空间,效率也会受到印象
4.select在返回的时候,会将未就绪的文件描述符从集合中去除,导致下一次监控的时候,如果还需要监控去掉的文件描述符,就需要重新进行添加
5.select没有直接告诉程序员哪一个文件描述符就绪了,需要程序员在返回的事件集合当中去判断
用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,©,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,©))//为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 5
class 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;//填写ip
if(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';//末尾添加\\0
cout<<"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模型
接口:
特点:
1.poll和select相比,跨平台移植性不如select,与epoll相比,监控效率不如epoll
2.相较于select改进的点:
a.不限制文件描述符的个数了
b.相较于select之前的事件集合的方式,改进成为事件结构。事件结构告诉我们,关心的文件描述符是什么,关心的文件描述符发生事件是什么
代码验证:
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.采用了事件结构的方式,简化了代码的编写
2.不限制文件描述符的个数
3.不需要在二次监控的时候重新添加文件描述符
缺点:
1.采用轮询遍历事件结构数组的方式,随着文件描述符增多,性能下降
2.不支持跨平台
3.也没有告诉用户哪一个具体的文件描述符就绪了,需要程序员信息遍历判断
4.也需要将事件结构拷贝到内核,再从内核拷贝用户空间
3.3epoll模型
3.3.1epoll的使用介绍
目前公认的在linux操作系统下,监控性能最高的
接口:
epoll_create:
epoll_ctl:
epoll_wait:
epoll原理:
使用epoll构建tcp服务器
构建流程:
构建代码:
#pragma once
#include <iostream>
using namespace std;
#include <unistd.h>
#include <sys/epoll.h>
#include <vector>
class EpollSvr
{
public:
EpollSvr()
:epfd(-1)
{}
void EpollIinit()
{
//进行创建,返回的是操作句柄
epfd=epoll_create(10);
if(epfd < 0)
{
cout<<"EpollINIT Error"<<endl;
exit(0);
}
}
void EpollAdd(int fd)
{
//创建事件结构体
struct epoll_event ep;
ep.events=EPOLLIN;//关心的事件(可读)
ep.data.fd=fd;//告诉epoll,关心的文件描述符
int ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ep);//添加关心的文件名描述符fd
if(ret < 0)//添加失败
{
cout<<"epoll add error"<<endl;
exit(1);
}
}
void EpollDelete(int fd)
{
int ret=epoll_ctl(epfd,EPOLL_CTL_DEL,fd,nullptr);//删除文件描述符
if(ret < 0)//删除失败
{
cout<<"epoll delete error"<<endl;
exit(2);
}
}
bool EpollWait(vector<int> &out)
{
struct epoll_event arr[10];//定义事件结构体数组
int size=sizeof(arr)/sizeof(arr[0]);
int ret=epoll_wait(epfd,arr,size,-1);//超时时间设置小于0,是阻塞等待
if(ret < 0)
return false;
if(ret > size)//判断事件是否超过数组大小
ret = size;
for(int i=0;i < ret; i++)
{
out.push_back(arr[i].data.fd);//将就绪的文件描述符进行返回
}
return true;
}
~EpollSvr()
{}
private:
int epfd;
};
#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 5
class 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;//填写ip
if(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';//末尾添加\\0
cout<<"client:"<<buff<<endl;
string str="ser echo:";
str+=buff;
send(sock,str.c_str(),str.size(),0);//不需要+1,\\0是语言的标准,文件不认识
return 0;
}
else
{
//close(sock);
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 "EpollSvr.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();
EpollSvr es;
es.EpollIinit();//初始化
es.EpollAdd(se.GetPid());//将侦听套接字添加进去
while(1)
{
vector<int> arr;
bool ret= es.EpollWait(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)
{
es.EpollAdd(sock);//将获取的链接添加至监控中
cout<<"get a new link"<<endl;
}
}
else
{
int ret=se.Task(arr[i]);
if(ret==-1)//对方已经关掉了
{
es.EpollDelete(arr[i]);//将文件描述符进行删除
cout<<"link end"<<endl;
close(arr[i]);
}
}
}
}
return 0;
}
3.3.2poll对文件描述符就绪事件的触发方式
3.3.2.1水平触发EPOLLLT
满足条件就会一直触发 -> 比如你在打游戏,你的妈妈叫你去吃饭,你没去,她又会过来叫你
EPOLLLT -> epoll的默认工作方式,select和poll都是水平触发方式
可读事件:
只要接收缓冲区当中的数据大于低水位标记(1字节),就会一直触发可读事件,直到接收缓冲区当中没有数据可读(接收缓冲区当中的数据低于低水位标记)
可写事件:
只要发送缓冲区当中的空间大于低水位标记(1字节),就会一直触发可写事件,直到发送缓冲区当中没有空间可写(发送缓冲区当中的空间低于低水位标记)
3.3.2.1边缘触发(边沿触发)EPOLLET
满足条件后只会触发一次 -> 比如你在打游戏,你的爸爸叫你去吃饭,只会叫你一次
EPOLLET -> 只有epoll才拥有
设置:
设置文件描述符对应的事件结构的时候,只需要在事件结构当中的事件变量中按位或上EPOLLET即可
struct epoll_event et;
ev.events = EPOLLIN|EPOLLET;
可读事件:
只有当新的数据到来的时候,才会触发可读,否则通知一次之后,就不再通知了 -> 每次到来一个新的数据,只会通知一次,如果应用程序没有将接收缓冲区的数据读完(没有读完的数据留在缓冲区之中,下次触发就从这里开始),也不会再次通知,直到新的数据到来,才会触发可读事件,因此需要尽量将数据读完
可写事件:
只有当发送缓冲区之中剩余空间从不可写变成可写的时候,才会触发一次可写事件就绪
对于ET模式而言,如果就绪事件产生,一定要把握好机会,对于可读事件,将数据读完,对于可写事件,将数据写完
ET模式结合了循环将数据进行读取和发送,不是频繁的进行通知,因此效率就比较高
使用ET模式对代码进行修改:
构建细节注意点:
1.如何判断数据读完了 :
设size=期望读取的字节、ret为实际读取的字节
ret<size表示缓冲区之中一定没有数据了 -> 读完了
ret==size
此时有可能有数据,有可能没有 ->都需要再次进行读取
再次读取有可能会进入阻塞,因此需要将其更改为非阻塞状态
非阻塞状态下,读空的时候,会报错并且返回(EAGAIN/EWOULDBLOCK),因此需要对异常进行特殊的处理
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.循环处理中,如果是阻塞的发送,那么最后一次处理会陷入阻塞的状态,因此需要改为非阻塞的状态
5.非阻塞状态中,返回值小于0的时候,有可能会报异常,因此需要特殊处理
#pragma once
#include <iostream>
using namespace std;
#include <unistd.h>
#include <sys/epoll.h>
#include <vector>
class EpollSvr
{
public:
EpollSvr()
:epfd(-1)
{}
void EpollIinit()
{
//进行创建,返回的是操作句柄
epfd=epoll_create(10);
if(epfd < 0)
{
cout<<"EpollINIT Error"<<endl;
exit(0);
}
}
void EpollAdd(int fd,bool is_et=false)
{
//创建事件结构体
struct epoll_event ep;
if(is_et)//设置为ET模式
ep.events=EPOLLIN|EPOLLET;//关心的事件(可读)
else
ep.events=EPOLLIN;
ep.data.fd=fd;//告诉epoll,关心的文件描述符
int ret=epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ep);//添加关心的文件名描述符fd
if(ret < 0)//添加失败
{
cout<<"epoll add error"<<endl;
exit(1);
}
}
void EpollDelete(int fd)
{
int ret=epoll_ctl(epfd,EPOLL_CTL_DEL,fd,nullptr);//删除文件描述符
if(ret < 0)//删除失败
{
cout<<"epoll delete error"<<endl;
exit(2);
}
}
bool EpollWait(vector<int> &out)
{
struct epoll_event arr[10];//定义事件结构体数组
int size=sizeof(arr)/sizeof(arr[0]);
int ret=epoll_wait(epfd,arr,size,-1);//超时时间设置小于0,是阻塞等待
if(ret < 0)
return false;
if(ret > size)//判断事件是否超过数组大小
ret = size;
for(int i=0;i < ret; i++)
{
out.push_back(arr[i].data.fd);//将就绪的文件描述符进行返回
}
return true;
}
~EpollSvr()
{}
private:
int epfd;
};
#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>
#include <fcntl.h>
#endif
#define BACKLOCK 5
class 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;//填写ip
if(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);
}
}
void SetNonBlock(int sock)//设置为非阻塞状态
{
int flag=fcntl(sock,F_GETFL);//获取当前文件描述符属性
fcntl(sock,F_SETFL,flag | O_NONBLOCK);//将文件描述符设置为非阻塞
}
int Task(int sock)//用链接的套接字去执行任务
{
string message;
while(1)
{
//进行数据的读取
//设置读取缓冲区
char buff[3]={0};
int num=recv(sock,buff,sizeof(buff)-1,0);//实际读取到多少数据
if(num < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)//没有数据了,再进行读取出现报错
break;
cerr<<"recv error"<<endl;
return -1;
}
else if(num==0)//对端关闭了链接
{
return -1;
}
message+=buff;//将上次结果进行累加
if(num < sizeof(buff)-1)//全部读取完毕
break;
}
cout<<"client:"<<message;
//进行数据的发送
//回显字符串
string send_str("echo:");
send_str+=message;
size_t pos=0;//发送位置
size_t send_size=send_str.size();//剩余发送字符的数量
while(1)
{
size_t size=send(sock,send_str.c_str()+pos,send_size,0);//从什么位置发送多少数据
if(size < 0)
{
if(errno == EAGAIN || errno == EWOULDBLOCK)//空间满了,继续发送
continue;
//出错了,对方关闭了链接
cerr<<"send error"<<endl;
return -1;
}
//更新下一次发送数据的位置,和剩余需要发送的数据
pos+=size;
send_size-=size;
if(send_size <= 0)//发送完毕了
break;
}
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 "EpollSvr.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();
EpollSvr es;
es.EpollIinit();//初始化
es.EpollAdd(se.GetPid());//将侦听套接字添加进去
while(1)
{
vector<int> arr;
bool ret= es.EpollWait(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)
{
//se.SetNonBlock(sock);//将套接字设置为非阻塞状态
es.EpollAdd(sock,true);//将获取的链接添加至监控中,并且是ET模式
cout<<"get a new link"<<endl;
}
}
else
{
se.SetNonBlock(arr[i]);//将套接字设置为非阻塞状态
int ret=se.Task(arr[i]);
if(ret==-1)//对方已经关掉了
{
es.EpollDelete(arr[i]);//将文件描述符进行删除
cout<<"link end"<<endl;
close(arr[i]);
}
}
}
}
return 0;
}
以上是关于五大IO模型的主要内容,如果未能解决你的问题,请参考以下文章
java内存流:java.io.ByteArrayInputStreamjava.io.ByteArrayOutputStreamjava.io.CharArrayReaderjava.io(代码片段
java缓冲字符字节输入输出流:java.io.BufferedReaderjava.io.BufferedWriterjava.io.BufferedInputStreamjava.io.(代码片段