在Java的NIO中,有三个比较重要的概念:Buffer、Channel和Selector。
结合上一篇文章提到的送花的例子。Buffer对应花,Channel对应A和B与花之间的联系,Selector就是不断进行轮询的线程。
Channel分为ServerSocketChannel和SocketChannel,是客户端与服务端进行通信的通道。
ServerSocketChannel用户服务器端,职责就是监听客户端的连接请求。一旦通过允许,就会建立与该客户端对应的SocketChannel。一个服务端的一个端口只能建立一个ServerSocketChannel用来监听连接。
SocketChannel具有唯一性。一个客户端可能链接多个服务端,那就是多个SocketChannel。服务端与多个客户端建立的连接就有多个SocketChannel。
Selector是用来负责阻塞轮询的线程,可以通过其静态方法Seletor.open()创建。服务端创建后通过Channel的register方法注册到ServerSocketChannel上,等待客户端连接。客户端同样创建Seletor后通过Channel的register方法注册到SocketChannel上。
当客户端的SocketChannel指定服务端的port和ip进行connect请求之后,服务端的Selector就可以检测到客户端的connect请求。然后服务端accept表示继续监听下一个请求,同时可以继续在与客户端建立了SocketChannel上监听读写请求。客户端同理。
Selector的作用就是监听SelectionKey.OP_ACCEPT(服务端专属)、SelectionKey.OP_CONNECT(客户端专属)、SelectionKey.OP_READ、SelectionKey.OP_Write四种注册的请求,一旦有请求被允许,就会调用相关的方法进行处理。
Buffer是用于在Channel中传递的数据。Buffer里有4个属性,来表示数据在Buffer中的存取情况:
- capacity:容量。Buffer的最大存储量,创建时指定,使用过程中不会改变
- limit:上线。Buffer中已有数据的最大值,<=capacity
- position:索引位置。position从0开始,随着get和put方法自动更新,用来记录实时数据的位置
- mark:用来暂存position的值。mark后可以通过reset方法将mark的值恢复到position
这4个属性的大小关系是:mark<=position<=limit<=capacity
接下来通过一个客户端与服务端通信的例子,来学习使用NIO。客户端每隔1秒向服务端发送请求,服务端响应并返回数据。
服务端:
package cn.testNio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* @Description : TODO
* @Author : houshuiqiang@163.com, 2017年10月2日 下午2:58:31
* @Modified :houshuiqiang@163.com, 2017年10月2日
*/
public class NioDemoServer {
public static void main(String[] args) {
NioServer nioServer = new NioServer(8181);
new Thread(nioServer, "nio-server-test").start();
}
}
class NioServer implements Runnable {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private volatile boolean stop;
public NioServer(int port){
stop = false;
try {
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public void stop(){
this.stop = true;
}
@Override
public void run(){
while (!stop){
try {
selector.select(); // 阻塞等待
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
try{
handleKey(selectionKey); // 可能发生客户端失联的错误
}catch(IOException e){
e.printStackTrace();
if (selectionKey != null) { // 将发生异常的客户端关闭,否则会一直被selector轮询到
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleKey(SelectionKey selectionKey) throws IOException {
if (selectionKey.isValid()) {
if (selectionKey.isAcceptable()) {
ServerSocketChannel ssc = (ServerSocketChannel)selectionKey.channel();
SocketChannel socketChannel = ssc.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
String body = getBodyFromSocketChannel(socketChannel);
if (null == body) {
// 断开链路
selectionKey.cancel();
selectionKey.channel().close();
}else if ("".equals(body)) {
// 心跳检测 ,忽略
}else{
String resultBody = handleBody(socketChannel, body);
write2Client(socketChannel, resultBody);
}
}
}
}
private String getBodyFromSocketChannel(SocketChannel socketChannel) throws IOException{
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int byteBufferSize = socketChannel.read(byteBuffer);
if (byteBufferSize == 0) { // 心跳检测,忽略
return "";
}else if (byteBufferSize > 0) {
byteBuffer.flip();
byte[] array = new byte[byteBuffer.remaining()];
byteBuffer.get(array);
return new String(array);
}else{
return null;
}
}
private String handleBody(SocketChannel socketChannel, String body) {
String hostAddress = socketChannel.socket().getInetAddress().getHostAddress();
System.out.println("message from client : " + hostAddress + ", content: " + body); // 模拟请求处理
return "server received message: " + body; // 模拟返回处理结果
}
private void write2Client(SocketChannel socketChannel, String resultBody) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 真实场景往往比1024要大
byteBuffer.put(resultBody.getBytes());
byteBuffer.flip();
socketChannel.register(selector, SelectionKey.OP_READ);
socketChannel.write(byteBuffer);
}
}
客户端:
package cn.testNio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @Description : TODO
* @Author : houshuiqiang@163.com, 2017年10月2日 下午5:58:49
* @Modified :houshuiqiang@163.com, 2017年10月2日
*/
public class NioDemoClient {
public static void main(String[] args) throws InterruptedException {
NioClient nioClient = new NioClient("192.168.10.47", 8181);
new Thread(nioClient, "nio-client-test").start();
for (int i = 0; i < 10; i++) {
nioClient.getQueue().offer("time" + i);
Thread.sleep(1000);
}
nioClient.stop();
}
}
class NioClient implements Runnable {
private Selector selector;
private SocketChannel socketChannel;
private String address;
private int port;
private volatile boolean stop;
private LinkedBlockingQueue<String> queue;
public NioClient(String address, int port){
this.address = address;
this.port = port;
this.stop = false;
queue = new LinkedBlockingQueue<String>();
try {
selector = Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
public BlockingQueue<String> getQueue(){
return queue;
}
public void stop(){
this.stop = true;
}
@Override
public void run(){
doConnect();
while (!stop) {
try {
selector.select(); // 阻塞等待
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
iterator.remove();
try{
handleKey(selectionKey);
}catch(IOException e){
e.printStackTrace();
if (selectionKey != null) {
selectionKey.cancel();
if (selectionKey.channel() != null) {
selectionKey.channel().close();
}
}
}catch(InterruptedException e){
e.printStackTrace();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
try {
socketChannel.close(); // 优雅关闭链接
selector.close(); // 直接selector.close()会关闭所有该seletor上的所有channel,但是服务器会接收到客户端强制关闭的错误信息。
} catch (IOException e) {
e.printStackTrace();
}
}
private void doConnect() {
try {
socketChannel.register(selector, SelectionKey.OP_CONNECT);
socketChannel.connect(new InetSocketAddress(address, port));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
private void handleKey(SelectionKey selectionKey) throws IOException, InterruptedException {
if (selectionKey.isValid()) {
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
if (selectionKey.isConnectable()) {
if (socketChannel.finishConnect()) {
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
if (selectionKey.isReadable()) {
String resultBody = getBodyFromSocketChannel(socketChannel);
if (null == resultBody) {
// 断开链路
selectionKey.cancel();
selectionKey.channel().close();
}else if ("".equals(resultBody)) {
// 心跳检测 ,忽略
}else{
System.out.println("received result : " + resultBody);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
if (selectionKey.isWritable()) {
sendRequest(socketChannel);
}
}
}
private void sendRequest(SocketChannel socketChannel) throws IOException, InterruptedException {
String requestBody = queue.poll(100, TimeUnit.MILLISECONDS);
if (null != requestBody) {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put(requestBody.getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
socketChannel.register(selector, SelectionKey.OP_READ);
if (! byteBuffer.hasRemaining()) {
System.out.println("send request to server : " + requestBody);
}
}else {
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
private String getBodyFromSocketChannel(SocketChannel socketChannel) throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int byteBufferSize = socketChannel.read(byteBuffer);
if (byteBufferSize == 0) { // 心跳检测,忽略
return "";
}else if (byteBufferSize > 0) {
byteBuffer.flip();
byte[] array = new byte[byteBuffer.remaining()];
byteBuffer.get(array);
return new String(array);
}else{
return null;
}
}
}
来源:oschina
链接:https://my.oschina.net/u/3466682/blog/1606632