【持续更新】 Java网络编程面试题

1、tcp和udp的区别

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

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

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

建立连接采用的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段代码:

v

void fun1() {

}

 

void fun2() {

}

 

void function(){

    fun1();

    fun2()

    .....

    .....

}

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

接着看下面这段代码:

void fun1() {

}

 

void fun2() {

}

 

void function(){

    new Thread(){

    public void run() {

        fun1();

    }

}.start();

 

new Thread(){

    public void run() {

        fun2();

    }

}.start();

.....

.....

}

这段代码是一种典型的异步,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.read();

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

2.非阻塞IO模型

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

所以事实上,在非阻塞IO模型中,用户线程需要不断地询问内核数据是否就绪,也就说非阻塞IO不会交出CPU,而会一直占用CPU。

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

伪代码

while(true){

  new MyThread(socket)

}

class MyThread{

    data = socket.read();

    if(data!= error){

        处理数据

        break;

    }

但是对于非阻塞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

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

8、 Reactor和Proactor IO设计模式是什么?

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

对于多线程模式,也就说来了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 三个变量来控制读写的过程。此三个变量的含义见如下表格:

在这里插入图片描述

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)

11、Java NIO 中的 Channel是什么?有什么特点?

Channel:

Java NIO中的SocketChannel是一个连接到TCP网络套接字的通道。

可以通过以下2种方式创建SocketChannel:

打开一个SocketChannel并连接到互联网上的某台服务器。
一个新连接到达ServerSocketChannel时,会创建一个SocketChannel。
打开 SocketChannel 下面是SocketChannel的打开方式:

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress(“http://jenkov.com”, 80));

关闭 SocketChannel

当用完SocketChannel之后调用SocketChannel.close()关闭SocketChannel: socketChannel.close();

从 SocketChannel 读取数据

要从SocketChannel中读取数据,调用一个read()的方法之一。

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

非阻塞模式

可以设置 SocketChannel 为非阻塞模式(non-blocking mode).设置之后,就可以在异步模式下调用connect(), read() 和write()了。

如果SocketChannel在非阻塞模式下,此时调用connect(),该方法可能在连接建立之前就返回了。为了确定连接是否建立,可以调用finishConnect()的方法。

像这样:

socketChannel.configureBlocking(false);

socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

 

while(! socketChannel.finishConnect() ){

    //wait, or do something else...

}

Java NIO Channel通道和流非常相似,主要有以下几点区别:

l 通道可以读也可以写,流一般来说是单向的(只能读或者写,所以之前我们用流进行IO操作的时候需要分别创建一个输入流和一个输出流)。

l 通道可以异步读写。

l 通道总是基于缓冲区Buffer来读写。

Java NIO中最重要的几个Channel的实现:

l FileChannel: 用于文件的数据读写

l DatagramChannel: 用于UDP的数据读写

l SocketChannel: 用于TCP的数据读写,一般是客户端实现

l ServerSocketChannel: 允许我们监听TCP链接请求,每个请求会创建会一个SocketChannel,一般是服务器实现

类层次结构

12、Java NIO中的Selector是什么?

Selector(选择器):

Selector 一般称 为选择器 ,当然你也可以翻译为 多路复用器 。

它是Java NIO核心组件中的一个,用于检查一个或多个NIO Channel(通道)的状态是否处于可读、可写。

如此可以实现单线程管理多个channels,也就是可以管理多个网络链接。

使用Selector的好处在于: 使用更少的线程来就可以来处理通道了, 相比使用多个线程,避免了线程上下文切换带来的开销。

Selector(选择器)的使用方法介绍

Selector的创建

Selector selector = Selector.open();

注册Channel到Selector(Channel必须是非阻塞的)

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

SelectionKey介绍

一个SelectionKey键表示了一个特定的通道对象和一个特定的选择器对象之间的注册关系。

从Selector中选择channel(Selecting Channels via a Selector)

选择器维护注册过的通道的集合,并且这种注册关系都被封装在SelectionKey当中.

停止选择的方法

wakeup()方法 和close()方法。

13、简单讲一下文件IO中的Path和Files

文件I/O基石:Path:

创建一个Path

File和Path之间的转换,File和URI之间的转换

获取Path的相关信息

移除Path中的冗余项

Files类:

Files.exists() 检测文件路径是否存在

Files.createFile() 创建文件

Files.createDirectories()和Files.createDirectory()创建文件夹

Files.delete()方法 可以删除一个文件或目录

Files.copy()方法可以吧一个文件从一个地址复制到另一个位置

获取文件属性

遍历一个文件夹

Files.walkFileTree()遍历整个目录

14、select、poll和epoll的区别

在linux 没有实现epoll事件驱动机制之前,我们一般选择用select或者poll等IO多路复用的方法来实现并发服务程序。在大数据、高并发、集群等一些名词唱得火热之年代,select和poll的用武之地越来越有限,风头已经被epoll占尽。

select的缺点:

单个进程能够监视的文件描述符的数量存在最大限制,通常是1024,当然可以更改数量,但由于select采用轮询的方式扫描文件描述符,文件描述符数量越多,性能越差;
在linux内核头文件中,有这样的定义:

#define __FD_SETSIZE 1024

内核 / 用户空间内存拷贝问题,select需要复制大量的句柄数据结构,产生巨大的开销;
select返回的是含有整个句柄的数组,应用程序需要遍历整个数组才能发现哪些句柄发生了事件;
select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。
相比select模型,poll使用链表保存文件描述符,因此没有了监视文件数量的限制,但其他三个缺点依然存在。

拿select模型为例,假设我们的服务器需要支持100万的并发连接,则在__FD_SETSIZE 为1024的情况下,则我们至少需要开辟1k个进程才能实现100万的并发连接。

除了进程间上下文切换的时间消耗外,从内核/用户空间大量的无脑内存拷贝、数组轮询等,是系统难以承受的。

因此,基于select模型的服务器程序,要达到10万级别的并发访问,是一个很难完成的任务。

epoll的实现机制与select/poll机制完全不同,上面所说的 select的缺点在epoll上不复存在。

设想一下如下场景:

有100万个客户端同时与一个服务器进程保持着TCP连接。而每一时刻,通常只有几百上千个TCP连接是活跃的(事实上大部分场景都是这种情况)。如何实现这样的高并发?

在select/poll时代,服务器进程每次都把这100万个连接告诉操作系统(从用户态复制句柄数据结构到内核态),让操作系统内核去查询这些套接字上是否有事件发生,轮询完后,再将句柄数据复制到用户态,让服务器应用程序轮询处理已发生的网络事件,这一过程资源消耗较大,因此,select/poll一般只能处理几千的并发连接。

epoll的设计和实现与select完全不同。

epoll通过在Linux内核中申请一个简易的文件系统

(文件系统一般用什么数据结构实现?B+树)

把原先的select/poll调用分成了3个部分:

1)调用epoll_create()建立一个epoll对象(在epoll文件系统中为这个句柄对象分配资源)

