(ROOT)网络与IO(TCP/IP)

linux文件系统

在 Linux 中:文件打开的时候有一个inode号,读到pagecache页缓存中。

虚拟文件系统进行了一个抽象:一切皆文件

网络与IO知识扫盲(一):Linux虚拟文件系统,文件描述符,IO重定向_寒泉Hq的博客-CSDN博客
磁盘文件、摄像头、打印机,都被看做是文件,基于文件的这种抽象,就可以应用到IO流了。
那么如何区分这些不同的文件?我们引入文件类型:
watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3NpbmF0XzQyNDgzMzQx,size_1,color_FFFFFF,t_70

硬链接

两个变量名指向了同一个物理位置
无论是硬链接还是软连接,如果修改任意一方,另外一个文件也会看到这个变化。
如果删掉了其中一个文件,另外一方还能找到这个文件。相当于只是删除了一个引用。

软链接

软链接是两个独立的文件。修改任意一个文件,另一个文件能看到这个变化。
如果删除原有的,则新的找不到链接了,会标红报错。

关于变量

父进程定义的变量x,不能在子进程取到,除非使用export
这也是为什么在/etc/profile/配环境变量的时候,要添加export的原因

关于代码块、管道开启的子进程

在一个花括号中,所有的指令在同一个进程中执行。

bash是解释执行的,如果看到管道,会将管道左侧的命令独立开启一个子进程,管道右侧的命令独立开启一个子进程,用管道进行对接。而进程的隔离级别很高,所以在新开启的进程中对变量a的修改,不会影响父进程a的值。

$$的优先级高于|$BASHPID的优先级高于|

 

IO

用户程序进行IO的读写,基本上会用到read&write两大系统调用。可能不同操作系统,名称不完全一样,但是功能是一样的。

先强调一个基础知识:read系统调用,并不是把数据直接从物理设备,读数据到内存。write系统调用,也不是直接把数据,写入到物理设备。

read系统调用,是把数据从内核缓冲区复制到进程缓冲区;而write系统调用,是把数据从进程缓冲区复制到内核缓冲区。这个两个系统调用,都不负责数据在内核缓冲区和磁盘之间的交换。底层的读写交换,是由操作系统kernel内核完成的。

内核缓冲与进程缓冲区

缓冲区的目的,是为了减少频繁的系统IO调用。大家都知道,系统调用需要保存之前的进程数据和状态等信息,而结束调用之后回来还需要恢复之前的信息,为了减少这种损耗时间、也损耗性能的系统调用,于是出现了缓冲区。

有了缓冲区,操作系统使用read函数把数据从内核缓冲区复制到进程缓冲区,write把数据从进程缓冲区复制到内核缓冲区中。等待缓冲区达到一定数量的时候,再进行IO的调用,提升性能。至于什么时候读取和存储则由内核来决定,用户程序不需要关心。

在linux系统中,系统内核也有个缓冲区叫做内核缓冲区。每个进程有自己独立的缓冲区,叫做进程缓冲区。

所以,用户程序的IO读写程序,大多数情况下,并没有进行实际的IO操作,而是在读写自己的进程缓冲区。

 

同步阻塞IO(Blocking IO)

最传统的一种IO模型,即在读写数据过程中会发生阻塞现象。当用户线程发出IO请求之后,内核会去查看数据是否就绪,如果没有就绪就会等待数据就绪,而用户线程就会处于阻塞状态,用户线程交出CPU。当数据就绪之后,内核会将数据拷贝到用户线程,并返回结果给用户线程,用户线程才解除 block 状态。典型的阻塞IO模型的例子为: data = socket.read();如果数据没有就绪,就会一直阻塞在 read()方法。

BIO的优点:

程序简单,在阻塞等待数据期间,用户线程挂起。用户线程基本不会占用 CPU 资源。

BIO的缺点:

一般情况下,会为每个连接配套一条独立的线程,或者说一条线程维护一个连接成功的IO流的读写。在并发量小的情况下,这个没有什么问题。但是,当在高并发的场景下,需要大量的线程来维护大量的网络连接,内存、线程切换开销会非常巨大。因此,基本上,BIO模型在高并发场景下是不可用的。

cedf03dba1524cf1919d94c08e5c017a.png

同步非阻塞IO(Non-blocking IO)

 

