• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

java网络和IOBIO/NIO面试题,从Linux底层告诉你为什么Java这么设计----持续补充

武飞扬头像
殷丿grd_志鹏
帮助1

主文章(我总结的面试题的索引目录—进不去就说明我还没写完)
https://blog.csdn.net/grd_java/article/details/122357831

javaBIO和NIO包括普通IO,都是对底层操作系统内核系统处理的封装,我们写的代码,编译完都是一条条指令,调用操作系统指令,所以我们要先了解系统底层的逻辑和指令,然后看JAVA如何进行面向对象的封装

我们要清晰的听清楚,面试官问的是操作系统层面的IO,还是Java的IO。下面我们都会针对两种情况说明
  1. 我本科是读网络工程专业的,所以对网络方面知识了解较多,可能有些东西我觉得重要,但是面试不会问,所以我指出我面试中遇到的关于网络的问题:ip、tcp、三次握手、四次分手
  2. 大家理解意思即可,不用每个细节都掌握

1. 底层概念(以Linux为例)

1. IO操作的对象?

  1. 基于冯诺依曼体系,CPU进行逻辑控制,主存(内存)进行临时存储,而IO主要表示针对输入输出设备的操作,包括硬盘,网络传输
  2. Linux中,提出一切皆文件的概念,比如将显示器抽象为文件,那么IO是,写,就是写文件的操作,对应的就将内容写到显示器上
  3. 而内存中,文件通过文件描述符(file descriptor)表示。打开文件,socket网络通信,底层API都是通过文件描述符作为参数,完成操作

2. Java IO的执行逻辑

  1. java编译为字节码文件后,由JVM(java 虚拟机)来解析
  2. 而JVM根据字节码文件,调用相应的C、C 的API或系统底层API,完成IO操作

3. 简述一下一切皆文件的具体体现,底层如何操作?

  1. 先看一个例子
    学新通
  1. 第一句,exec 8<> /dev/tcp/www.百度.com; exec命令用于调用并执行指令,8<>是指定文件描述符为8具有输入输出两个方向,<表示输入,>表示输出。/dev/tcp/www.百度.com一切皆文件,也就是一个socket连接。用8这个文件描述符,指向到百度的socket连接
  2. 进入/proc目录后发现有很多目录,/proc目录是内核程序运行中,在文件系统可以看到的东西,而很多的数字目录,就可以联系到程序,也就是说,无论是输入输出设备也好,就连程序也可以映射为文件系统中的目录中去
  3. echo $$,查询当前接收用户输入,解析用户指令的外壳程序(shell)是哪个,可见是11086进程,一切皆文件,也就是/proc/11086文件
  4. 进去后,你会发现11086目录下有一个fd,他就是文件描述符file descriptor的缩写
  1. 解析一下fd目录
    学新通
  1. 0,1,2每个程序都有,java程序也有,0表示标准输入System.in,1表示标准输出System.out,2表示报错System.error
  2. java中由对象表示,操作系统中,由数字,数值,文件描述符来表示
  3. 8,对应我们刚exec 8<> /dev/tcp/www.百度.com;这条命令,8就是描述到百度的socket的文件描述符。
  1. 使用8这个文件描述符,利用http协议,向百度发送东西
    学新通
  1. echo 命令相当于输出语句,可以打印到本地,可以基于TCP
  2. echo -e “\转移字符”,-e表示输出转义字符
  3. echo -e “GET / HTTP/1.0\n” >& 8。>,表示输出。& 8 ,表示重定向到指定文件描述符8,也就是连接到百度的socket连接
  1. 我们上面的socket是8<>,也就是输入和输出,是可以通过 cat <& 8 来接收输入的
    学新通
    学新通

4. 阻塞和非阻塞

  1. 上面的例子很清楚表示了什么是阻塞,我们通过socket连接,连接到百度
  2. 但是这只是建立了连接,我们还没有发送数据,百度不知道我们什么时候发数据
  3. 也就是说,百度建立连接后,我们可能立即发送数据,也可能等一会发送数据
  4. 而百度,它只能一直读,因为它不知道什么时候发数据,但我们又没发,它什么都读不到,只能等待我们发送数据,干不了别的事。这个等待的时间差,就是阻塞,也就是BIO
  5. 非阻塞,就是几乎没有这个等待的时间差,它不等我们,看我们不进行操作,它将干别的事去了。也就是NIO或AIO。

2. 网络

