APP下载

这几道Java网络程式设计面试题都看不懂 怎么拿高薪

消息来源:baojiabao.com 作者: 发布时间:2024-05-09

报价宝综合消息这几道Java网络程式设计面试题都看不懂 怎么拿高薪

1、tcp和udp的区别

TCP:是面向连线的流传输控制协议,具有高可靠性,确保传输资料的正确性,有验证重发机制,因此不会出现丢失或乱序。

UDP:是无连线的资料报服务,不对资料报进行检查与修改,无须等待对方的应答,会出现分组丢失、重复、乱序,但具有较好的实时性,UDP段结构比TCP的段结构简单,因此网络开销也小。

2、tcp连线建立的时候3次握手,断开连线的4次握手的具体过程

1. 建立连线采用的3次握手协议,具体是指:

l 第一次握手是客户端connect连线到server

l 第二次server accept client的请求之后,向client端传送一个讯息,相当于说我都准备好了,你连线上我了

l 第三次 就是client向server传送的,就是对第二次握手讯息的确认。之后client和server就开始通讯了。

2.断开连线的4次握手,具体如下:

l 断开连线的一端传送close请求是第一次握手

l 另外一端接收到断开连线的请求之后需要对close进行确认,传送一个讯息,这是第二次握手

l 传送了确认讯息之后还要向对端传送close讯息,要关闭对对端的连线,这是第3次握手

l 而在最初发送断开连线的一端接收到讯息之后,进入到一个很重要的状态time_wait状态,这个状态也是面试官经常问道的问题,最后一次握手是最初发送断开连线的一端接收到讯息之后。对讯息的确认。

3、什么是同步?什么是异步?

同步:

如果有多个任务或者事件要发生,这些任务或者事件必须逐个地进行,一个事件或者任务的执行会导致整个流程的暂时等待,这些事件没有办法并发地执行;

异步:

如果有多个任务或者事件发生,这些事件可以并发地执行,一个事件或者任务的执行不会导致整个流程的暂时等待。

这就是同步和异步。

举个简单的例子,假如有一个任务包括两个子任务A和B,对于同步来说,当A在执行的过程中,B只有等待,直至A执行完毕,B才能执行;而对于异步就是A和B可以并发地执行,B不必等待A执行完毕之后再执行,这样就不会由于A的执行导致整个任务的暂时等待。

如果还不理解,可以先看下面这2段程式码:

这段程式码就是典型的同步,在方法function中,fun1在执行的过程中会导致后续的fun2无法执行,fun2必须等待fun1执行完毕才可以执行。

接着看下面这段程式码:

这段程式码是一种典型的异步,fun1的执行不会影响到fun2的执行,并且fun1和fun2的执行不会导致其后续的执行过程处于暂时的等待。

事实上,同步和异步是一个非常广的概念,它们的重点在于多个任务和事件发生时,一个事件的发生或执行是否会导致整个流程的暂时等待。我觉得可以将同步和异步与Java中的synchronized关键字联络起来进行类比。当多个执行绪同时访问一个变数时,每个执行绪访问该变数就是一个事件,对于同步来说,就是这些执行绪必须逐个地来访问该变数,一个执行绪在访问该变数的过程中,其他执行绪必须等待;而对于异步来说,就是多个执行绪不必逐个地访问该变数,可以同时进行访问。

同步和异步可以表现在很多方面,但是记住其关键在于多个任务和事件发生时,一个事件的发生或执行是否会导致整个流程的暂时等待。一般来说,可以通过多执行绪的方式来实现异步,但是千万记住不要将多执行绪和异步画上等号,异步只是宏观上的一个模式,采用多执行绪来实现异步只是一种手段,并且通过多程序的方式也可以实现异步。同步和异步着重点在于多个任务的执行过程中,一个任务的执行是否会导致整个流程的暂时等待

4、.什么是阻塞?什么是非阻塞?

阻塞:

当某个事件或者任务在执行过程中,它发出一个请求操作,但是由于该请求操作需要的条件不满足,那么就会一直在那等待,直至条件满足;

非阻塞:

当某个事件或者任务在执行过程中,它发出一个请求操作,如果该请求操作需要的条件不满足,会立即返回一个标志资讯告知条件不满足,不会一直在那等待。

举个简单的例子:

