IO模型/网络协议

一、文件系统IO

举个例子

在Linux系统中运行java代码

package com.softeem.wolf.IO;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * Created by 苍狼
 * Time on 2022-10-31
 */
public class test {
    public static void main(String[] args) throws IOException {
        File file = new File("test.txt");
        FileOutputStream fos = new FileOutputStream(file);
        String str = "123456789\n";
        byte[] bytes = str.getBytes();
        for (int i = 0; i < 100000; i++) {
            fos.write(bytes);
            fos.flush();
        }
        fos.close();
    }
}

javac编译一下test.java文件, 生成test.class文件

[root@CentOS-7-64 ~]# ll
total 4
-rw-r--r-- 1 root root 498 Nov  1 20:41 test.java
[root@CentOS-7-64 ~]# javac test.java
[root@CentOS-7-64 ~]# ll
total 8
-rw-r--r-- 1 root root 694 Nov  1 20:41 test.class
-rw-r--r-- 1 root root 498 Nov  1 20:41 test.java

然后运行java test, 会生成一个test.txt文件, 这个时候用ll命令可以看到三个文件, test.java, test.class, test.txt, 且这三个文件都有内容.

[root@CentOS-7-64 ~]# java test
[root@CentOS-7-64 ~]# ll
total 988
-rw-r--r-- 1 root root     694 Nov  1 20:41 test.class
-rw-r--r-- 1 root root     498 Nov  1 20:41 test.java
-rw-r--r-- 1 root root 1000000 Nov  1 20:43 test.txt

这个时候立即关掉服务器(记住是关掉服务器不是关掉远程客户端连接, 是立即关闭服务器), 然后再重新启动服务器, 查看这三个文件, 你会发现这个时候test.txt文件内容为空.这是为什么呢?

这里说一下FileOutputStream这个类, 其中flush()并没有实现, 写不写都无所谓. 如果把它换成BufferedOutputStream, (虽然这个类对flush()进行了实现)结果也是一样的.

[root@CentOS-7-64 ~]# ll
total 4
-rw-r--r-- 1 root root   0 Nov  1 20:43 test.class
-rw-r--r-- 1 root root 498 Nov  1 20:43 test.java
-rw-r--r-- 1 root root   0 Nov  1 20:43 test.txt

明明在关掉服务器之前test.txt有数据的啊, 为什么重启一下服务器数据反而为空?

这里再看一个例子

package com.softeem.wolf.IO;

import java.io.*;

/**
 * Created by 苍狼
 * Time on 2022-11-01
 */
public class Test15 {

    public static String fileUrl = "C:\\Users\\Lenovo\\Desktop\\test.txt";
    public static byte[] bytes = "123456789\n".getBytes();

    public static void testRandomAccessFilewrite01() throws Exception {
        RandomAccessFile raf = new RandomAccessFile(fileUrl, "rw");
        for (int i = 0; i<100000; i++){
            raf.write(bytes);
            raf.getChannel().force(true);
        }
        raf.close();
    }

    public static void main(String[] args) throws Exception {
        testRandomAccessFilewrite01();
    }
}

这个时候我们重复上述操作, 发现并不会出现这种问题, 这又是为什么呢?

大家看看这咋图

我们所有对于Linux的操作都是在内核中完成的, 包括Linux命令ll, vim等, 所以我们之前用ll, vim查看文件的时候其实是操作内核查看内存中的数据, 而这时内存中的内容并没有写到硬盘中, 所以才导致我们在运行完java代码之后立即关掉服务器会导致数据丢失. 而第二个例子的代码因为一个方法raf.getChannel().force(true);这个方法表示内核会让每次写到内存中的数据都强制写入硬盘当中, 所以我们运行完了之后立即关掉服务器并不会导致数据丢失, 因为此时数据已经写入了硬盘当中了. 但是因为每一次内存写操作都需要写入硬盘所以运行速度相当之慢, 这就跟操作mysql数据库和redis数据库一样, 内存操作永远比硬盘操作快得多. 所以不同的项目技术选型就尤为重要了.

二、网络IO

2.1、计算机网络模型