2)调用epoll_ctl向epoll对象中添加这100万个连接的套接字

3)调用epoll_wait收集发生的事件的连接

如此一来,要实现上面说是的场景,只需要在进程启动时建立一个epoll对象,然后在需要的时候向这个epoll对象中添加或者删除连接。同时,epoll_wait的效率也非常高,因为调用epoll_wait时,并没有一股脑的向操作系统复制这100万个连接的句柄数据,内核也不需要去遍历全部的连接。

下面来看看Linux内核具体的epoll机制实现思路。

当某一进程调用epoll_create方法时,Linux内核会创建一个eventpoll结构体,这个结构体中有两个成员与epoll的使用方式密切相关。eventpoll结构体如下所示:

struct eventpoll{    

....    

/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/    

struct rb_root  rbr;

/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/   

struct list_head rdlist;  

  ....

};

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件。这些事件都会挂载在红黑树中,如此,重复添加的事件就可以通过红黑树而高效的识别出来(红黑树的插入时间效率是lgn,其中n为树的高度)。

而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中叫ep_poll_callback,它会将发生的事件添加到rdlist双链表中。

在epoll中,对于每一个事件,都会建立一个epitem结构体,如下所示:

struct epitem{    

struct rb_node  rbn;//红黑树节点    

struct list_head    rdllink;//双向链表节点    

struct epoll_filefd  ffd;  //事件句柄信息    

struct eventpoll *ep;    //指向其所属的eventpoll对象    

struct epoll_event event; //期待发生的事件类型

}

