这只是一个简单的服务器,
server.h
这个里面就是实现一个Server的类。然后一个run方法
/**
*Server class
*@author lipu123
*@date 2024-5-24
*/
#ifndef SERVER_H
#define SERVER_H
namespace avdance{
class Server{
public:
Server();
~Server();
public:
void run();
};
}
#endif // SERVER_H
server.cpp,这个里面实现了Server中的具体函数
/**
*Server class
*@author lipu123
*@date 2024-5-24
*/
#include "server.h"
#include<iostream>
#include<unistd.h>
namespace avdance{
Server::Server() {
std::cout<<"construct...."<<std::endl;
}
Server::~Server(){
std::cout<<"destruct...."<<std::endl;
}
void Server::run(){
while(1){
std::cout<<"runing..."<<std::endl;
::usleep(1000000);//sleep one second
}
return ;
}
}
mian.cpp
#include<iostream>
#include "server.h"
int main(int argc,char* argv[]) {
avdance::Server* server=new avdance::Server();
if(server){
server->run();
}
return 0;
}
五种 I/O 模型
先花费点时间了解这几种 I/O 模型,有助于后面的理解。
阻塞 I/O 与非阻塞 I/O
阻塞和非阻塞的概念能应用于所有的文件描述符,而不仅仅是socket
。我们称阻塞的文件描述符为阻塞 I/O
,称非阻塞的文件描述符为非阻塞I/O
。
socket
在创建的时候默认是阻塞的,我们可以给socket
系统调用的第 2 个参数传递 SOCK_NONBLOCK
标志,或者通过fcntl
系统调用的F_SETFL
命令将其设置为非阻塞的。
- 针对阻塞 I/O 执行的系统调用可能因为无法立即完成而被操作系统挂起,直到等待的事件发生为止。可能被阻塞的系统调用为
accept
、send
、recv
和connect
; - 针对非阻塞
I/O
执行的系统调用则总是立即返回,而不管事件是否已经发生。如果事件没有立即发生,这些系统调用就返回-1
,和出错的情况一样。此时我们必须根据errno
来区分这两种情况。- 对
accept
、send
、recv
而言,事件未发生时errno
通常被设置为EAGAIN
(意为“再来一次”)或者EWOUDBLOCK
(意为“期望阻塞”); - 对
connect
而言,errno
则被设置成EINPROGRESS
(意为“在处理中”)。
- 对
很显然,只有在事件已经发生的情况下操作非阻塞I/O
(读、写等),才能提高程序的效率。因此,非阻塞I/O
通常要和其他I/O
通知机制一起使用,比如I/O
复用和SIGIO
信号。
笔者认为,我们使用非阻塞
I/O
的最佳情况是:【当我们进行系统调用的时候,它所需要的事件已经发生了】,这样系统调用就不会被阻塞,直接进行处理。比如accept
函数,I/O
复用的好处就是当我们调用accept
函数的时候,已经有客户端在请求连接,这样直接调用accept
,提高运行效率。
I/O 复用
I/O 复用是一种 I/O 通知机制,而且是最常用的通知机制。
I/O 复用是指应用程序通过 I/O 复用函数(select、poll、epoll_wait)向内核注册一组事件,内核通过 I/O 复用函数把其中就绪的事件通知给应用程序。
需要注意的是 I/O 复用函数本身是阻塞的,它们能提高程序效率的原因在于它们具有同时监听多个 I/O 事件的能力。
信号驱动 I/O
为一个目标文件描述符指定宿主进程,那么被指定的宿主进程将捕获到 SIGIO 信号。这样,当文件描述符上有事件发生时,SIGIO 信号的信号处理函数将被触发,我们也就可以在该信号处理函数中对目标文件描述符执行非阻塞 I/O 操作了。
异步 I/O
理论上讲,阻塞 I/O、非阻塞 I/O、信号驱动 I/O 和 I/O 复用都是同步 I/O。
- 同步I/O:内核向应用程序通知的是就绪事件,比如只通知有客户端连接,要求用户代码自动执行I/O操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区);
- 异步I/O:内核向应用程序通知的是完成事件,比如读取客户端的数据之后才通知应用程序,由内核完成I/O操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在“后台”完成的)。
对异步 I/O 而言,用户可以直接对 I/O 执行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及 I/O 操作完成之后内核通知应用程序的方式。异步 I/O 的读写操作总是立即返回,不论 I/O 是否是阻塞的,因为真正的读写操作已经由内核接管。
https://www.cnblogs.com/lafiteee/p/16222757.html
五种 I/O 模型
阻塞IO
阻塞IO指的是用户程序将IO请求提交后,无需等待IO操作的完成,而是可以继续处理别的事情。
所谓阻塞IO,是指以事件触发的机制来对IO操作进行处理。
与多进程和多线程技术相比,阻塞I/O技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。
我们举个例子吧:
假如说我们要去读一个数据,我们要发生系统调用,然后去询问IO是否准备好了,
对于阻塞I/O,如果没有准备好的话,他就会一直等,一直等,直到他准备好了。然后再进行下一步操作。
非阻塞IO
对于非阻塞I/O,发起系统调用后,不是一直等,而是立即返回一个没准备好的I/O。
然后过一段时间这个用户态会再次询问是否准备好了。如果还是没有准备好,还是会立即返回,知道准备好了。
然后在一些空挡里面,这个CPU是可以干其他的事情的。
- 阻塞IO
为了完成IO发起IO调用,若IO事件没有就绪,则一直等待,直到IO就绪,开始数据拷贝。
优点:流程最为简单,使用复杂度非常低。
缺点:IO效率较低,对资料利用不足。
- 非阻塞IO
为了完成IO发起IO调用,若IO事件没有就绪,则调用直接返回(返回后,先进行一些其他任务,之后再重新发起IO调用)。
优点:不等待IO就绪,而是直接立即返回,可以继续对其他描述符进行IO操作,充分利用资源,效率相较于阻塞IO有所提高。
缺点:IO不够实时,且通常需要循环进行操作,增加了一定的复杂度。
异步IO
发起IO调用,让系统进行IO就绪等待以及完成数据拷贝,完成后再通过信号通知进程。
注意:
(1)异步IO会告诉系统,需要对哪个描述符进行什么IO操作,要把IO操作的数据拷贝到哪块缓冲区,以及完成后通过什么信号通知进程。
(2)当进程收到信号,就表示IO已经完成,这时就可以直接对指定缓冲区中的数据进行处理。
优点:对系统资源利用非常高,效率也是最高。
缺点:流程复杂度也是最高。
同步阻塞:
- 同步指的是程序按照顺序执行,每个操作都要等待前一个操作完成才能继续。
- 阻塞表示当程序执行一个IO操作(如读取文件或网络请求)时,线程或进程会等待直到操作完成,期间无法执行其他任务。
例子:传统的阻塞式网络编程。当发送一个网络请求时,线程会阻塞在那里,直到接收到服务器的响应。
同步非阻塞:
- 同步仍然是指按顺序执行,但是在等待IO操作完成时,线程不会被阻塞。
- 非阻塞表示执行IO操作时,线程可以继续执行其他任务而不必等待IO操作完成。
例子:通过轮询方式检查资源是否可用,或者设置超时等待,如果资源还不可用就返回并继续执行其他任务。
异步阻塞:
- 异步指的是不按照严格的顺序执行,而是通过事件驱动或回调等方式处理IO操作。
- 阻塞在这里指的是IO操作执行时仍然会等待,但不会阻塞整个线程或进程,而是使用额外的线程或事件处理机制来处理IO。
例子:某些使用线程池的异步编程框架,当执行一个阻塞的IO操作时,会从线程池中获取一个线程执行IO操作,其他任务则可以继续使用线程池中的其他线程执行。
异步非阻塞:
- 异步仍然是通过事件驱动或回调等方式处理IO操作。
- 非阻塞表示执行IO操作时,不会等待IO操作完成,而是通过回调函数或事件通知的方式处理IO操作的结果。
例子:JavaScript中的异步操作,如使用Promise或回调函数处理异步请求,当发起一个异步网络请求时,JavaScript引擎可以继续执行其他任务,而不必等待网络请求返回结果。