TCP/IP 与 OSI 都是为了使网络中的两台计算机能够互相连接并实现通信与回应,但他们最大的不同在于,OSI 是一个理论上的网络通信模型,而 TCP/IP 则是实际上的网络通信标准。

2.1.1、OSI七层模型 

1、物理层:实现计算机节点之间比特流的透明传输,规定传输媒体接口的标准,屏蔽掉具体传输介质和物理设备的差异,使数据链路层不必关心网络的具体传输介质,按照物理层规定的标准传输数据就行

2、数据链路层:通过差错控制、流量控制等方法,使有差错的物理线路变为无差错的数据链路。

数据链路层的几个基本方法:数据封装成桢、透明传输、差错控制、流量控制。

封装成桢:把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。
透明传输:零比特填充、转义字符。
差错控制:接收者检测错误,如果发现差错,丢弃该帧,差错控制方法有 CRC 循环冗余码
流量控制:控制发送的传输速度,使得接收方来得及接收。传输层TCP也有流量控制功能,但TCP是端到端的流量控制,链路层是点到点(比如一个路由器到下一个路由器)

3、网络层:实现网络地址与物理地址的转换,并通过路由选择算法为分组通过通信子网选择最适当的路径

网络层最重要的一个功能就是:路由选择。路由一般包括路由表和路由算法两个方面。每个路由器都必须建立和维护自身的路由表,一种是静态维护,也就是人工设置,适用于小型网络;另一种就是动态维护,是在运行过程中根据网络情况自动地动态维护路由表。

4、传输层:提供源端与目的端之间提供可靠的透明数据传输,传输层协议为不同主机上运行的进程提供逻辑通信。

  • 网络层协议负责的是提供主机间的逻辑通信;
  • 传输层协议负责的是提供进程间的逻辑通信。

5、会话层:是用户应用程序和网络之间的接口,负责在网络中的两节点之间建立、维持、终止通信。

6、表示层:处理用户数据的表示问题,如数据的编码、格式转换、加密和解密、压缩和解压缩。

7、应用层:为用户的应用进程提供网络通信服务,完成和实现用户请求的各种服务。

2.1.2、TCP/IP模型

TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含了一系列构成互联网基础的网络协议,是Internet的核心协议。TCP/IP协议族按照层次由上到下,层层包装。

上图表示了TCP/IP协议中每个层的作用,而TCP/IP协议通信的过程其实就对应着数据入栈与出栈的过程。入栈的过程,数据发送方每层不断地封装首部与尾部,添加一些传输的信息,确保能传输到目的地。出栈的过程,数据接收方每层不断地拆除首部与尾部,得到最终传输的数据。

2.2、应用程序通信的大致过程

正常情况下两个应用之间的通信直观的感受是这样的

真实的详细的通信过程

APP(应用程序) 实现的协议主要是应用层, 表示层, 会话层, 也就是开发者可以自己自定义的内容, 而传输层, 网络层中的协议是通过操作系统内核实现的, 不同的操作系统是有区别的. 这个一般是改不了的, 而数据链路层, 物理层协议是通过网卡等硬件设备来实现的.  

这里APP1(应用程序1)向APP2(应用程序2)传输数据, 先通过APP1将数据传给操作系统内核, 然后操作系统再将数据传给网卡等硬件设备, 通过无线网络(基站)传输(通过ip地址)给APP2中的主机B的网卡等硬件设备. 然后传给操作系统内核, 操作系统内核最后给到APP2, 这个时候用户就可以在APP2看到APP1传来的数据信息了.这是APP1传输给APP2的数据, 同样APP2传输给APP1的数据也是同样的道理.

2.3、传输控制层(TCP/UDP协议)

这里主要分析一下TCP协议, TCP协议是面向连接的可靠的传输控制层协议, 它是以三次握手来实现的(这里的连接可以理解为资源的创建)

三次握手的大致过程

分析

当c(客户端)开始向s(服务端)发送数据包时(第一次握手), 如果s收到了的话, 两端就开辟了资源, 只不过这时的资源状态是不通的. 只要三次握手都能正常执行, 则资源状态就是可以流通的. 当资源正常开辟使用时, APP就可以读取资源中的数据了.