当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的rdlist双链表中是否有epitem元素即可。如果rdlist不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。

通过红黑树和双链表数据结构,并结合操作系统底层回调机制,造就了epoll的高效

epoll的用法

第一步:epoll_create()系统调用。此调用返回一个句柄,之后所有的使用都依靠这个句柄来标识。

第二步:epoll_ctl()系统调用。通过此调用向epoll对象中添加、删除、修改感兴趣的事件,返回0标识成功,返回-1表示失败。

第三部:epoll_wait()系统调用。通过此调用收集收集在epoll监控中已经发生的事件。

15、网络编程中设计并发服务器,使用多进程与多线程 ,请问有什么区别?

1,进程:

子进程是父进程的复制品。

子进程获得父进程数据空间、堆和栈的复制品。
2,线程:

相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

两者都可以提高程序的并发度,提高程序运行效率和响应时间。
线程和进程在使用上各有优缺点:

线程执行开销小,但不利于资源管理和保护;而进程正相反。

同时,线程适合于在SMP机器上运行,而进程则可以跨机器迁移。

SMP的全称是"对称多处理"(Symmetrical Multi-Processing)技术,是指在一个计算机上汇集了一组处理器(多CPU),各CPU之间共享内存子系统以及总线结构。

15、网络编程的一般步骤

对于TCP连接:

1.服务器端

1)创建套接字create;

2)绑定端口号bind;

3)监听连接listen;

4)接受连接请求accept,并返回新的套接字;

5)用新返回的套接字recv/send;

6)关闭套接字。

2.客户端

1)创建套接字create;

2)发起建立连接请求connect;

3)发送/接收数据send/recv;

4)关闭套接字。

TCP总结:

Server端:create – bind – listen– accept– recv/send– close

Client端:create——- conncet——send/recv——close.

对于UDP连接:

1.服务器端:

1)创建套接字create;

2)绑定端口号bind;

3)接收/发送消息recvfrom/sendto;

4)关闭套接字。

2.客户端:

1)创建套接字create;

2)发送/接收消息sendto/recvfrom;

3)关闭套接字.

UDP总结:

Server端:create—-bind —-recvfrom/sendto—-close

Client端:create—- sendto/recvfrom—-close.

函数原型int recv( In SOCKET s, Out char *buf, In int len, In int flags);

16、TCP的全称是?

    Transfer Control Protocol。

17、UDP的全称是?

    User Datagram Protocol。

18、请说出TCP和UDP的区别?

TCP:

一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议 。

特点:

面向连接;

点到点的通信;

高可靠性;

占用系统资源多、效率低;

UDP:

一种无连接的、提供面向事务的简单不可靠信息传送服务的传输层通信协议。

特点:

非面向连接

传输不可靠,可能丢失

发送不管对方是否准备好,接收方收到也不确认

可以广播发送

非常简单的协议,开销小

TCP—传输控制协议,提供的是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。

UDP—用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快

19、TCP为什么不是两次连接?而是三次握手?

如果A与B两个进程通信,如果仅是两次连接。

可能出现的一种情况

就是:A发送完请求报文以后,由于网络情况不好,出现了网络拥塞,即B延时很长时间后收到报文,即此时A将此报文认定为失效的报文。

B收到报文后,会向A发起连接。此时两次握手完毕

B会认为已经建立了连接可以通信,B会一直等到A发送的连接请求

而A对失效的报文回复自然不会处理。

因此会陷入B忙等的僵局,造成资源的浪费。

20、说明socket是什么?

从上图可以看到:底层的东西已经被内核实现了,即我们通常意义上的内核协议栈(传输层,网络层,链路层)

最上面的Application(应用层)是我们用户所要实现的,它是属于用户进程的一部分,工作在用户空间,那么用户空间的程序要想访问内核,使用内核的服务,就需要一个接口,去访问所需要的服务

对于网络编程来说,这个接口就是套接口(Socket)。

   Socket:可以看作用户进程和内核网络协议栈编程(交互)接口

   Socket:不仅可以在同一台主机上进行通信,也可以在网络上不同的主机间进行通信,也可以异构(软硬件平台不同)进行通信(手机qq和PC机上的qq进行通信,手机的系统是ARM,而PC机是x86)