1. TCP/IP参考模型的分层是什么,目的是什么,好处是什么?

  1. 基于OSI 7层参考模型(只作为参考),应用层->表示层->会话层->传输层->网络层->数据链路层->物理层
  2. TCP/IP参考模型没有完全参考OSI模型,它共分4层,由上到下依次是:应用层->传输层->网络层->数据链路层(或网络接口层,和OSI中数据链路层和物理层对应)。
  3. 目的是,分层解耦合,如果不分层,我们开发应用层应用,需要从数据链路层、网络层、传输层这些东西都重头到尾写一次。
  4. 分层的好处就是,我们开发,只需要关注应用层的应用如何开发,而下面三层都在操作系统内核中写好了,我们只需要调用API。我们100个应用,使用的下三层都是直接调操作系统API

2. TCP/IP协议和TCP/IP参考模型的区别?

  1. TCP/IP参考模型,是模型,规定分层
  2. TCP/IP协议,指TCP/IP协议集,包含很多协议,工作在应用层,传输层,网络层。而物理层(主机-网络层),TCP/IP模型没有真正提供实现和协议,只要求第三方实现的物理层能为上层(网络层)提供一个访问接口,使网络层可以利用物理层来传递IP数据包
  3. TCP/IP模型各层主要协议,需要记住,应用层(FTP、HTTP、SNMP),传输层(TCP),网络层(IP)。不知道这些协议是干啥的,就百度一下。
  1. FTP(文件传输协议)、TELNET(远程登录协议)、HTTP(超文本传输协议)建立在TCP(传输控制协议,可靠,三次握手,有链接)之上
  2. SNMP(简单网络管理协议)、DNS(域名系统)建立再UDP(用户数据报协议,不可靠,无连接)之上
  3. IP(网际互连协议)
    学新通

3. 应用层程序如何交互?

  1. 通过应用层协议交互,例如http(超文本传输协议),smtp(邮件传输协议),ssh(安全外壳协议)
  2. 不同工作场景,选择合适协议来工作,发送时通过协议加密,接收时,通过协议解析
  3. 开发时,不考虑底层,我们会指定使用的协议,但是其实是调用底层内核的传输层接口,传输层调用网络层,网络层调用网络接口层(物理层,链路层)

4. 简述TCP协议。

  1. 工作在传输层的协议,传输控制协议,是面向连接的,可靠的传输协议
  2. 可靠是因为,TCP协议采用三次握手和四次分手。中间某一步出错,可以重来。可靠的体现。
  3. 三次握手主要是建立连接的过程,用于建立连接,所谓建立连接,就是双方开辟资源空间(程序,线程),通过这些资源调用传输控制层接口,进行数据通信(发一个,接收一个确认,没有接收到确认会重发,可靠的体现)。
  4. 四次挥手(分手),用于断开链接。

