Java面试15|网络

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试15|网络相关的知识,希望对你有一定的参考价值。

 

1、查看网络的统计信息:

netstat -s 结果中显示统计信息,保护收发包,建立连接的数量

netstat -at 列出所有TCP端口

netstat -au 列出所有的UDP端口

netstat -aut 目前正在运行的TCP/UDP服务

netstat常用的参数如下:

-s或–statistice 显示网络工作信息统计表。

-t或–tcp 显示TCP传输协议的连线状况。

-u或–udp 显示UDP传输协议的连线状况。

-a或–all 显示所有连接中的Socket。

-n或–numeric 直接使用IP地址,而不通过域名服务器。

 

netstat -n | awk ‘/^tcp/ {++S[$NF]} END{for(a in S) print a, S[a]}‘  

 $NF表示倒数第一个参数,某次运行的结果如下:

 技术分享

 

TIME_WAIT状态从上图可知由3种状态可以转换而来,根据TCP协议定义的3次握手断开连接规定,发起socket主动关闭的一方 socket将进入TIME_WAIT状态。TIME_WAIT状态将持续2个MSL(Max Segment Lifetime),在Windows下默认为4分钟,即240秒。TIME_WAIT状态下的socket不能被回收使用. 具体现象是对于一个处理大量短连接的服务器,如果是由服务器主动关闭客户端的连接,将导致服务器端存在大量的处于TIME_WAIT状态的socket, 甚至比处于Established状态下的socket多的多,严重影响服务器的处理能力,甚至耗尽可用的socket,停止服务。

通过调试Linux内核参数可以进行一些优化:

(1)net.ipv4.tcp_fin_timeout,默认60s,减小fin_timeout,减少TIME_WAIT连接数量。 

(2)net.ipv4.tcp_tw_reuse = 1表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;

(3)net.ipv4.tcp_tw_recycle = 1表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。

 

2、TCP(Transmission Control Protocol)三次握手与四次分手

TCP在不可靠的传输信道上提供了可靠传输的抽象,隐藏了我们的应用程序大部分的复杂性功能:丢包重传,按序传送,拥塞控制和避免,数据完整性,其他特性。当您使用TCP流,TCP协议保证您的所有字节发送与接收的数据是相同的,他们会以相同的顺序到达对端。TCP设计为一个顺序发送协议,而不是一个定时发送协议。 

所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。 
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时,将触发三次握手。
 

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

TCP协议的连接是全双工连接,一个TCP连接存在双向的读写通道。 
简单说来是 “先关读,后关写”,一共需要四个阶段。以客户机发起关闭连接为例:
1.服务器读通道关闭
2.客户机写通道关闭
3.客户机读通道关闭
4.服务器写通道关闭
关闭行为是在发起方数据发送完毕之后,给对方发出一个FIN(finish)数据段。直到接收到对方发送的FIN,且对方收到了接收确认ACK之后,双方的数据通信完全结束,过程中每次接收都需要返回确认数据段ACK。
详细过程:
第一阶段  

(1)客户机发送完数据之后,向服务器发送一个FIN数据段,序列号为i;
(2)服务器收到FIN(i)后,返回确认段ACK,序列号为i+1,关闭服务器读通道;
(3)客户机收到ACK(i+1)后,关闭客户机写通道;
(此时,客户机仍能通过读通道读取服务器的数据,服务器仍能通过写通道写数据)
第二阶段 

(1)服务器发送完数据之后,向客户机发送一个FIN数据段,序列号为j;
(2)客户机收到FIN(j)后,返回确认段ACK,序列号为j+1,关闭客户机读通道;
(3)服务器收到ACK(j+1)后,关闭服务器写通道。
这是标准的TCP关闭两个阶段,服务器和客户机都可以发起关闭,完全对称。

 

技术分享

 

  

技术分享