假如我要读取一个档案中的内容,如果此时档案中没有内容可读,对于同步来说就是会一直在那等待,直至档案中有内容可读;而对于非阻塞来说,就会直接返回一个标志资讯告知档案中暂时无内容可读。

阻塞和非阻塞着重点在于发出一个请求操作时,如果进行操作的条件不满足是否会返会一个标志资讯告知条件不满足。理解阻塞和非阻塞可以同线程阻塞类比地理解,当一个执行绪进行一个请求操作时,如果条件不满足,则会被阻塞,即在那等待条件满足。

5、什么是阻塞IO?什么是非阻塞IO?

在了解阻塞IO和非阻塞IO之前,先看下一个具体的IO操作过程是怎么进行的。

通常来说,IO操作包括:对硬盘的读写、对socket的读写以及外设的读写。

当用户执行绪发起一个IO请求操作(本文以读请求操作为例),核心会去检视要读取的资料是否就绪,对于阻塞IO来说,如果资料没有就绪,则会一直在那等待,直到资料就绪;对于非阻塞IO来说,如果资料没有就绪,则会返回一个标志资讯告知使用者执行绪当前要读的资料没有就绪。当资料就绪之后,便将资料拷贝到使用者执行绪,这样才完成了一个完整的IO读请求操作,也就是说一个完整的IO读请求操作包括两个阶段:

1)检视资料是否就绪;

2)进行资料拷贝(核心将资料拷贝到使用者执行绪)。

那么阻塞(blocking IO)和非阻塞(non-blocking IO)的区别就在于第一个阶段,如果资料没有就绪,在检视资料是否就绪的过程中是一直等待,还是直接返回一个标志资讯。

Java中传统的IO都是阻塞IO,比如通过socket来读资料,呼叫read方法之后,如果资料没有就绪,当前执行绪就会一直阻塞在read方法呼叫那里,直到有资料才返回;

而如果是非阻塞IO的话,当资料没有就绪,read方法应该返回一个标志资讯,告知当前执行绪资料没有就绪,而不是一直在那里等待。

6、什么是同步IO?什么是异步IO?

我们先来看一下同步IO和异步IO的定义,在《Unix网络程式设计》一书中对同步IO和异步IO的定义是这样的:

A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes.An asynchronous I/O operation does not cause the requesting process to be blocked.

从字面的意思可以看出:同步IO即 如果一个执行绪请求进行IO操作,在IO操作完成之前,该执行绪会被阻塞;而异步IO为 如果一个执行绪请求进行IO操作,IO操作不会导致请求执行绪被阻塞。

事实上,同步IO和异步IO模型是针对使用者执行绪和核心的互动来说的:

对于同步IO:当用户发出IO请求操作之后,如果资料没有就绪,需要通过使用者执行绪或者核心不断地去轮询资料是否就绪,当资料就绪时,再将资料从核心拷贝到使用者执行绪;

而异步IO:只有IO请求操作的发出是由使用者执行绪来进行的,IO操作的两个阶段都是由核心自动完成,然后传送通知告知使用者执行绪IO操作已经完成。也就是说在异步IO中,不会对使用者执行绪产生任何阻塞。

这是同步IO和异步IO关键区别所在,同步IO和异步IO的关键区别反映在资料拷贝阶段是由使用者执行绪完成还是核心完成。所以说异步IO必须要有操作系统的底层支援。

注意同步IO和异步IO与阻塞IO和非阻塞IO是不同的两组概念。

阻塞IO和非阻塞IO是反映在当用户请求IO操作时,如果资料没有就绪,是使用者执行绪一直等待资料就绪,还是会收到一个标志资讯这一点上面的。也就是说,阻塞IO和非阻塞IO是反映在IO操作的第一个阶段,在检视资料是否就绪时是如何处理的。

7、 IO模型有几种?分别是什么?

在《Unix网络程式设计》一书中提到了五种IO模型

分别是:阻塞IO、非阻塞IO、多路复用IO、讯号驱动IO以及异步IO。

下面就分别来介绍一下这5种IO模型的异同。

1.阻塞IO模型

最传统的一种IO模型,即在读写资料过程中会发生阻塞现象。

当用户执行绪发出IO请求之后,核心会去检视资料是否就绪,如果没有就绪就会等待资料就绪,而使用者执行绪就会处于阻塞状态,使用者执行绪交出CPU。当资料就绪之后,核心会将资料拷贝到使用者执行绪,并返回结果给使用者执行绪,使用者执行绪才解除block状态。

