什么是Socket?
Socket是应用层与TCP/IP协议族通信中的中间软件抽象层,它是一组接口。TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket);
原生JDK网络编程 - BIO
概念:BIO(blocking I/O):同步阻塞。
当客户端有连接请求时,服务端就会需要启动一个线程对客户端的连接进行数据读写,如果客户端不进行读写了,那么这个线程也会等着,这样就会造成阻塞。
ServerSocket
负责绑定IP地址,启动监听端口;Socket
负责发起连接操作;连接成功后,双方通过输入和输出流进行同步阻塞式
通信;
原生JDK网络编程 - NIO
概念:NIO库是JDK1.4中引入的。NIO弥补了原来的I/O的不足,它在标准Java代码中提供了高速的、面向块的I/O。NIO翻译成no-blocking io或者new io都说的通;
和BIO的主要区别
面向流与面向缓冲
Java NIO 和 IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节;它们没有被缓存在任何地方。此外,它不能前后移动流中的数据;如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。阻塞与非阻塞IO
Java IO的各种流是阻塞的。这意味着,当一个线程调用read()或write()a时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能在干任何事情;
Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么也不会获取。而不是保持线程阻塞,所以直到数据变的可以读取之前,该线程可以继续做其他事情。非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。线程通常将非阻塞IO的空闲时间用于在其他通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
NIO三大核心组件
Selector选择器
Selectior
意为”选择器“,也可以称为”轮询代理器“、”事件订阅器“、”channel容器管理机“都行。
Java NIO的选择器允许一个单独的线程来监视多个输入通道,你可以注册多个通道使用一个选择器,然后使用一个单独的线程来操作这个选择器,进而”选择“通道;这些通道里已经有可以处理的输入,或者选择已准备写入的通道;这种选择机制,使得一个单独的线程很容易来管理多个通道;
应用程序将向Selector
对象注册需要它关注的Channel,以及具体的某一个Channel
会对哪些IO事件感兴趣。Selector
中也会维护一个”已经注册的Channel“的容器;
Channel管道
通道,被建立的一个应用程序和操作系统交互事件,传递内容的渠道。那么既然是和操作系统进行内容的传递,那么说明应用程序可以通过通道读取数据,也可以通过通道向操作系统写数据,而且可以同时进行读写;
- 所有被
Selector
注册的通道,只能是继承了SelectoableChannel
类的子类; SelectoableChannel
:应用服务器程序的监听通道。只有通过这个通道,应用程序才能向操作系统注册支持”多路复用IO“的端口监听。同时支持UDP协议和TCP协议;SocketChannel
:TCP Socket套接字的监听通道,一个Socket套接字对应了一个客户端IP:端口到服务器IP:端口的通信连接;
通道中的数据总是要先读取到一个Buffer,或者总是要从一个Buffer中写入;
buffer缓冲区
Buffer
用于和NIO通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中。以写为例,应用程序都是将数据写入缓冲,再通过通道把缓冲的数据发送出去,读也是一样,数据总是先从通道读取缓冲,应用程序再读缓冲的数据。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存(其实就是数组)。这块内存被包装成NIO Buffer
对象,并提供了一组方法,用来方便的访问该块内存。
关系图
SelectionKey
什么是SelectionKey
SelectionKey
是一个抽象类,表示selectableChannel
在Selector
中注册的标识,每个Channel
向Selector
注册时,都将会创建一个SelectionKey
。SelectionKey
将Channel
与Selector
建立了关系,并维护了channel
时间。
可以通过cancel
方法取消键,取消的键不会立即从selector
中移除,而是添加到cancelledKeys
中,在下一次select操作时移除它,所以在调用某个
key时,需要使用
isValid`进行校验。
Selection类型和就绪条件
在向Selector
对象注册感兴趣的事件时,JAVA NIO共定义了四种:OP_READ
、OP_WRITE
、OP_CONNECT
、OP_ACCEPT
(定义在SelectionKey
中),分别对应读、写、请求连接、接受连接等网络Socket操作。
操作类型 | 就绪条件及说明 |
---|---|
OP_READ | 当操作系统读缓冲区有数据可读时就绪。并非时刻都有数据可读,所以一般需要注册该操作,仅当有就绪时才发起读操作,避免浪费CPU |
OP_WRITE | 当操作系统写缓冲区有空闲空间时就绪。一般情况下写缓冲区都有空闲空间,小块数据直接写入即可,没必要注册该操作类型,否则该条件不断就绪浪费CPU;但如果是写密集型的任务,比如文件下载时,缓冲区很可能满,注册该操作类型就很有必要,同时注意写完后取消注册。 |
OP_CONNECT | 当SocketChannel.connect()请求连接成功后就绪,该操作只给客户端使用。 |
OP_ACCEPT | 当接收到一个客户端连接请求时就绪。该操作只给服务端使用。 |
服务端和客户端分别感兴趣的类型
ServerSocketChannel
和SocketChannel
可以注册自己感兴趣的操作类型,当对应操作类型的就绪条件满足时OS会通知channel
下表描述各种Channel
允许注册的操作类型,Y表示允许注册,N表示不允许注册,其中服务器SocketChannel
指由服务器ServerSocketChannel.accept()
返回时的对象。
OP_READ | OP_WRITE | OP_CONNECT | OP_ACCEPT | |
---|---|---|---|---|
服务器ServerSocketChannel | Y | |||
服务器SocketChannel | Y | Y | ||
客户端SocketChannel | Y | Y | Y |
服务端启动ServerSocketChannel,关注OP_ACCEPT事件;
客户端启动SocketChannel,连接服务器,关注OP_CONNECT事件;
服务器接受连接,启动一个服务器的SocketChannel,这个SocketChannel可以关注OP_READ、OP_WRITE事件,一般连接建立后会直接关注OP_READ事件;
客户端这边的客户端SocketChannel发现连接建立后,可以关注OP_READ、OP_WRITE事件,一般是需要客户端需要发送数据了才关注OP_RAED事件;
连接建立后客户端与服务器开始相互发送消息(读写),根据实际情况来关注OP_READ、OP_WRITE事件;