如果最后一次握手失败了, 则资源的状态仍然是不可用的(这个时候资源已经开辟了), 那么如果这个时候c向s发送数据, 则会导致数据包的丢失, 不能发送到s的开辟的资源中, 那么这个时候怎么解决呢?

重复第二次握手, 就是s再向c发送syn+ack, 如果c收到了, 则向s发送ack, 如果第三次握手成功, 则开辟资源的状态就是可以正常操作的. 就可以实现正常的通信了.

这里有人会问, 这个开辟的资源是什么?

其实就是socket.

[root@CentOS-7-64 ~]# netstat -natp
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      906/sshd            
tcp        0      0 192.168.101.102:22      192.168.101.1:62234     ESTABLISHED 1265/sshd: root@pts 
tcp6       0      0 :::33060                :::*                    LISTEN      944/mysqld          
tcp6       0      0 :::3306                 :::*                    LISTEN      944/mysqld          
tcp6       0      0 :::22                   :::*                    LISTEN      906/sshd

看这个State为ESTABLISHED就代表的是资源可以正常执行的状态.而状态为LISTEN则代表目前资源处于监听状态, 不可以正常数据操作的.

2.4、socket通信原理

大家看这张图

在Linux中演示三次握手

命令: tcpdump -nn -i any port 80(抓取端口号为80端口的数据包)

命令: curl www.baidu.com(抓取百度主页的内容)

当监听到了80端口有数据包传输, 则可以看到数据包中的详细内容. 

分析一下抓取的包的内容. 首先前面三行, 代表三次握手的控制包.

注: 凡是有.代表确认上一个信息.

[S]代表syn, 且length为0. 主机(192.168.101.102.39430)向百度(14.215.177.38.80)发送控制包(第一次握手).

[S.]中的S代表syn, .代表ack, length为0. 百度(14.215.177.38.80)向主机(192.168.101.102.39430)发送控制包(第二次握手). 

[.]带表ack, 且length也为0. 主机(192.168.101.102.39430)向百度(14.215.177.38.80)发送控制包(第三次握手).确认收到.

在完成三次握手时, 后面就是进行进行数据传输.

  • 首先客户端向百度服务器的内核发送length为77的请求头, 发送[p.], 这个p代表不要缓冲, 让百度服务器内核立即知道. 
  • 然后百度服务器向客户端发送确认信息[.]
  • 接着百度服务器向客户端发送length为1460大小的数据包, 
  • 客户端回复收到[.]
  • 接着百度服务器又向客户端发送length为1321大小的数据包
  • 客户端回复收到[.]

这里为什么百度服务器向客户端发送数据时第一次为什么大小是1460? 而第二次为1321?

这里解释一下1460怎么来的, 首先在三次握手中规定了数据包的最大传输大小为1460(mss 1460)那第二次1321是因为你用的这个命令curl www.baidu.com 是为了获取百度的主页信息, 而百度的主页信息数据大小就是2781, 第一次百度最大只能传输的数据包大小是1460, 所以一个数据包是传输不完的, 那么第二次还剩下1321, 这也没有超过数据包的最大量, 所以就将剩余的数据传输给客户端.

如果要深究的的话

输入ifconfig可以查看对应的网卡信息, 可以看到这里有一个mtu 1500, 这个代表最大传输单元, 其中其实是包括TCP首部的20和IP首部的20, 减掉40就得到了1460, 代表HTTP数据可以最大传输的大小为1460. 下面的这个图已经有展示了.

最后四个数据包的信息就是四次挥手的过程了, 我就不详细展开说了, 大家应该都可以看得懂. 

[F.]: 客户端开始发送信息给百度服务端, 表示开始断开连接了.

[.]: 表示百度服务端向客户端发起收到数据包.

[FP.]: 百度服务端也开始向客户端发送断开数据包.

[.]: 客户端向百度服务端发起收到数据包.

猜你喜欢

转载自blog.csdn.net/m0_50370837/article/details/127637507