典型的阻塞IO模型的例子为:

data = socket.openinputstream;

如果资料没有就绪,就会一直阻塞在read方法。

2.非阻塞IO模型

当用户执行绪发起一个read操作后,并不需要等待,而是马上就得到了一个结果。如果结果是一个error时,它就知道资料还没有准备好,于是它可以再次传送read操作。一旦核心中的资料准备好了,并且又再次收到了使用者执行绪的请求,那么它马上就将资料拷贝到了使用者执行绪,然后返回。

所以事实上,在非阻塞IO模型中,使用者执行绪需要不断地询问核心资料是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

典型的非阻塞IO模型一般如下:

但是对于非阻塞IO就有一个非常严重的问题,在while循环中需要不断地去询问核心资料是否就绪,这样会导致CPU占用率非常高,因此一般情况下很少使用while循环这种方式来读取资料。

3.多路复用IO模型

多路复用IO模型是目前使用得比较多的模型。Java NIO实际上就是多路复用IO。

在多路复用IO模型中,会有一个执行绪不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正呼叫实际的IO读写操作。因为在多路复用IO模型中,只需要使用一个执行绪就可以管理多个socket,系统不需要建立新的程序或者执行绪,也不必维护这些执行绪和程序,并且只有在真正有socket读写事件进行时,才会使用IO资源,所以它大大减少了资源占用。

在Java NIO中,是通过selector.select去查询每个通道是否有到达事件,如果没有事件,则一直阻塞在那里,因此这种方式会导致使用者执行绪的阻塞。

也许有朋友会说,我可以采用 多执行绪+ 阻塞IO 达到类似的效果,但是由于在多执行绪 + 阻塞IO 中,每个socket对应一个执行绪,这样会造成很大的资源占用,并且尤其是对于长连线来说,执行绪的资源一直不会释放,如果后面陆续有很多连线的话,就会造成效能上的瓶颈。

而多路复用IO模式,通过一个执行绪就可以管理多个socket,只有当socket真正有读写事件发生才会占用资源来进行实际的读写操作。因此,多路复用IO比较适合连线数比较多的情况。

另外多路复用IO为何比非阻塞IO模型的效率高是因为在非阻塞IO中,不断地询问socket状态时通过使用者执行绪去进行的,而在多路复用IO中,轮询每个socket状态是核心在进行的,这个效率要比使用者执行绪要高的多。

不过要注意的是,多路复用IO模型是通过轮询的方式来检测是否有事件到达,并且对到达的事件逐一进行响应。因此对于多路复用IO模型来说,一旦事件响应体很大,那么就会导致后续的事件迟迟得不到处理,并且会影响新的事件轮询。

4.讯号驱动IO模型

在讯号驱动IO模型中,当用户执行绪发起一个IO请求操作,会给对应的socket注册一个讯号函式,然后使用者执行绪会继续执行,当核心资料就绪时会发送一个讯号给使用者执行绪,使用者执行绪接收到讯号之后,便在讯号函式中呼叫IO读写操作来进行实际的IO请求操作。

5.异步IO模型

异步IO模型才是最理想的IO模型,在异步IO模型中,当用户执行绪发起read操作之后,立刻就可以开始去做其它的事。

而另一方面,从核心的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对使用者执行绪产生任何block。

然后,核心会等待资料准备完成,然后将资料拷贝到使用者执行绪,当这一切都完成之后,核心会给使用者执行绪传送一个讯号,告诉它read操作完成了。

也就说使用者执行绪完全不需要实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收核心返回的成功讯号时表示IO操作已经完成,可以直接去使用资料了。

也就说在异步IO模型中,IO操作的两个阶段都不会阻塞使用者执行绪,这两个阶段都是由核心自动完成,然后传送一个讯号告知使用者执行绪操作已完成。

使用者执行绪中不需要再次呼叫IO函式进行具体的读写。

这点是和讯号驱动模型有所不同的

在讯号驱动模型中,当用户执行绪接收到讯号表示资料已经就绪,然后需要使用者执行绪呼叫IO函式进行实际的读写操作;而在异步IO模型中,收到讯号表示IO操作已经完成,不需要再在使用者执行绪中呼叫iO函式进行实际的读写操作。