当用户线程发起一个 read 操作后,并不需要等待,而是马上就得到来一个结果。如果结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦内核中的数据准备好了,并且又再次收到了用户线程的请求,那么它马上就将数据拷贝到了用户线程,然后返回。所以事实上,在非阻塞IO模型中,用户线程需要不断的询问内核数据是否就绪,也就是说非阻塞IO不会交出CPU,而会一直占有CPU。典型的非阻塞IO模型一般如下:while(true){data = socket.read();if(data != error){// 处理数据 break;}}// 非阻塞IO又一个非常严重的问题,在while 循环中需要不断的去询问内核数据是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取数据。

NIO的优点:

每次发起的 IO 系统调用,在内核的等待数据过程中可以立即返回。用户线程不会阻塞,实时性较好。

NIO的缺点:

需要不断的重复发起IO系统调用,这种不断的轮询,将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低。

3a213314c3d943faa04af2c3e507de0b.png

多路复用IO模型(IO Multiplex)(同步(非)阻塞)

 NIO结构:

Channel:通道,连接客户端和服务端的一个管道,管道内可以双向传输数据。

Selector:选择器,可以想象成一个环状传送带,上面可以接入很多管道,selector还可以对每个管道设置感兴趣的颜色(连接(红色),读(黄色),写(蓝色),接收数据)。当Selector开始轮询的时候Selector这个传送带就一直转动,当某个管道被传送到感兴趣事件检查点的时候,selector会检查改管道当前颜色(即事件)之前是否被注册成了感兴趣颜色(事件),如果感兴趣,那么Selector就可以对这个管道做处理了,比如把管道传给别的线程,让别的线程完成读写操作。

ByteBuffer:字节缓冲区,本质上是一个连续的字节数组,Selector感兴趣的事件发生后对管道的读操作所读到的数据都存储在ByteBuffer中,而对管道的写操作也是以ByteBuffer为源头,值得注意的是ByteBuffer可以有多个。想想看,我们使用的所有基本类型都可以转换成byte字节,比如Integer类型占4字节,那么传递数字 1 ByteBuffer底层数组被占用了4个格子。

c73c176ff3a34e37b0d42171ab1c16c7.png

多路复用IO模型是目前使用的比较多的模型。在这种模式中,首先不是进行read系统调动,而是进行select/epoll系统调用。当然,这里有一个前提,需要将目标网络连接,提前注册到select/epoll的可查询socket列表中。然后,才可以开启整个的IO多路复用模型的读流程。

(1)进行select/epoll系统调用,查询可以读的连接。kernel会有一个线程不断去查询所有select的可查询socket列表,只有当socket真正有读写事件时,才真正调用实际的IO读写操作。,当任何一个socket中的数据准备好了,select就会返回(此处参见epoll的ET和LT模式_epoll et lt 怎么设置_FuzhouJiang的博客-CSDN博客。所以不会有太大资源占用

当用户进程调用了select,那么整个线程会被block(阻塞掉)。

(2)用户线程获得了目标连接后,发起read系统调用,用户线程阻塞。内核开始复制数据。它就会将数据从kernel内核缓冲区,拷贝到用户缓冲区(用户内存),然后kernel返回结果。

(3)用户线程才解除block的状态,用户线程终于真正读取到数据,继续执行。

多路复用IO的特点:

I/O多路复用:虽然I/O多路复用的函数也是阻塞的,但是其与以上两种还是有不同的,I/O多路复用是阻塞在select,epoll(epollwait)这样的系统调用之上,而没有阻塞在真正的I/O系统调用如recvfrom之上

epollwait也可以非阻塞,如下:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

timeout 的取值为:

  1. -1 = 永不超时,直到等到一个I/O事件才会返回

  2. 0 = 立即返回,不管本次调用有没有I/O事件,都会立即返回

  3. >0 表示在达到该值后,若仍然没有I/O事件,该函数会超时返回

IO多路复用模型,建立在操作系统kernel内核能够提供的多路分离系统调用select/epoll基础之上的。多路复用IO需要用到两个系统调用(system call), 一个select/epoll查询调用(阻塞),一个是IO的读取调用(非阻塞)。

和NIO模型相似,多路复用IO需要轮询。负责select/epoll查询调用的线程,需要不断的进行select/epoll轮询(此处epoll不再是轮询了,因此不占用cpu,只有poll轮询详见区别),查找出可以进行IO操作的连接。

另外,多路复用IO模型与前面的NIO模型,是有关系的。对于每一个可以查询的socket,一般都设置成为non-blocking模型。只是这一点,对于用户程序是透明的(不感知)。

多路复用IO的优点:

用select/epoll的优势在于,它可以同时处理成千上万个连接(connection)。与一条线程维护一个连接相比,I/O多路复用技术的最大优势是:系统不必创建线程,也不必维护这些线程,从而大大减小了系统的开销。Java的NIO(new IO)技术,使用的就是IO多路复用模型。在linux系统上,使用的是epoll系统调用。

多路复用IO的缺点:

本质上,select/epoll系统调用,属于同步IO,socket监听也是阻塞IO,也可以是非阻塞。都需要在读写事件就绪后,自己负责进行读写,也就是说这个读写过程是阻塞的。

select、poll、epoll区别

mp.weixin.qq.com/s/YdIdoZ_yusVWza1PU7lWaw

流?I/O操作?阻塞?epoll?_epoll_wait 阻塞_一口Linux的博客-CSDN博客

臭名昭著的epoll bug

JDK NIO的BUG,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update18修复了该问题,但是直到JDK1.7版本该问题仍旧存在,只不过该BUG发生概率降低了一些而已,它并没有被根本解决。

Selector BUG出现的原因

若Selector的轮询结果为空,也没有wakeup或新消息处理,则发生空轮询,CPU使用率100%

netty(NIO框架)解决方式

1) 根据该BUG的特征,首先侦测该BUG是否发生;

2) 将问题Selector上注册的Channel转移到新建的Selector上;

3) 老的问题Selector关闭,使用新建的Selector替换。