21、为什么需要端口?端口是真实存在的还是虚拟的概念?

IP地址用来标志一台计算机,但是一台计算机上可能提供多种网络应用程序,使用端口来区分这些应用程序。

端口是虚拟的概念,并不是说在主机上真的有若干个端口。通过端口,可以在一个主机上运行多个网络应用程序。

端口范围0—65535,16位整数。

22、Java中,端口使用两个字节表示,可以表示多少个端口? UDP和TCP端口是各自独立的吗?

端口范围0—65535,16位整数。

由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立,如TCP有一个255号端口,UDP也可以有一个255号端口,二者并不冲突。

23、URL类有什么作用?

URL:Uniform Resource Locator,统一资源定位器;俗称“网址”,如:

"http://www.baidu.com:80/index.html#aa?cansu=bjsxt“

由4部分组成:

l 协议: http;

l 存放资源的主机域名:www.baidu.com;

l 端口号:80;

l 资源文件名: index.html#aa?cansu=bjsxt;

URL是指向互联网“资源”的指针。资源可以是简单的文件或目录,也可以是对更为复杂的对象的引用,例如对数据库或搜索引擎的查询。

24、基于TCP的Socket网络编程的主要步骤是什么?

基于TCP协议的Socket编程的主要步骤

服务器端(server):

  1. 构建一个ServerSocket实例,指定本地的端口。这个socket就是用来监听指定端口的连接请求的。

2.重复如下几个步骤:

a. 调用socket的accept()方法来获得下面客户端的连接请求。通过accept()方法返回的socket实例,建立了一个和客户端的新连接。

b.通过这个返回的socket实例获取InputStream和OutputStream,可以通过这两个stream来分别读和写数据。

c.结束的时候调用socket实例的close()方法关闭socket连接。

客户端(client):

1.构建Socket实例,通过指定的远程服务器地址和端口来建立连接。

2.通过Socket实例包含的InputStream和OutputStream来进行数据的读写。

3.操作结束后调用socket实例的close方法,关闭。

25、【上机】写出建立TCP服务器ServerSocket的代码。并说明accept方法有什么特点?

//服务器监听请求;

ServerSocket ss=new ServerSocket(9999);

//接受请求:创建了socket;

Socket socket=ss.accept();

26、【上机】写出建立TCP客户端Socket的代码。并说明建立Socket后,通过什么方法Socket获得流对象?

/

/客户端向服务器端发送请求;

Socket socket=new Socket("127.0.0.1",9999);

//建好连接后,开始传输数据;

OutputStream os=socket.getOutputStream();

27、基于UDP的Socket网络编程的主要步骤是什么?

基于UDP协议的Socket编程的主要步骤

服务器端(server):

  1. 构造DatagramSocket实例,指定本地端口。

  2. 通过DatagramSocket实例的receive方法接收DatagramPacket.DatagramPacket中间就包含了通信的内容。

  3. 通过DatagramSocket的send和receive方法来收和发DatagramPacket.

客户端(client):

  1. 构造DatagramSocket实例。

2.通过DatagramSocket实例的send和receive方法发送DatagramPacket报文。

3.结束后,调用DatagramSocket的close方法关闭。

28、【上机】使用UDP的方式,完成对象的传递。

(1)客户端向服务器端传送对象信息;

DatagramSocket ds=new DatagramSocket(9999);

         //构建数据包;

        Scanner input=new Scanner(System.in);

        System.out.println("请输入用户名:");

        String username=input.nextLine();

        System.out.println("请输入密码:");

        String password=input.nextLine();

 //事先要创建好User类;

        User user=new User(username,password);

        //字节流;在内存开辟缓冲区;

        ByteArrayOutputStream baos=new ByteArrayOutputStream();

        ObjectOutputStream oos=new ObjectOutputStream(baos);

        oos.writeObject(user);

 

        byte buf[]=baos.toByteArray();

        DatagramPacket dp=new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 8888);//DatagramPacket只用byte[]数组;

             //发送数据;

        ds.send(dp);

(2)服务器端从客户端接收对象信息;

//服务器定义DatagramSocket以接收数据;