注意,异步IO是需要操作系统的底层支援,在Java 7中,提供了Asynchronous IO。也就是java中的AIO NIO2.0

前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是讯号驱动模型,IO操作的第2个阶段都会引起使用者执行绪阻塞,也就是核心进行资料拷贝的过程都会让使用者执行绪阻塞。

在传统的网络服务设计模式中,有两种比较经典的模式:一种是 多执行绪,一种是执行绪池。

对于多执行绪模式,也就说来了client,服务器就会新建一个执行绪来处理该client的读写事件,如下图所示:

这种模式虽然处理起来简单方便,但是由于服务器为每个client的连线都采用一个执行绪去处理,使得资源占用非常大。因此,当连线数量达到上限时,再有使用者请求连线,直接会导致资源瓶颈,严重的可能会直接导致服务器崩溃。

因此,为了解决这种一个执行绪对应一个客户端模式带来的问题,提出了采用执行绪池的方式,也就说建立一个固定大小的执行绪池,来一个客户端,就从执行绪池取一个空闲执行绪来处理,当客户端处理完读写操作之后,就交出对执行绪的占用。因此这样就避免为每一个客户端都要建立执行绪带来的资源浪费,使得执行绪可以重用。

但是执行绪池也有它的弊端,如果连线大多是长连线,因此可能会导致在一段时间内,执行绪池中的执行绪都被占用,那么当再有使用者请求连线时,由于没有可用的空闲执行绪来处理,就会导致客户端连线失败,从而影响使用者体验。因此,执行绪池比较适合大量的短连线应用。

因此便出现了下面的两种高效能IO设计模式:Reactor和Proactor。

在Reactor模式中,会先对每个client注册感兴趣的事件,然后有一个执行绪专门去轮询每个client是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询,如下图所示:

多路复用IO就是采用Reactor模式。注意,上面的图中展示的 是顺序处理每个事件,当然为了提高事件处理速度,可以通过多执行绪或者执行绪池的方式来处理事件。

在Proactor模式中,当检测到有事件发生时,会新起一个异步操作,然后交由核心执行绪去处理,当核心执行绪完成IO操作之后,传送一个通知告知操作已完成,可以得知,异步IO模型采用的就是Proactor模式。

9、Java NIO 中的Buffer是什么?如何使用?

Buffer(缓冲区):

Java NIO Buffers用于和NIO Channel互动。 我们从Channel中读取资料到buffers里,从Buffer把资料写入到Channels;

Buffer本质上就是一块内存区;

一个Buffer有三个属性是必须掌握的,分别是:capacity容量、position位置、limit限制。

Buffer的常见方法

Buffer clear Buffer flip Buffer rewind Buffer position(int newPosition)

Buffer的使用方式/方法介绍:

分配缓冲区(Allocating a Buffer):

ByteBuffer buf = ByteBuffer.allocate(28);//以ByteBuffer为例子

写入资料到缓冲区(Writing Data to a Buffer)

写资料到Buffer有两种方法:

1.从Channel中写资料到Buffer

int bytesRead = inChannel.read(buf); //read into buffer.

2.通过put写资料:

buf.put(127);

10、Nio buffer 的内部结构是什么?

一个 buffer 主要由 position,limit,capacity 三个变数来控制读写的过程。此三个变数的含义见如下表格:

引数

写模式

读模式

position

当前写入的单位资料数量。

当前读取的单位资料位置。

limit

代表最多能写多少单位资料和容量是一样的。

代表最多能读多少单位资料,和之前写入的单位资料量一致。

capacity

buffer 容量

buffer 容量

Buffer 常见方法:

flip: 写模式转换成读模式

rewind :将 position 重置为 0 ,一般用于重复读。

clear :清空 buffer ,准备再次被写入 (position 变成 0 , limit 变成 capacity) 。

compact: 将未读取的资料拷贝到 buffer 的头部位。

mark 、 reset:mark 可以标记一个位置, reset 可以重置到该位置。

Buffer 常见型别: ByteBuffer 、 MappedByteBuffer 、 CharBuffer 、 DoubleBuffer 、 FloatBuffer 、 IntBuffer 、LongBuffer 、 ShortBuffer 。

channel 常见型别 :FileChannel 、 DatagramChannel(UDP) 、 SocketChannel(TCP) 、 ServerSocketChannel(TCP)

2019-01-22 07:37:00

相关文章