具体:对Selector的select操作周期进行统计,每完成一次空的select操作进行一次计数,若在某个周期内连续发生N次空轮询,则触发了epoll死循环bug。重建Selector,判断是否是其他线程发起的重建请求,若不是则将原SocketChannel从旧的Selector上去除注册,重新注册到新的Selector上,然后将原来的Selector关闭。

总结

NIO的空转bug历史悠久流传广泛,应用服务器的前端框架一般都采取换一个新Selector的方式对此进行处理,屏蔽掉了JDK5/6的问题,但对于此问题来讲,还是尽量将JDK的版本更新到最新,或者使用NIO框架如Netty,Grizzly等进行研发,以免出更多的问题。

信号驱动IO模型(signal driven I/O)

 

如何充分的解除线程的阻塞呢?那就是异步IO模型。

 

 

3ce9858b22f043efa0222383c18e75c1.png

在信号驱动IO模型中,当用户线程发起一个IO请求操作,会给对应的socket注册一个信号函数,然后用户线程会继续执行,当内核数据就绪时会发送一个信号给用户线程,用户线程接收到信号之后,便在信号函数中调用IO读写操作来进行实际的IO请求操作。

4b15cd9b5c7742ad8dbede8b34d2125d.png

异步IO模型(asynchronous I/O)

异步IO模型才是最理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事情。而另一方面,从内核的角度,当它受到一个asynchronout read之后,它会立刻返回,说明read请求已经成功发起来,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就是说用户线程完全不需要实际的整个IO操作是如何进行的,只需要发起一个请求,当接收内核返回的成功信号时表示IO操作已完成,可以直接去使用数据了。也就是说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用IO函数进行实际的读写操作。注意,异步IO是需要操作系统的底层支持,在Java 7中,提供了Asynchronous IO。

1a8dd54d7e044aa19101a3d0ba656a27.png

Java IO包

40d5df2019bb4896bdf17cf96eb379b3.png

 

mmap

零拷贝(Zero-copy)和mmap - 知乎 (zhihu.com)

零拷贝(Zero-copy)和mmap_rocketmq_zh_39446980的博客-CSDN博客

 

971767ef19a202042cdefcd3be6b4fa7.png

什么是 mmap_一缕阳光a的博客-CSDN博客

对于RocketMQ来说这两个步骤使用的是mmap+write,而Kafka则是使用mmap+write持久化数据,发送数据使用sendfile(零拷贝和应用 - 蚂蚁吃豆腐 - 博客园 (cnblogs.com)

rocketmq使用了mmap,没有使用sendfile,但为什么rocketmq不使用sendfile, 因为rocketmq可以处理消息的顺序,消息的过滤,而Kafka不能有这些功能。 kafka中,sendfile压根就没有到达数据应用层,数据只在内核中中,不会进入用户进程的,也就是不会进入java程序,无法对数据进行操作(修改、排序)(RocketMQ原理 - 简书 (jianshu.com)
作者:lesline
链接:https://www.jianshu.com/p/0620ceefaced
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

 

netty

概述

分类

Netty是基于Reactor模型开发的,在netty中可以构建3种模型结构,分别是:

  • 单 Reactor 单线程
  • 单 Reactor 多线程
  • 主从 Reactor 多线程

单 Reactor 单线程

模型

9df7e4e5d8824b4d848d8d68dbe3e11d.png

模型说明

1)Select 是前面 I/O 复用模型介绍的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求

2)Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发

3)如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理

4)如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应

5)Handler 会完成 Read→业务处理→Send 的完整业务流程

原理

只指定一个线程执行客户端连接和读写操作,也就是在一个Reactor中完成,对应在Netty中的实现就是将NioEventLoopGroup线程数设置为1:

NioEventLoopGroup group = new NioEventLoopGroup(1);
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ServerHandlerInitializer());

单 Reactor多线程

模型

16dbc456ff9a4c729e010e461b2b313e.png

模型说明

1)Reactor 对象通过select 监控客户端请求事件, 收到事件后,通过dispatch进行分发

2)如果建立连接请求, 则右Acceptor 通过accept 处理连接请求, 然后创建一个Handler对象处理完成连接后的各种事件

3)如果不是连接请求,则由reactor分发调用连接对应的handler 来处理

4)handler 只负责响应事件,不做具体的业务处理, 通过read 读取数据后,会分发给后面的worker线程池的某个线程处理业务

5)worker 线程池会分配独立线程完成真正的业务,并将结果返回给handler

6)handler收到响应后,通过send 将结果返回给client

原理

一个单Reactor中进行客户端连接处理,然后业务处理交给线程池:

NioEventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ServerHandlerInitializer());

主从 Reactor 多线程

模型

0fa4e45c9bc743f7bc813e44803b78be.png

模型说明

1)Reactor主线程 MainReactor 对象通过select 监听连接事件, 收到事件后,通过Acceptor 处理连接事件

2)当 Acceptor 处理连接事件后,MainReactor 将连接分配给SubReactor

3)subreactor 将连接加入到连接队列进行监听,并创建handler进行各种事件处理

4)当有新事件发生时, subreactor 就会调用对应的handler处理

5)handler 通过read 读取数据,分发给后面的worker 线程处理

6)worker 线程池分配独立的worker 线程进行业务处理,并返回结果

7)handler 收到响应的结果后,再通过send 将结果返回给client

8)Reactor 主线程可以对应多个Reactor 子线程, 即MainRecator 可以关联多个SubReactor

原理

主从多线程模型是有多个Reactor,也就是存在多个selector,所以我们定义一个bossGroup和一个workGroup

NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ServerHandlerInitializer());

TCP/IP网络模型

 网络模型详解_3389 time_wait-CSDN博客

 

get和post

 

为何GET只发一次TCP连接,POST发两次TCP连接_http post发两个包-CSDN博客

http状态码

HTTP状态码_http 417-CSDN博客

http状态码可以让我很方便的了解到请求的所在状态,当然其也是大厂笔试的必考题。

所以很有必要总结一下,对今后的学习也是很有帮助的。

HTTP状态码总的分为五类:

1开头:信息状态码

2开头:成功状态码

3开头:重定向状态码

4开头:客户端错误状态码

5开头:服务端错误状态码

 1XX:信息状态码

状态码

含义

描述

100

继续

初始的请求已经接受,请客户端继续发送剩余部分

101

切换协议

请求这要求服务器切换协议,服务器已确定切换

 2XX:成功状态码

状态码

含义

描述

200

成功

服务器已成功处理了请求

201

已创建

请求成功并且服务器创建了新的资源

202

已接受

服务器已接受请求,但尚未处理

203

非授权信息

服务器已成功处理请求,但返回的信息可能来自另一个来源

204

无内容

服务器成功处理了请求,但没有返回任何内容

205

重置内容

服务器处理成功,用户终端应重置文档视图

206

部分内容

服务器成功处理了部分GET请求

3XX:重定向状态码

状态码

含义

描述

300

多种选择

针对请求,服务器可执行多种操作

301

永久移动

请求的页面已永久跳转到新的url

302

临时移动

服务器目前从不同位置的网页响应请求,但请求仍继续使用原有位置来进行以后的请求

303

查看其他位置

请求者应当对不同的位置使用单独的GET请求来检索响应时,服务器返回此代码

304

未修改

自从上次请求后,请求的网页未修改过

305

使用代理

请求者只能使用代理访问请求的网页

307

临时重定向

服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来进行以后的请求

4XX:客户端错误状态码

状态码

含义

描述

400

错误请求

服务器不理解请求的语法

401

未授权

请求要求用户的身份演验证

403

禁止

服务器拒绝请求

404

未找到

服务器找不到请求的页面

405

方法禁用

禁用请求中指定的方法

406

不接受

无法使用请求的内容特性响应请求的页面

407

需要代理授权

请求需要代理的身份认证

408

请求超时

服务器等候请求时发生超时

409

冲突

服务器在完成请求时发生冲突

410

已删除

客户端请求的资源已经不存在

411

需要有效长度

服务器不接受不含有效长度表头字段的请求

412

未满足前提条件

服务器未满足请求者在请求中设置的其中一个前提条件

413

请求实体过大

由于请求实体过大,服务器无法处理,因此拒绝请求

414

请求url过长

请求的url过长,服务器无法处理

415

不支持格式

服务器无法处理请求中附带媒体格式

416

范围无效

客户端请求的范围无效

417

未满足期望

服务器无法满足请求表头字段要求

 

5XX:服务端错误状态码

状态码

含义

描述

500

服务器错误

服务器内部错误,无法完成请求

501

尚未实施

服务器不具备完成请求的功能

502

错误网关

服务器作为网关或代理出现错误

503

服务不可用

服务器目前无法使用

504

网关超时

网关或代理服务器,未及时获取请求

505

不支持版本

服务器不支持请求中使用的HTTP协议版本

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值