绿色箭头为client,而蓝色的为server。

 
参考:
(1)http://www.cnblogs.com/Jessy/p/3535612.html 
(2)http://www.oschina.net/question/2011290_2199294?fromerr=o3YIYWOP
(3)http://itindex.net/detail/51010-httpclient-%E5%8E%9F%E7%90%86-%E6%97%B6%E5%BA%8F%E5%9B%BE

 

3、TCP协议的流量控制和拥塞控制

 

 

4、Java NIO

是一种同步非阻塞的I/O模型,也是I/O多路复用的基础。具体说就是Selector会不断轮询注册在其上的Channel,如果某个Channel上有新的TCP连接,读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectorKey可以获取就绪Channel的集合,进行后续I/O操作。

NIO单线程轮询事件,找到可以进行读写的网络描述符进行读写。除了事件的轮询是阻塞的(没有可干的事情必须要阻塞),剩余的I/O操作都是纯CPU操作,没有必要开启多线程。

并且由于线程的节约,连接数大的时候因为线程切换带来的问题也随之解决,进而为处理海量连接提供了可能。

单线程处理I/O的效率确实非常高,没有线程切换,只是拼命的读、写、选择事件。但现在的服务器,一般都是多核处理器,如果能够利用多核心进行I/O,无疑对效率会有更大的提高。

public class MultiplexerTimeServer implements Runnable {

	private Selector selector;

	private ServerSocketChannel servChannel;

	private volatile boolean stop;

	/**
	 * 初始化多路复用器、绑定监听端口
	 */
	public MultiplexerTimeServer(int port) {
		try {
			selector = Selector.open();
			
			// Channel主要用来读写网络上的数据的。打开ServerSocketChannel,用于监听客户端的连接,它是所有客户端连接的父管道
			servChannel = ServerSocketChannel.open();
			
			// 设置为非阻塞模式
			servChannel.configureBlocking(false);
			
			// 绑定监听端口 8080
			servChannel.socket().bind(new InetSocketAddress(port), 1024);
			
			/*
			 * Selector会不断地轮询在其上的Channel,如果某个Channel上面有新的TCP
			 * 连接接入、读和写事件,这个Channel就处于就绪状态
			 * 
			 * 注册到Reacotr线程的多路复用器Selector上,监听ACCEPT事件
			 */
			servChannel.register(selector, SelectionKey.OP_ACCEPT);
			
			System.out.println("The time server is start in port : " + port);
		} catch (IOException e) {
			e.printStackTrace();
			System.exit(1);
		}
	}

	public void stop() {
		this.stop = true;
	}

	public void run() {
		while (!stop) {
			try {
				// This method performs a blocking selection operation. 
				// It returns only after at least one channel is selected,(只有在至少有一个事件就绪后才会进行返回,所以是阻塞的) 
				// this selector‘s wakeup method is invoked, the current thread is interrupted, 
				// or the given timeout period expires, whichever comes first. 
				selector.select(1000); // 阻塞等待,休眠时间为1s
				Set<SelectionKey> selectedKeys = selector.selectedKeys();
				/*
				 * 当有处于就绪状态的Channel时,selector将返回就绪状态的Channel的SelectionKey
				 * 集合,通过对就绪状态的Channel集合进行迭代,可以进行网络的异步读写操作
				 */
				Iterator<SelectionKey> it = selectedKeys.iterator();
				SelectionKey key = null;
				while (it.hasNext()) {
					key = it.next();
					it.remove();
					try {
						// 事件分发器,单线程选择就绪的事件。
						// I/O处理器,包括connect、read、write等,这种纯CPU操作,一般开启CPU核心个线程就可以。
						// 业务线程,在处理完I/O后,业务一般还会有自己的业务逻辑,有的还会有其他的阻塞I/O,如DB操作,RPC等。只要有阻塞,就需要单独的线程。
						handleInput(key); // 所以在这里其实最好是用其它的线程来处理,而不要影响了事件分发器线程
					} catch (Exception e) {
						if (key != null) {
							key.cancel();
							if (key.channel() != null)
								key.channel().close();
						}
					}
				}
			} catch (Throwable t) {
				t.printStackTrace();
			}
		}

		// 多路复用器关闭后,所有注册在上面的Channel和Pipe等资源都会被自动去注册并关闭,所以不需要重复释放资源
		if (selector != null)
			try {
				selector.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
	}

	private void handleInput(SelectionKey key) throws IOException {

		if (key.isValid()) {
			
			// 处理新接入的请求消息
			if (key.isAcceptable()) {
				// 接受一个新的客户端接入请求
				ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
				SocketChannel sc = ssc.accept();
				// 设置客户端为异步非阻塞
				sc.configureBlocking(false); 
				// Add the new connection to the selector
				sc.register(selector, SelectionKey.OP_READ);
			}
			if (key.isReadable()) {
				/*
				 * 读取数据
				 * 读取到的字节数,返回值有以下有三种结果:
				 * (1)大于0,读取到字节,对其进行解编码
				 * (2)等于0,没有读取到字节,南纺股份正常场景,忽略
				 * (3)-1 ,链路已经关闭,需要关闭SocketChannel,释放资源
				 */
				SocketChannel sc = (SocketChannel) key.channel();
				ByteBuffer readBuffer = ByteBuffer.allocate(1024);
				// 由于设置了SocketChannel为异步非阻塞的,所以它的read是非阻塞的
				int readBytes = sc.read(readBuffer);
				if (readBytes > 0) {
					/*
					 * 将缓冲区当前的limit设置为position,position为0,用于后续对缓冲区的读取操作。
					 * 然后根据缓冲区可读的字节个数创建字节数组,调用get()操作将缓冲区可读的字节数
					 * 组复制到新创建的字节数组中
					 */
					readBuffer.flip();
					byte[] bytes = new byte[readBuffer.remaining()];
					readBuffer.get(bytes);
					String body = new String(bytes, "UTF-8");
					System.out.println("The time server receive order : " + body);
					String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? 
							new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";
					doWrite(sc, currentTime);
				} else if (readBytes < 0) {
					// 对端链路关闭
					key.cancel();
					sc.close();
				} else{
					; // 读到0字节,忽略
				}
					
			}
		}
	}

	private void doWrite(SocketChannel channel, String response)throws IOException {
		/*
		 * 由于SocketChannel是异步非阻塞的,并不能保证一次能够把所有需要发送的数据发送,此时会出现写半包问题。
		 * 需要注册写操作???,不断轮询Selector将没有发送完的bytebuffer发送完毕。可以通过byteBuffer的hasRemain()
		 * 方法判断是否发送完毕。
		 */
		if (response != null && response.trim().length() > 0) {
			// 将应答消息异步发送给客户端
			byte[] bytes = response.getBytes();
			ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
			// 将缓冲区中的字节数据发送
			writeBuffer.put(bytes);
			writeBuffer.flip(); // 缓存区复位
			channel.write(writeBuffer);
		}
	}
}