DatagramSocket ds=new DatagramSocket(8888);

 

//定义一个数据包来接收数据;

//数据包里是byte数组,所以还得定义一个byte数组;

byte buf[]=new byte[1024];

DatagramPacket dp=new DatagramPacket(buf, buf.length);

         //接收客户端发过来的数据;

ds.receive(dp);

 

        //用字节流和对象流读取对象信息;

ByteArrayInputStream bais=new ByteArrayInputStream(buf);

ObjectInputStream ois=new ObjectInputStream(bais);

 

User user=(User)ois.readObject();

        System.out.println(user);

 

29、HTTPClient相关问题

什么是httpClient?
什么是HttpClient不能做的?
HttpClient有哪些特性?
HttpClient怎样发送带参数的GET请求?
HttpClient怎样发送带参数的POST请求?
HttpClient怎样获取响应状态?
HttpClient怎样获取响应内容?
HttpClient怎样上传文件?

30、NIO 和传统 BIO区别是什么?

NIO vs BIO之间的理念上面的区别(NIO将阻塞交给了后台线程执行)

IO是面向流的,NIO是面向缓冲区的

Java BIO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方;

NIO则能前后移动流中的数据,因为是面向缓冲区的 BIO流是阻塞的,NIO流是不阻塞的 Java IO的各种流是阻塞的。

这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。

该线程在此期间不能再干任何事情了 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。

NIO可让您只使用一个(或几个)单线程管理多个通道(网络连接或文件),但付出的代价是解析数据可能会比从一个阻塞流中读取数据更复杂。

非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。

选择器

Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来“选择”通道:这些通道里已经有可以处理的输入,或者选择已准备写入的通道。这种选择机制,使得一个单独的线程很容易来管理多个通道。

31、Java NIO 的几个核心组成部分是什么?作用分别是什么?

l Channels

l Buffers

l Selectors

基本上,所有的 IO 在NIO 中都从一个Channel 开始。

Channel 有点象流。

数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。

Channel的实现:

(涵盖了UDP 和 TCP 网络IO,以及文件IO)

l FileChannel

l DatagramChannel

l SocketChannel

l ServerSocketChannel

读数据:

int bytesRead = inChannel.read(buf);

写数据:

int bytesWritten = inChannel.write(buf);

Buffer

Buffer实现: (byte, char、short, int, long, float, double )

l ByteBuffer

l CharBuffer

l DoubleBuffer

l FloatBuffer

l IntBuffer

l LongBuffer

l ShortBuffer

Buffer使用

l 读数据 flip()方法:

buf.flip();

将Buffer从写模式切换到读模式 调用flip()方法会将position设回0,并将limit设置成之前position的值。

l (char) buf.get()

读取数据

l Buffer.rewind()

将position设回0,所以你可以重读Buffer中的所有数据 limit保持不变,仍然表示能从Buffer中读取多少个元素(byte、char等)

l Buffer.mark()方法,

可以标记Buffer中的一个特定position。

之后可以通过调用 Buffer.reset()方法,恢复到Buffer.mark()标记时的position

一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。

l clear()方法:

清空整个缓冲区。 position将被设回0,limit被设置成 capacity的值

l compact()方法:

只会清除已经读过的数据;任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。

将position设到最后一个未读元素正后面,limit被设置成 capacity的值 写数据 buf.put(127);

Buffer的三个属性

capacity:

l 表示容量

l Buffer的一个固定的大小值;

l Buffer满了需要将其清空才能再写;

l ByteBuffer.allocate(48);该buffer的capacity为48byte CharBuffer.allocate(1024);该buffer的capacity为1024个char

position:

含义取决于Buffer处在读模式还是写模式(初始值为0,写或者读操作的当前位置)

写数据时,初始的position值为0;

其值最大可为capacity-1

将Buffer从写模式切换到读模式,position会被重置为0

limit:

含义取决于Buffer处在读模式还是写模式(写limit=capacity;读limit等于最多可以读取到的数据)

写模式下,limit等于Buffer的capacity 切换Buffer到读模式时, limit表示你最多能读到多少数据;

Selector

Selector允许单线程处理多个 Channel。

如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用Selector就会很方便。

例如,在一个聊天服务器中。

要使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。