5. 简述三次握手。

  1. 简单来说就是:A说我想要(SYN),B说我听见(ACK)了我也想要(SYN),A再说我听见(ACK)了那就开始(建立连接)吧
  2. 假设有一个客户端,一个服务端,客户端先发送一个由传输层创建的数据包(syn包(seq=j),同步序列编号,简单理解为:对方报文发送的开始序号)给服务端。请求建立连接
  3. 服务端接收到syn包后确认客户端的SYN(ack=j 1),给客户端发送确认包(SYN包(seq=k)即SYN ACK包,ACK表示确认字符表示发来的数据已确认接收无误。),简单理解为,对方发送数据的缓冲区大小
  4. 客户端收到服务器的SYN ACK包向服务器发送确认包ACK(ack=k 1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。简单理解为,能被接收的最大报文段长度MSS
  5. 但是注意,建立连接,不是真的用什么东西连通,而是双方确认后,开辟相应资源空间(线程等),专门为对方准备,不是建立了物理连接,而是双方资源的连接,后续双方通过这份资源进行通信

6. 三次握手的作用

  1. 作用就是确认双方输入输出都是通的,可访问的。
  2. 客户端发送syn包,服务端接收到,发送确认包,syn ack,此时客户端,知晓我发送数据,服务端可以接收到,并给予了回复,站在客户端的角度,输入输出都没问题。
  3. 此时站在服务端,输入没问题,我收到了客户端的syn包,但是输出不确定,我向客户端发送确认包syn ack,需要客户端的回复,告诉我,它确切接收到了我的回应
  4. 当服务端接收到客户端的确认包ACK后,服务端可以确定输入输出都没问题,因为收到了客户端的消息,给予回应,客户端也返回了结果,说明收到了
  5. 输入输出都没问题,可以建立连接

7. 简述四次挥手

  1. 作用就是确认双方都可以释放、回收资源(线程等),断开链接。简单理解就是,A说我完事(FIN)了,B说我听见(ACK)了我看看我完事没,B说我也完事(FIN)了,A说我听见(ACK)了OK彻底结束。
  2. 客户端的应用程序通知TCP数据已经发送完毕时,TCP向服务端发送一个带有FIN附加标记的报文段(FIN表示英文finish)
  3. 服务端收到这个FIN报文段之后,并不立即用FIN报文段回复客户端,而是先向客户端发送一个确认序号ACK,同时通知自己相应的应用程序:对方要求关闭连接(先发送ACK的目的是为了防止在这段时间内,对方重传FIN报文段)
  4. 服务端的应用程序告诉TCP:我要彻底的关闭连接,TCP向客户端送一个FIN报文段
  5. 客户端收到这个FIN报文段后,向服务端发送一个ACK表示连接彻底释放

8. 为什么断开链接要释放资源(socket)

  1. 因为建立连接,实际上是申请了线程等资源,开启了socket连接,每个socket都需要一个端口来区分处理,输入输出时,通过不同的端口号进行区分,可以准确数据通信。端口有限,需要释放资源。
  2. 比如,tomcat吃死80端口,那么100个socket连接80端口,如何区分?其实每个socket都有唯一的ip:port对ip:port的ip:端口的映射,虽然服务端都是80端口,但是客户端ip和端口号是不同的。因此就有了唯一性。
  3. 一个socket连接,对应一组ip-port到ip-port的映射,并且客户端和服务端各有一个,一共4个维度(ip-port)
    学新通
  4. 上图中ESTABLEISHED表示建立连接,可以看到我们框住的socket和它下面的,都是来自192.168.150.14:22这台ip机子的22端口发来的连接
  5. 而socket通过服务端的端口号,61325,61865进行区分不同socket连接,61325端口socket发送接收都是针对61325这个端口,不会跑到别的端口,别的socket连接
  6. 而端口号,取值范围是0~65535,如果断开链接不释放资源(socket程序,socket线程),端口号会被用完。

9. 简述IP,如何通过ip地址传输数据?

  1. ip地址是每个互联网机器的唯一标识,是网络层协议
  2. ip地址由网络位和主机位组成,具体由子网掩码指定,子网掩码有多少个0,就有多少个主机位,例如192.168.1.0/24,代表这是一个C类ip地址,主机位有8位。24代表24个网络位,对应子网掩码为255.255.255.0(11111111.11111111.11111111.00000000),可以用于子网划分
  3. 子网的作用,假设一个公司有10000人,但只有一个ip地址(公有ip),每人一台电脑,那么一台主机需要一个ip地址,怎么办呢?就是子网划分,大家都用私有ip,而对外通信使用公有ip
  4. IP有一个路由表,浩大的网络中,如何能让我们快速寻址(找到对应目标)。TCP/IP出来时,利用下一跳解决了这个问题
  5. 下一跳就是直连的路由,主机A要给主机C发消息(消息中包含源地址和目标地址),首先目的地址和路由表掩码与运算,也就是它会匹配路由表,匹配上就直接发送(比如再同一个局域网),如果匹配不上,交给默认路由0.0.0.0,一般是网关,依次类推,网关下一跳是运营商,然后交给运营商,运营商再交给城域网,这样一跳一跳直到路由表匹配,交给主机B(其实这里大多数是给到网关),一直匹配不上,数据包就会丢弃
  6. 当匹配到后,需要交给数据链路层通过ARP和RARP协议匹配ARP表找主机,找不到再抛出去。一般是公网的一个ip,也就是网关,而真正的用户是这个网关的子网,私有ip,肯定是网关的下一跳。此时通过源地址就可以获取对应的物理地址,然后交给数据链路层通过ARP和RARP协议找到主机。

10、简述ARP和RARP。

  1. 网络层匹配还不够,还需要数据链路层继续匹配,当ip地址匹配,一般是匹配到网关,因为子网划分的原因,大多数用户都是私有ip,只有网关是公网ip
  2. 那么如何将数据包给指定用户呢?就是通过MAC地址匹配(物理地址,物理网卡的一个地址)
  3. ARP协议(地址解析协议),是根据IP地址获取物理地址(MAC地址)的一个TCP/IP协议,它会拿着数据包目标地址,进行ARP广播(广播到局域网每一台主机),主机接收到广播后,如果和自己ip地址对应,它会给予回应,不对应,将丢弃ARP包
  4. RARP协议(反地址解析协议),将MAC地址转换为IP地址

11、简述什么是跨域?

  1. 请求端的域名、协议、端口,有一项,和请求资源的不一样,就是跨域。

12、http和https的区别?

  1. http是超文本传输协议,信息是明文传输,使用80端口,速度比https快。
  2. https是安全的http协议,在http下加入了ssl层,确认网站真实性和保证数据传输安全。需要用到ca证书。信息是加密传输。使用443端口。

13、get和post请求的区别?

  1. get主要用来从服务端获取数据,post主要用来传输数据
  2. get请求可以刷新,post请求如果被刷新,就会被重新提交
  3. get请求可以收藏到书签,post不行。
  4. get请求可以被缓存,post不行。
  5. get请求数据长度有限制(因为放在URL中,一般限制在2048个字节,不同浏览器处理不同,比如Chrome是2M),post没有限制。
  6. get安全性较差(东西都写到URL中了),post安全性高一点,东西都放到body(请求体)中了。

14、VPN是什么?

  1. 虚拟专用网络,在公网上建立的虚拟专用网络。(可以想象成一个只有少部分人知道的密道,比如连接了vpn后,可以把自己看做是对方局域网的设备。)
  2. 可以加密通信,一般公司会用vpn网关,通过对数据包的加密和目标地址转换,实现远程访问。
  3. 可以通过服务器(代理服务器),硬件,软件等多种方式实现

15、DNS是什么?

  1. 域名系统,是互联网上,为了解决网上机器命名的一种系统。
  2. 互联网上访问一台主机,需要ip地址,但因为ip地址难以记忆,所以发明了域名,域名和ip地址对应,方便我们记忆。比如www.百度.com。
  3. 域名和ip地址之间的对应关系,就由域名系统(DNS服务器)负责。
  4. 我们访问某个域名时,DNS服务器就会把域名转换成其对应的ip地址。

16、什么是cookie,干嘛的?

  1. cookie全名http cookie,是一小段存储在浏览器端的文本,大小不超过4kb。发送网络请求时,cookie会在请求头里,一起发送给服务器。是客户端用来存储会话id(session id)的一种机制
  2. session,会话,主要用来验证用户身份的。当浏览器与服务端建立连接后,就产生一次会话。无论有没有登录,都会建立会话。这次会话当中,浏览器请求数据都会在请求头里自动带上会话id(session id)。
  3. cookie主要用来解决http无状态这个问题的,cookie里面的内容,会随着http请求的请求头,自动发送到服务器上。浏览器关闭,cookie也不会消失,而是依然存储到硬盘上。我们可以通过浏览器强制清除cookie缓存,或者设置有效时间。
  4. 一般cookie使用场景和注意事项如下:
  1. 需要在http请求里,自动发送给服务器的数据,可以放在cookie。相反不需要给服务器的就不要放在cookie中。
  2. 需要保密的数据,不应该放在cookie里,因为cookie保存在硬盘,而且可以被第三方拦截。
  3. cookie有容量限制,不可以放太多东西
  4. 尽量不要让前端(JavaScript)写cookie,而是让后端去写。

17、什么是session,工作机制是什么?

  1. session是存储在服务器端的,主要存储用户会话数据,SessionID需要存储在浏览器端,通常存储在cookie中,浏览器发送请求时,需要带着sessionId,这样服务器端就可以根据sessionId找出当前请求的用户是谁了。
  2. session一般会配置一个过期时间,Session过期之后,用户需要重新登录(重新获取sessionID)
  3. 用户没有登录,也会有SessionID,所以重新登录的过程,就是重新申请SessionID

18、JWT是什么?

  1. 全称JSON Web Token,主要用来做校验
  2. JWT由3部分组成:标头(Header)、有效载荷(Payload)和签名(Signature)。在传输的时候,会将JWT的3部分分别进行Base64编码后用.进行连接形成最终传输的字符串。
  1. JWT头:描述JWT元数据的JSON对象,alg属性表示签名使用的算法,默认为HMAC SHA256(写为HS256);typ表示令牌类型,JWT令牌统一写为JWT,最后使用Base64 URL算法将上述JSON对象转换为字符串保存。
{
	"alg":"HS256",
	"typ":"JWT"
}
  1. Payload有效荷载:JWT主体部分,也是JSON对象,包含需要传递的数据,JWT指定七个默认字段供选择,一般我们只用sub字段,表示主题。我们通常使用自定义私有字段,一般存放用户名,是否登录等等一些状态的JSON(JWT默认不加密,可不要把隐秘信息放在这里
//七个默认字段
{
	"iss":发行人
	"exp":到期时间
	"sub":主题
	"aud":用户
	"nbf":在此之前不可用
	"iat":发布时间
	"jti":JWT ID用于标识该JWT
}
//自定义私有字段
{
	"sub":"主题",//主题有人会用
	"password":"123456",
	"admin":true
}
学新通
  1. Signature签名哈希:上面两部分的数据签名,需要使用base64编码后的header和payload数据,通过指定算法生成哈希,以确保数据不会被篡改
  1. 需要先指定密钥(secret,仅保存在服务器,不可以对外公开)
  2. 根据header中指定的签名算法(默认是HS256)生成签名,反正就是利用secret进行签名算法加密。
    学新通
  1. 计算出签名哈希后,JWT头,有效荷载,签名哈希三部分,会组合成一个字符串,每个部分用.进行分隔。

18、JWT每部分的作用?

  1. header和payload可以直接用base64解码出原文,header可以获取哈希签名算法,payload中获取有效数据
  2. signature使用的是只能加密,不能解密的算法,无法解码出原文,作用是校验token有没有被篡改。
  3. 因为我们会保存一个secret密钥在服务端,所以根据header中获取的加密算法,利用该算法和secret对header、payload进行加密,对比加密后的数据和客户端发送过来的是否一致。
  4. 一般secret对于不同加密算法含义也不同,比如MD5算法,secret代表盐值。

19、签名和加密的区别?

  1. 非对称加密中,私钥和公钥是成对使用的。由私钥加密的数据,只能由配对的公钥解密。
  2. 签名就是A用私钥加密后,接收数据的一方用A的公钥可以成功能解密,那边可以证明数据是由A所发;
  3. 也就是说,签名的本质其实也就是加密。只不过由公钥加密换成了私钥加密
  4. 由于有的文件很大,所以一般是将文件的HASH计算出来,然后对HASH进行签名
  5. 校验的时候,则用公钥解密HASH数据,再将文件的HASH计算一遍,与解密后的HASH数据对比,这个过程称之为校验
  6. 加密,就是加密,通过特定算法,对数据加密。

20、对称性加密,非对称加密的区别?

  1. 对称加密:加密和解密使用同一个密钥,不可以用于数字签名和数字鉴别。数据发送方将明文(原始数据)和加密密钥一起经过特殊加密算法处理后变成复杂的加密密文发送出去,收方收到密文后,使用加密密钥及相同算法的逆算法对密文进行解密,使其恢复成可读明文。
  2. 非对称加密:可以用于数字签名和数字鉴别,加密和解密所使用的不是同一个密钥,需要两个密钥来进行加密和解密
  1. 首先甲方,会生成一个公钥和一个私钥,私钥只有自己持有,公钥发送给乙方
  2. 甲方加密和解密都使用私钥,使用私钥加密的数据,只有公钥可以解密
  3. 乙方接收到加密数据后,通过公钥解密,然后可以使用公钥加密数据,公钥加密的数据,只有私钥可以解密。
  4. 甲方可以使用私钥对公钥加密的数据进行解密

21、Session和JWT的区别和优缺点。

  1. 主要区别是用户状态保存的位置,Session是服务端,JWT是客户端。
  2. 所以Session会有分布式Session共享问题(比如nginx进行了负载均衡,会话由两个不同服务器处理,每个服务器Session不一样的问题),需要多机数据共享,可以用redis解决。
  3. JWT的优点
  1. 扩展性好:分布式部署下,不需要解决共享问题,因为存储在客户端。
  2. 无状态:jwt不在服务端存储任何状态,RESTful API原则之一就是无状态,发出请求时,总会返回带有参数的响应,不会产生附加影响。JWT的载荷中可以存储常用信息,用于交换信息,有效的使用JWT,可以降低服务器查询数据库的次数。
  1. JWT的缺点
  1. 安全性:JWT的payload是base64编码,没有加密,因此jwt不能存敏感数据,session是存在服务端,相对安全。
  2. 性能:jwt太长,由于无状态,数据都放在JWT中,编码后jwt非常长,cookie大小限制4k很可能放不下,一般jwt放在localStorage中,每次http请求都会把jwt携带到Header中,有时http的Header比Body还大。而session只是很短的一个sessionID字符串,因此处理起来,jwt的性能开销更大。
  3. 一次性:jwt是一次性的,想要修改里面的内容,必须重新签发一个jwt。

3. IO

说白了,IO调用的都是底层Linux的方法,例如读方法read()需要传参fd(文件描述符),然后对文件进行读操作,如果socket是阻塞的,那么会有一个标识,我们调用read()时,需要人为帮他阻塞,如果是非阻塞的,我们调用read()时,不用帮他阻塞(当然还需要诸如select等内核帮助)。这些事JVM帮我们做了,它会通过BIO和NIO两种技术,帮我们完成阻塞和非阻塞的操作

3.1 IO和BIOLinux底层

1. 为什么IO是计算机的瓶颈

  1. 内存的时间单位是纳秒级别,而I/O(网卡,磁盘寻址,输入输出)是毫秒级别
  2. 1s = 1000ms =1000000vm= 1000000000ns(1毫秒=1000微秒=1000000纳秒)
  3. 所以差一点的内存比好一点的I/O设备快几十万倍,所以IO是计算机瓶颈

2. Socket是阻塞的么?每个accept连接都是新的文件对象么?

  1. Socket既可以阻塞,也可以非阻塞,每个Socket连接,都是一个新的文件对象,产生新的文件描述符
  2. 上面我虽然这么说,但是大家也不知道是不是真的,接下来看一下Linux源码,验证我上面的说法
  1. 通过Linux命令man 2 socket,可以看到,它给出了非阻塞的参数定义,也就是socket可以非阻塞
    学新通
    学新通
  2. 通过命令 man 2 bind,我们看example,实例程序,是C的代码,但大家应该能看懂
  3. 可见它main方法中,上来就调用了socket,返回值是int型的sfd,也就是文件描述符,然后bind绑定端口,listen开启监听(基于文件描述符),当开启监听后,需要accept开启连接,当开启连接后,生成全新文件描述符cfd
  4. 也就是说,每个新的连接,都生成一个新的文件对象
    学新通
    学新通

3. 什么是BIO呢?

  1. 把前面的东西理解了,就完全没问题了。Linux生成新的文件描述符,是因为Linux万物皆文件,而JAVA万物皆对象。
  2. 将Linux文件换成对象,就是一个Socket连接是一个对象,每个accept连接,又是一个新对象。所以每个accept连接互不冲突。
  3. BIO就是阻塞IO,也就是系统底层参数为默认的socket,上面我们说了,socket也可以非阻塞
  4. 也就是说,JAVA也不过是调用系统内核的东西,只不过JAVA还有JVM帮他,所以JAVA可以抽象为面向对象,实际还是JVM再调用Linux底层的东西。
  5. 所以你可以这样和面试官说,JAVA BIO就是通过JVM调用系统内核处理IO的东西,首先,开启一个socket连接,对应一个文件描述符,有了socket连接后,进行bind绑定,绑定到端口,然后开启Listen监听,监听指定文件描述符,然后开启accept连接,开启一个accept连接,会生成一个新的文件描述符,把文件概念换成对象,就是Java的BIO

3.2 Linux底层如何阻塞和非阻塞

4. Linux如何完成阻塞?

  1. 当我们没有指定socket不阻塞后,就是Linux中没有指定SOCK_NONBLOCK参数为非阻塞,也就默认socket是一个阻塞的连接
  2. JAVA中直接使用socket套接字,accept连接时,就是阻塞的。也就是默认的,并不是说Socket是阻塞的。
  3. Linux 内核底层的read读方法需要传参连接的文件描述符fd,缓存区大小,文件总大小,我们知道,socket可以用SOCK_NONBLOCK参数进行设置socket是阻塞的。规定生成阻塞的或非阻塞的socket连接文件描述符,如果是阻塞的read方法执行前,如果没有可以读取的内容,会帮他卡住,也就是阻塞住
    学新通

5. Linux如何完成非阻塞?

  1. 如果调用Linux的read()方法时,文件描述符fd是非阻塞的socket连接
  2. 那么它在read时,如果没有东西,立即返回结果,不进行阻塞
  3. 什么时候我们开心了想起来了,再去read()一下,如果此时有需要读的东西,就读。也就是我不等你,你来了,你等我一会。
  4. 非阻塞就是一种思想,假设它只开一个线程,进行监听,不断监听fd 6,当监听到后,有连接过来,它不断遍历,通过read()方法轮询fd 8 和 fd 9 ,有操作(读,写)过来,就进行处理,没有,就继续轮询,这样就不用阻塞了。
    学新通
  5. 上面这种结构有很大缺陷,假设1000个客户端连接,当有一个连接突然要发送数据,我们可能需要调用999次read()方法,才能去处理
  6. 而最好能够减少和内核交互(read())的次数,并且及时知道有操作过来,把1000次询问,变成1次,可以节省大量资源。如果可以把文件描述符拿到,然后及时知道想读想写就好了。
  7. 所以Linux提供了新的系统调用,select,通过man 2 select命令查看,JAVA模型当中,对应selector选择器对象
  1. 首先它的参数nfds,表示可以传很多个文件描述符,而指针fd_set *readfds表示想读的文件描述符,writefds表示想写的文件描述符,轮询时将相应的文件描述符放进去。我们调用方,通过指针可以获取到想读的,想写的
  2. 它会轮询这些文件描述符,判断它们的操作状态,然后返回文件描述符的个数
    学新通

3.3 JAVA 的 BIO和 NIO

javaBIO和NIO包括普通IO,都是对底层操作系统内核系统处理的封装,我们写的代码,都是一条条指令,调用操作系统指令,所以我们要先了解系统底层的逻辑和指令,然后看JAVA如何进行面向对象的封装

6. BIO一般搭配多线程使用,为什么?

  1. 假设服务端tomcat监听8080端口,文件描述符为6
  2. 当同时两个客户端,通过与服务端Linux内核三次握手,建立TCP连接后
  3. 双方开始分配资源,来处理连接,此时两个客户端各自分配好了资源
  4. 假设服务端只分配一个线程资源,但是连接不会建立一个,假设两个连接都建立完成,文件描述符分别为8和9
  5. 此时,tomcat只有一个线程,阻塞监听fd 8的读,但是此时fd 8 没有立即发送数据,或者发送的数据太大
  6. 那么fd 9就必须一直等着处理fd 8的线程执行结束,再来处理自己的请求。
    学新通
  7. 而有了多线程,第一个线程处理fd 8,第二个线程处理fd 9就好了,但是如果两个客户端都不发数据,两个线程依然会阻塞住
    学新通

7. JAVA NIO如何实现非阻塞?请结合系统底层原理阐述!

  1. BIO和NIO是阻塞IO和非阻塞IO的简写,是JAVA中或者说编程语言中的概念,底层调用的还是socket的read()方法
  2. 但是NIO和BIO不同之处在于,NIO使用了操作系统的select系统调用,它可以同时处理多个文件描述符轮询它们的操作状态,然后返回可以进行操作的文件描述符个数。此方法执行一次轮询一次,本身不阻塞
  1. 需要我们将多个文件描述符传入
  2. select的writefds和readfds两个指针,会保存想要读,写操作的文件描述符,当然还有其它操作
  3. 最终,还会返回需要处理操作的文件描述符的个数
  4. 但是select只是负责轮询,看看你给我的这些文件描述符,谁想写,谁想读,如果有新连接过来,不归我管
  5. 所以我们需要外部调用select的程序,自己实现这样功能,JAVA通过Channel通道来处理整个IO请求,而对select的调用和封装,通过Selector完成
  1. 所以,Java就是抽象了系统底层
  1. 首先启动socket,进行端口绑定,这里java抽象出了通道Channel的概念,我们所有的NIO操作都通过Channel进行,包括启动socket服务端监听
  2. 然后需要指定操作哪些文件描述符了java抽象了selector选择器,我们需要将连接注册到select,也就是文件描述符给select,并且java可以指定选择器轮询时,关注的操作有哪些,通过register()方法注册时可以指定,比如下图OP_ACCEPT就是关注监听连接事件。注意只是注册,并没有调用底层select方法
  3. 通俗点讲,bind相当于启动一个socket连接文件描述符,绑定到Channel通道,然后socket连接文件描述符就需要保存起来,通过ss.refister()保存
  4. 知道有哪些文件描述符,就可以调用select了,通过selector.select()调用操作系统select,然后可以得到select()方法返回值,也就是需要操作的文件描述符的个数,然后判断是否有需要操作的东西,没查到就一直去调用select(),直到有操作
  5. 如果有操作,获取操作集,看看是什么操作,然后执行相应的操作。假设是建立连接操作
  1. 我们知道建立一个socket连接,会生成新的文件描述符,而NIO都在通道中完成操作,所以通过key.channel()可以拿到socket通道,然后调用accept()生成socket连接获取SocketChannel
  2. 而我们前面也说过,socket可以指定阻塞或非阻塞java通过sc.configureBlocking(false)指定当前通道封装的socket连接,是非阻塞状态
  3. 然后我们可以选择把它放到selector中,sc.refister注册进去,关注读操作。
  4. 当前循环结束后,下一次while(true),又会进行selector.select()的调用,此时新加入的文件描述符,也在文件描述符集合中,作为select的参数,调用select,回到上面的第4步,多路复用技术
    学新通
    学新通
    学新通

3.4 epoll

简单聊一下epoll,poll和select差不多,就不说了,epoll很重要,对于使用Netty、redis、nginx的人。它是redis内核,通信效率较高的方法
  1. select的缺陷
  1. 当select完成后,获取需要操作的文件描述符,然后需要对每一个文件描述符进行操作,执行read()方法等等操作
  2. 看java代码就能看出,我们select一次,然后处理一次,再select一次,再处理一次
  3. 每次都需要传输文件描述符(假设1000个),也就是疯狂的内存拷贝过程
  1. Linux命令 man epoll可以发现他有3个系统调用,epoll_create、epoll_ctl、epoll_wait
    学新通
  2. Linux 命令 man 2 epoll_create,可以发现它的返回值,RETURN_VALUE会返回一个文件描述符,而且是epoll自己的,和通信没关系
    学新通
    学新通
  3. Linux 命令 man 2 epoll_ctl,可见它需要传我们上面调用epoll_create()返回的epoll文件描述符epfd和我们实际要操作IO的文件描述符fd,以及event事件。并且,和select不同,这个只需要调用一次。解决了重复调用,内存拷贝的问题
    学新通
  4. Linux命令 man 2 epoll_wait,可见它需要传入epfd,epoll的文件描述符和epoll事件,用来判断我发的事件,有没有
    学新通
  5. 调用过程如下
    学新通
  1. tomcat调用epoll_create 获取 epoll文件描述符,epfd 6
  2. tomcat开启8080端口的监听,fd 8
  3. 调用epoll_ctl(6,8),将fd 8这个tomcat监听,挂载到epoll 6,这里当然得指定直接,假设只指定监听事件
  4. 调用epoll_waite(), 查看epoll事件有没有,有就返回
  5. 此时一个客户端来建立连接,此时epoll监听事件触发成功,epoll_waite()返回,建立成功,fd 9
  6. 调用epoll_ctl(6,9),将连接 fd 9 挂载到epoll 6,此时这里指定的事件应该是,read读取事件
  7. epoll_waite()继续查看epoll事件监听
为什么效率高呢
  1. 底层epoll实例像一颗红黑树,整个epoll实例是6号描述符,也就是红黑树整个是属于6号描述符
  2. 假设上面的例子,监听会放进去一个8,客户端来连接,放进去一个9,内存空间是内核和进程(用户)空间打通
  3. 这种系统调用叫mmap,一种内存映射,也就是两边(tomcat和系统内核)调用同一个内存区域
  4. 有数据之后,还有一个类似list的结构体,比如fd 9有数据可读,就放到list中
  5. epoll_wait最终实际就是访问list里面这个9

3.5 零拷贝

系统底层的拷贝的问题
  1. 假设磁盘有一个文件A,程序B想要读取文件然后通过网络发送出去
  2. 此时程序B会调用系统内核,内核对读取这个文件数据,读到数据先放到内核
  3. 内核再拷贝到程序B的进程空间
  4. 进程空间,再拷贝回内核
  5. 然后内核再搞到网卡,发出去
  6. 可见有一个拷贝的过程,而零拷贝可以解决这个问题,借助与内核提供的sendfile()方法
零拷贝
  1. Linux命令man 2 sendfile(),可见他有out_fd输出文件描述符,in_fd输入文件描述符
    学新通
  2. 当我们想要通过网络,将文件A发送出去,我们无需,请求内核,内核读数据,拷贝到进程空间,进程空间再拷贝回内核,内核再去网卡发送出去
  3. 而是直接通过sendfile()指定文件A为输入,网卡为输出,直接读取到内核,然后通过网卡发出
  4. 少了拷贝的过程,所以叫零拷贝

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgcakic
系列文章
更多 icon
同类精品
更多 icon
继续加载