内存是存储和CPU打交道的数据,在Linux上内存主要分为两部分(内核部分和用户部分)。
read系统调用,并不是把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理设备。
read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。
缓冲区的目的,是为了减少频繁的系统IO调用。
有了缓冲区,操作系统使用read函数把数据从内核缓冲区复制到用户缓冲区,write把数据从用户缓冲区复制到内核缓冲区中。等待缓冲区达到一定数量的时候,再进行IO的调用,提升性能。
至于什么时候读取和存储则由内核来决定,用户程序不需要关心。在linux系统中,系统内核也有个缓冲区叫做内核缓冲区。每个进程有自己独立的缓冲区,叫做进程缓冲区。所以,用户程序的IO读写程序,大多数情况下,并没有进行实际的IO操作,而是在读写自己的进程缓冲区。
用户程序进行IO的读写,基本上会用到系统调用
read&write,read把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区。
典型的Java服务器处理网络请求的过程:
客户端请求:通过网卡,读取客户端的请求的数据,将数据读取到内核缓冲区。
服务端用户程序请求数据:在内核缓冲区的数据在读取到用户缓冲区。
介绍IO模型前需要明白同步/异步、阻塞/非阻塞
同步(Synchronization) / 异步(Asynchronization)
同步和异步都是基于应用程序与操作系统处理 IO 事件所采用的方式,比如:
同步:是应用程序要直接参与 IO 读写的操作。
异步:所有的 IO 读写交给操作系统去处理,应用程序只需要等待通知。
同步方式在处理 IO 事件的时候,必须阻塞在某个方法上面等待我们的 IO 事件完成(阻塞 IO 事件或者通过轮询 IO 事件的方式)。
对于异步来说,所有的 IO 读写都交给了操作系统。这个时候,我们可以去做其他的事情,并不需要去完成真正的 IO 操作,当操作完成 IO 后会给我们的应用程序一个通知。
阻塞(Block) / 非租塞(NonBlock)
阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式,比如当数据没有准备就绪的时候。
阻塞:往往需要等待缓冲区中的数据准备好过后才处理其他的事情,否则一直等待在那里。
非阻塞:当我们的进程访问我们的数据缓冲区的时候,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好,也直接返回。
能够组成的模型分为同步阻塞、同步非阻塞、异步阻塞、异步非阻塞,不存在同步异步、阻塞非阻塞。
对于一次 IO 访问,它会经历两个阶段:等待数据准备就绪 (Waiting for the data to be ready);将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)。
举例来说:
读函数:分为等待系统可读和真正的读。
写函数:分为等待网卡可以写和真正的写。
说明:等待就绪的阻塞是不使用 CPU 的,是在“空等”。而真正的读写操作的阻塞是使用 CPU 的,真正在“干活”,而且这个过程非常快,属于 memory copy,宽带通常在 1GB/s 级别以上,可以理解为基本不耗时。
-阻塞I/O
-非阻塞I/O
-I/O复用
-信号驱动I/O
-异步I/O
非阻塞IO通过进程反复调用IO函数
采用轮询,占用CPU
### I/O复用
主要是select和epoll,对一个IO端口,两次调用,两次返回,能实现对多个IO端口进行监听。
多个连接共用一个等待机制。
首先开启套接口信号驱动IO功能,通过系统调用sigation执行一个信号处理函数(此信号调用直接返回,进程继续工作)。
当数据准备就绪时,生成一个signal信号,通知应用程序来取数据。
告知内核启动某个操作,并让内核在整个操作完成的的那个之后(将数据从内核复制到用户自己的缓冲区),进行通知。
和信号驱动模型的主要区别:信号驱动IO由内核通知我们合适可以开始一个IO操作
在client进行IO操作的时候需要等待,所以是同步的。异步IO模型由内核通知我们IO何时已经完成,client不需要进行IO的处理了,所以是异步的。
一个形象的示例说明:
活动:演唱会
角色1:举办商 售票业务员
角色2:黄牛
角色3:小明
角色4:送票快递员
同步阻塞 :
小明从家里面先到演唱会现场问售票业务员买票,但是票还没出来。三天以后才出来,
小明直接打了个地铺睡在举办商售票大厅,一直等票出来,然后买票。
同步非阻塞 : socket可以设置non-blocking
小明从家里面先到演唱会现场问售票业务员买票,但是票还没出来,然后小明走了,办理其他事情去了,
然后过了2个小时,又去举办商售票大厅买票来了,如果票还没有出来,小明又先去办其他事情了,
重复上面的操作,直到有票可以买。
i/o复用: java selector() => linux epoll_wait() 同步非阻塞I/O
小明想买票看演唱会,都直接给黄牛(selector/epoll)打电话了,说帮我留意买个票,
票买了通知我,我自己去取(当我接到黄牛的电话时,我需要花费整个路成的时间去读这个数据,买拿这个票),
那么票没出来之前,小明完全可以做自己的事情。
信号i/o:
小明想买票看演唱会,给举办商售票业务员说,给给你留个电话,有票了请你给我打个电话通知一下 (是看人家操作系统提不提供这种功能,Linux提供,windows没有这种机制),我自己再来买票
(小明完全可以做自己的事情,但是票还是需要小明自己去拿的)
异步非阻塞i/o: Linux aio_read aio_write
小明想买票看演唱会,给举办商售票业务员说(异步非阻塞i/o)打电话了,给你留个地址,有票了请通知快递员,
把这张票送到这个地址来,当小明听到敲门声,看见快递员,就知道票好了,而且指导票好了的时候,
票已经到他手上了,票不用小明自己去取(应用不用自己再去read数据了)
网络编程:
服务端需要等待客户端连接,服务需要先于客户端进行启动网络编程通信必须需要IP和端口。
服务端流程:
1、服务端需要先启动ServerSocket实例
2、绑定端口
3、等待客户端的连接
4、进行读写操作
5、关闭资源
客户端的流程:
1、创建Socket实例
2、连接服务端Connect(服务端的IP+端口 “localhost/127.0.0.1/192.168.43.11”)
3、进行读写操作
4、关闭资源
/** * desc:服务端 * @user:西财彭于晏 */ public class Server { public static void main(String[] args) { ServerSocket serverSocket = null; try { //创建ServerSocket实例 serverSocket = new ServerSocket(); //绑定端口 serverSocket.bind(new InetSocketAddress(6666)); System.out.println("服务端启动成功啦"); //等待客户端的连接:accept(),获取用户的连接是socket,进行读写操作的是socket,不是ServerSocket Socket socket = serverSocket.accept(); System.out.println("有客户端连接啦"); //读写操作 InputStream inputStream = socket.getInputStream(); byte[] bytes = new byte[100]; int len = 0; while ((len = inputStream.read(bytes)) != -1) { System.out.println("客户端发送消息:"+new String(bytes,0,len)); } System.out.println("服务端关闭啦"); //关闭资源 socket.close(); inputStream.close(); } catch (IOException e) { e.printStackTrace(); } finally { if (serverSocket != null) { try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
/** * desc: 客户端 * @user:西财彭于晏 */ public class Client { public static void main(String[] args) { Socket socket = null; try { //创建socket实例 socket = new Socket(); //连接服务端 :connect socket.connect(new InetSocketAddress("127.0.0.1", 6666)); System.out.println("连接服务端成功"); //读写操作 OutputStream outputStream = socket.getOutputStream(); outputStream.write("杨柯真帅!".getBytes()); System.out.println("客户端已发送数据"); } catch (IOException e) { e.printStackTrace(); } finally { //关闭资源 if (socket != null) { try { System.out.println("关闭资源"); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
运行结果
客户端:
服务端: