本文共 7446 字,大约阅读时间需要 24 分钟。
为使不同计算机厂家的计算机能够互相通信,以便在更大的范围内建立计算机网络,有必要建立一个国际范围的网络体系结构。
目的是为了让我们屏蔽底层网络的复杂性,比如一些后端语言开发的人员,如果对底层不是非常了解也没有什么关系。
HTTP服务器(基于TCP开发的,基于Web服务的一个协议)实现了高三层这些功能。
是Internet一个重要的传输层协议。TCP提供面向连接、可靠、有序、字节流传输服务。应用程序在使用TCP之前,必须先建立TCP连接。
标志位说明:
三次握手的目的是最大程度上去检验网络是否通畅。就好比打电话的"喂喂喂",在正式发送数据之前做一个检验。
这并不是网络的建立连接,而是确立的机制。
用户数据报协议UDP是Internet传输层协议。提供无连接、不可靠、数据报尽力传输服务。
开发应用人员在UDP上构建应用,关注以下几点:
TCP | UDP |
---|---|
面向连接 | 无连接 |
提供可靠性 | 不可靠 |
慢 | 快 |
资源占用多 | 资源占用少 |
但是应用开发大部分都是基于TCP的,保证数据可靠性;只有在对网络高性能和数据库可靠性不高的情况下,比如看直播,音视频播放,物联网中智慧城市的一些设备的日志上报、状态上报,这种情况就可以用到UDP开发协议。
Internet中应用最广泛的网络应用编程接口,实现3种底层协议接口:
SOCK_DGRAM
(面向UDP接口)SOCK_STREAM
(面向TCP接口)SOCK_RAW
(面向网络层协议接口IP、ICMP等)主要socket API及其调用过程
当使用到Socket requeat = serverScoket.accept()
,Inputstream inputStream = request.getInputStream();
,OutputStream outputStream = request.getOutputStream();
的时候,会产生阻塞,等待下一步命令之后才释放。
1xx
(临时响应):表示临时响应并需要请求者继续执行操作的状态代码
2xx
(成功):表示成功处理了请求的状态代码。
3xx
(重定向):表示要完成请求,需要进一步操作。通常, 这些状态代码用来重定向。
4xx
(请求错误):这些状态代码表示请求可能出错,妨碍了服务器的处理。
5xx
(服务器错误):这些状态代码表示服务器在尝试处理请求时发生内部错误。这些错误可能是服务器本身的错误,而不是请求出错。
阻塞/非阻塞是获取资源的方式,异步/同步是程序如何处理资源的逻辑设计。代码中使用的API:ServerSocket#accept、InputStream#read
都是阻塞的API。操作系统底层API中,默认Socket操作都是Blocking型,send/recv
等接口都是阻塞的。
阻塞导致在处理网络I/O时,一个线程只能处理一个网络连接。
始于Java 1.4,提供了新的JAVA IO操作非阻塞API。用意是替代Java IO和Java Networking相关的API。
NIO中有三个核心组件:Buffer缓冲区,Channel通道,Selector选择器
缓冲区本质上是一个可以写入数据的内存块(类似数组),然后可以再次读取。此内存块包含在NIO Buffer对象中,该对象提供了一组方法,可以更轻松地使用内存块。相比较直接对数组的操作,Buffer API更加容易操作和管理。
使用Buffer进行数据写入与读取,需要进行如下四个步骤:
buffer.flip()
,转换为读取模式;buffer.clear()
或buffer.compact()
清除缓冲区;Buffer三个重要属性:
ByteBuffer为性能关键型代码提供了**直接内存(direct堆外)和非直接内存(heap堆)两种实现。**堆外内存获取的方式:ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(noBytes);
好处:
建议:
//创建一个字节为4的byte字节缓冲区,默认是写入模式ByteBuffer byteBuffer =ByteBuffer.allocate(4);//写入1字节的数据 byteBuffer.put((byte)1);//转为读取模式byteBuffer.flip();//读取到缓冲区中的数据byteBuffer.get();//清除整个缓冲区byteBuffer.clear();//仅清除已阅读的数据,转为写入模式byteBuffer.compact();//重置position为0byteBuffer.rewind();//标记position位置byteBuffer.mark();//重置position为上次mark的位置byteBuffer.reset();
Channel的API涵盖了UDP/TCP网络和文件IO;FileChannel;DatagramChannel;SocketChannel;ServerSocketChannel。
和标准IO Stream操作的区别:在一个通道内进行读取和写入,stream通常是单向的(input或output),可以非阻塞读取和写入通道,通道始终读取或写入缓冲区。
SocketChannel用于建立TCP网络连接,类似java.net.Socket
。有两种创建SocketChannel形式:
//客户端主动发起连接的方式SocketChannel socketChannel = SocketChannel.open();//设置为非阻塞模式socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("127.0.0.1",8080));channel.write(byteBuffer);//发送请求数据-向通道写入数据int bytesRead = socketChannel.read(byteBuffer);//读取服务端返回-读取缓冲区的数据socketChannel.close();//关闭连接
write()
在尚未写入任何内容时就可能反回了。需要在循环中调用write()。read()
方法可能直接返回而根本不读取任何数据,根据返回的int值判断读取了多少字节。ServerSocketChannel可以监听新建的TCP连接通道,类似ServerSocket。
//创建网络服务端ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();serverSocketChannel.configureBlocking(false);//设置为非阻塞模式serverSocketChannel.socket().bind(new InetSocketAddress(8080));//绑定端口while(true){ SocketChannel socketChannel = serverSocketChannel.accept();//获取新tcp连接通道 if(socketChannel != null){ //tcp请求 读取/响应 }}
serverSocketChannel.accept():
如果该通道处于非阻塞模式,那么如果没有挂起的连接,该方法立即返回null。必须检查返回的SocketChannel是否为null。
Selector是一个Java NIO组件,可以检查一个或多个NIO通道,并确定哪些通道已准备好进行读取或写入。实现单个线程可以管理多个通道,从而管理多个网络连接。
一个线程使用Selector监听多个channel的不同事件:四个事件分别对应SelectionKey四个常量。
SelectionKey.OP_CONNECT
OP_ACCEPT
OP_READ
OP_WRITE
实现一个线程处理多个通道的核心概念理解:事件驱动机制。
非阻塞的网络通道下,开发者通过Selector注册对于通道感兴趣的事件类型,线程通过监听事件来触发相应的代码执行。(拓展:更底层是操作系统的多路复用机制)
//放弃对channel的轮询,借助消息通知机制Selector selector = Selector.open();channel.configureBlocking(false);SelectionKey key = channel.register(selector,SelectionKey.OP_READ);//注册感兴趣的事件key.interestOps(SelectionKey.OP_ACCEPT);//对serverSocketChannel上面的accrpt事件感兴趣(serverSocketChannel只能支持accept操作)while(true){ //由accept轮询,变成了事件通知的方式 selector.select();//不再轮询通道,改用轮询事件的方式,select方法有阻塞效果,直到有时间通知才会有返回 int readChannels = selector.select();//select收到新的事件,方法才会返回 if(readyChannels == 0) continue; //获取事件 SetselectedKeys = selector.selectedKeys(); //遍历查询结果 Iterator keyIterator = selectedKeys.iterator(); while(keyIterator.hasNext()){ //被封装的查询结果 SelectionKey key = keyIterator.next(); //判断不同的事件类型,执行对应的逻辑处理 //key.isAcceptable();key.isConnectable();key.Readable();key.isWritable(); if(key.isAcceptable(){ ServerSocketChannel server = (ServerSocketChannel) key.attachment(); //将拿到客户端连接通道,注册到selector上面 SocketChannel clientSocketChannel = server.accept(); } keyIterator.remove(); }}
如果程序需要支撑大量的连接,使用NIO时最好的方式。Tomcat8中,已经完全去除BIO相关的网络处理代码,默认采用NIO进行网络处理。
实际企业中不会只用单线程进行操作,而会对NIO进行多线程的改进。
参考Doug Lea的著名文章《Scalable in Java》
NIO为开发者提供了功能丰富及强大的IO处理API,但是在应用于网络应用开发的过程中,直接使用JDK提供的API,比较繁琐。而且要想将性能进行提升,光有NIO还不够,还需要将多线程技术与之结合起来。
因为网络编程本身的复杂性,以及JDK API开发的使用难度较高,所以在开源社区中,涌出来很多对JDK NIO进行封装、增强后的网络编程框架,例如: Netty、 Mina等。
一个请求,一个工作线程,CPU利用率低,新版本中不再使用。
apr (Apache Portable Runtime/Apache可移植运行库),是Apache HTTP服务器的支持库。JNI的形式调用Apache HTTP服务器的核心动态链接库来处理文件读取或网络传输操作Tomcat默认监听指定路径,如果有apr安装,则自动启用
Tomcat8开始,默认NIO方式,非阻塞读取请求信息,非阻塞处理下一个请求,完全异步。
配置项 | 默认 | 建议 | 注意 |
---|---|---|---|
ConnectionTimeout | 20s | 减少 | |
maxThread处理连接的最大线程数 | 200 | 增加 | 不是越大越好 |
acceptCount(backlog)等待接受accept的请求数量限制 | 100 | 增加 | socket参数,min(accept,/proc/sys/net/core/somaxconn) |
maxConnections最大连接处理数 | nio 1w apr 8192 | 不变 |
转载地址:http://amugn.baihongyu.com/