 Selector轮询是阻塞的,而真正的I/O是异步非阻塞的。

对于NIO来说,缓存可以使用DirectByteBuffer和HeapByteBuffer。如果使用了DirectByteBuffer,一般来说可以减少一次系统空间到用户空间的拷贝。但Buffer创建和销毁的成本更高,更不宜维护,一般用来读取大文件时使用。

 

参考文章:http://blog.csdn.net/szzt_lingpeng/article/details/50612018  

 

5、NIO中, 如果不显式的调用 system.gc() 那会出现什么问题?

DirectBuffer的GC规则与堆对象的回收规则是一样的,只有垃圾对象才会被回收,而判定是否为垃圾对象依然是根据引用树中的存活节点来判定。

如果DirectByteBuffer的空间够用,那么System.gc()是不会触发FullGC的。 也就是说在空间不够用时,显示调用才能进行回收,如果不显式调用,那只能是抛出内存异常了。

在垃圾收集时,虽然虚拟机会对DirectMemory进行回收,但是DirectMemory却不像新生代和老年代那样,发现空间不足了就通知收集器进行垃圾回收,它只能等待老年代满了后FullGC,然后“顺便地”帮它清理掉内存中废弃的对象。否则,只能等到抛出内存溢出异常时,在catch块里调用System.gc()。

参考:http://blog.csdn.net/donsonzhang/article/details/46666353 

 

6、https 的工作原理,和 http 的区别 

 

关于https可以参考:http://www.cnblogs.com/xinzhao/p/4949344.html

 

7、描述HTTP协议(HTTP请求和响应报文的格式)

HTTP协议采用“请求-应答”模式,当使用普通模式(非KeepAlive模式)时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议);当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。 

(1)在HTTP 1.0中Keep-Alive默认是关闭的,需要在HTTP头中加入“Connection: Keep-Alive” ,才能启用Keep-Alive

(2)HTTP 1.1中Keep-Alive默认启用,加入“Connection: close”可关闭。目前大部分浏览器都是用HTTP 1.1协议,也就是说默认都会发起Keep-Alive的连接请求了,所以是否能完成一个完整的Keep- Alive连接就看Web服务器的设置情况。

技术分享

1,请求行

由3部分组成,分别为:请求方法、URL(见备注1)以及协议版本,之间由空格分隔

请求方法包括GET、HEAD、PUT、POST、TRACE、OPTIONS、DELETE以及扩展方法,当然并不是所有的服务器都实现了所有的方法,部分方法即便支持,处于安全性的考虑也是不可用的

协议版本的格式为:HTTP/主版本号.次版本号,常用的有HTTP/1.0和HTTP/1.1

2,请求头部

请求头部为请求报文添加了一些附加信息,由"名/值"对组成,每行一对,名和值之间使用冒号分隔

常见请求头如下:

请求头

说明

Host

接受请求的服务器地址,可以是IP:端口号,也可以是域名

User-Agent

发送请求的应用程序名称

Connection

指定与连接相关的属性,如Connection:Keep-Alive

Accept-Charset

通知服务端可以发送的编码格式

Accept-Encoding

通知服务端可以发送的数据压缩格式

Accept-Language

通知服务端可以发送的语言

请求头部的最后会有一个空行,表示请求头部结束,接下来为请求正文,这一行非常重要,必不可少

HTTP响应报文格式:

HTTP响应报文主要由状态行、响应头部、响应正文3部分组成

技术分享

1,状态行

由3部分组成,分别为:协议版本,状态码,状态码描述,之间由空格分隔。常见的状态码如下:

 

状态码

说明

200                   

响应成功

302

跳转,跳转地址通过响应头中的Location属性指定(JSP中Forward和Redirect之间的区别

400

客户端请求有语法错误,不能被服务器识别

403

服务器接收到请求,但是拒绝提供服务(认证失败)

404

请求资源不存在

500

服务器内部错误

   

2,响应头部

与请求头部类似,为响应报文添加了一些附加信息。常见响应头部如下:

响应头

说明

Server

服务器应用程序软件的名称和版本

Content-Type

响应正文的类型(是图片还是二进制字符串)

Content-Length

响应正文长度

Content-Charset

响应正文使用的编码

Content-Encoding

响应正文使用的数据压缩格式

Content-Language

响应正文使用的语言

















以上是关于Java面试15|网络的主要内容,如果未能解决你的问题,请参考以下文章

Java进阶之光!2021必看-Java高级面试题总结

Java工程师面试题,二级java刷题软件

如何在片段中使用 GetJsonFromUrlTask​​.java

软工网络15个人作业4——alpha阶段个人总结

软工网络15个人作业4--alpha阶段个人总结

软工网络15个人作业4——alpha阶段个人总结