一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

使用
创建:



Selector selector = Selector.open();   注册通道: channel.configureBlocking(false);   //与Selector一起使用时,Channel必须处于非阻塞模式 

//这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式(而套接字通道都可以)

SelectionKey key = channel.register(selector, Selectionkey.OP_READ);  //第二个参数表明Selector监听Channel时对什么事件感兴趣

//SelectionKey.OP_CONNECT  SelectionKey.OP_ACCEPT  SelectionKey.OP_READ SelectionKey.OP_WRITE //可以用或操作符将多个兴趣组合一起

SelectionKey 包含了interest集合 、ready集合 、Channel 、Selector 、附加的对象(可选)

int interestSet = key.interestOps();

可以进行类似interestSet & SelectionKey.OP_CONNECT的判断

select():

阻塞到至少有一个通道在你注册的事件上就绪了

selectNow():

不会阻塞,不管什么通道就绪都立刻返回

selectedKeys():

访问“已选择键集(selected key set)”中的就绪通道

close():

使用完selector需要用其close()方法会关闭该Selector,且使注册到该Selector上的所有SelectionKey实例无效  

Set selectedKeys = selector.selectedKeys();  

Iterator keyIterator = selectedKeys.iterator();  

while(keyIterator.hasNext()) {       

SelectionKey key = keyIterator.next();       

if(key.isAcceptable()) {           

// a connection was accepted by a ServerSocketChannel.       

} else if (key.isConnectable()) {          

 // a connection was established with a remote server.       

} else if (key.isReadable()) {          

 // a channel is ready for reading       

} else if (key.isWritable()) {           

// a channel is ready for writing       

}     

keyIterator.remove();//注意这里必须手动remove;

表明该selectkey我已经处理过了;

}

32、简单说一下http协议?

HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。

HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。

HTTP 工作原理

HTTP协议工作于客户端-服务端架构上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。

Web服务器有:Apache服务器,Nginx,IIS服务器(Internet Information Services)等。

Web服务器根据接收到的请求后,向客户端发送响应信息。

HTTP默认端口号为80,但是你也可以改为8080或者其他端口。

HTTP三点注意事项:

l HTTP是无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

l HTTP是媒体独立的:这意味着,只要客户端和服务器知道如何处理的数据内容,任何类型的数据都可以通过HTTP发送。客户端以及服务器指定使用适合的MIME-type内容类型。

l HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

33、http协议下客户端请求报文是什么?

客户端发送一个HTTP请求到服务器的请求消息包括以下格式:

l 请求行(request line)

l 请求头部(header)

l 空行

l 请求数据

四个部分组成

下图给出了请求报文的一般格式。

客户端请求:

GET /hello.txt HTTP/1.1

User-Agent: curl/7.16.3 libcurl/7.16.3 OpenSSL/0.9.7l zlib/1.2.3

Host: www.example.com

Accept-Language: en, mi

34、描述一下http协议服务器响应报文有哪些?

HTTP响应也由四个部分组成,分别是:

l 状态行

l 消息报头

l 空行

l 响应正文

HTTP/1.1 200 OK

Date: Mon, 27 Jul 2009 12:28:53 GMT

Server: Apache

Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT

ETag: “34aa387-d-1568eb00”

Accept-Ranges: bytes

Content-Length: 51

Vary: Accept-Encoding

Content-Type: text/plain

35、HTTP协议中常用的请求方法有哪些

根据HTTP标准,HTTP请求可以使用多种请求方法。

HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。

HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
在这里插入图片描述

36、常见的HTTP状态码有哪些

当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。

HTTP状态码的英文为HTTP Status Code。

下面是常见的HTTP状态码:

l 200 - 请求成功

l 301 - 资源(网页等)被永久转移到其它URL

l 404 - 请求的资源(网页等)不存在

l 500 - 内部服务器错误

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
在这里插入图片描述

37、HTTP 协议中content-type指的是什么?

Content-Type,内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些Asp网页点击的结果却是下载到的一个文件或一张图片的原因。

38、网络传输协议本质和作用是什么?

协议本质是双方约定好的一种传输规则,为了让传输数据的双方节点能建立连接,按照约定去传输和解析数据

39、可以实现一个简单的网络协议吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值