Java NIO之Reactor模型

Reactor模式思想:分而治之+事件驱动

1)分而治之

一个连接里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。

Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。

2)事件驱动

每个Task对应特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Handler执行。

3)角色

Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;

Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;

Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。

单Reactor单线程模型

优点:

  • 不需要做并发控制,代码实现简单清晰。

缺点:

  • 不能利用多核CPU;
  • 一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,处理成百上千的链路时性能上无法支撑;
  • 一旦reactor线程意外跑飞或者进入死循环,会导致整个系统通信模块不可用。

程序示例

package com.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;
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;

/**
 * Reactor线程
 * 
 * @author yaosht
 *
 */
public class ServerReactorThread implements Runnable {

	// 选择器
	public final Selector selector;
	// ServerSocketChannel
	public final ServerSocketChannel serverSocketChannel;

	/**
	 * 构造方法
	 * 
	 * @param port
	 * @throws IOException
	 */
	public ServerReactorThread(int port) throws IOException {
		// 打开一个选择器selector
		selector = Selector.open();
		// 通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象
		serverSocketChannel = ServerSocketChannel.open();

		InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
		// 端口地址
		serverSocketChannel.socket().bind(inetSocketAddress);

		// 配置ServerSocketChannel为非阻塞模式
		serverSocketChannel.configureBlocking(false);
		// 将该channel注册到selector上并设置关注OP_ACCEPT监听事件
		SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

		// 利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
		selectionKey.attach(new Acceptor());
		System.out.println("等待客户端的消息……");
	}

	@Override
	public void run() {
		try {
			while (true) {
				// 进行选择操作
				while (selector.select() > 0) {

					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iterator = keys.iterator();
					System.out.println("有关注的事件发生:" + keys.size());
					while (iterator.hasNext()) {

						SelectionKey selectionKey = iterator.next();
						// 派发事件处理
						dispatch(selectionKey);
						// 删除已选择集合中的键
						iterator.remove();

					}

				} // while
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 进行SelectionKey事件派发<br/>
	 * 获取attach附加的对象并进行方法调用:Acceptor或SocketHandler<br/>
	 * 
	 * @param key
	 */
	void dispatch(SelectionKey key) {
		if (key.isValid()) {
			if (key.isAcceptable()) {
				System.out.println("Acceptor");
				Acceptor acceptor = (Acceptor) (key.attachment());
				acceptor.handle(key);
			} else if (key.isReadable()) {
				System.out.println("SocketHandler");
				SocketHandler socketHandler = (SocketHandler) (key.attachment());
				socketHandler.handle(key);

			}
		}
	}

	/**
	 * 内部类Acceptor
	 * 
	 * @author yaosht
	 *
	 */
	class Acceptor {
		public void handle(SelectionKey key) {
			try {
				ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
				// 接收该channel上的socket连接
				SocketChannel socketChannel = serverSocketChannel.accept();
				if (socketChannel != null) {
					// 将该channel设置为非阻塞模式
					socketChannel.configureBlocking(false);
					// 将该channel注册到selector上并设置关注OP_READ事件
					SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
					// 调用SocketReadHandlerThread来处理channel的相关事件
					selectionKey.attach(new SocketHandler());
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
package com.reactor;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;

/**
 * socket读取数据处理线程<br/>
 * 在这里将读取、处理、发送放到一起了<br/>
 * 
 * @author yaosht
 *
 */
public class SocketHandler {
	/**
	 * 处理读取数据
	 */
	public void handle(SelectionKey key) {
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		try {
			// byteBuffer.clear();
			SocketChannel socketChannel = (SocketChannel) key.channel();
			socketChannel.read(byteBuffer);
			// 从缓冲区中get数据并进行解析计算
			byteBuffer.flip();
			byte[] bytes = new byte[byteBuffer.remaining()];
			byteBuffer.get(bytes);
			String formulaStr = new String(bytes);
			System.out.println("服务器端线程:" + Thread.currentThread().getName() + "处理客户端("
					+ socketChannel.getRemoteAddress().toString() + ")发送的公式:" + formulaStr);
			if (formulaStr.equals("end")) {
				System.out.println("客户端(" + socketChannel.getRemoteAddress().toString() + ")结束通信");
				socketChannel.close();
				key.cancel();
			} else if (!formulaStr.toString().contains("+")) {
				byteBuffer.clear();
				byteBuffer.put(("客户端(" + socketChannel.getRemoteAddress().toString() + ")发送不正确的加法公式").getBytes());
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			} else {
				// 进行计算,并将结果放入到缓冲区中
				String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
				String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
				int a = Integer.parseInt(stra);
				int b = Integer.parseInt(strb);
				int sum = a + b;
				byteBuffer.clear();
				System.out.println(
						"客户端(" + socketChannel.getRemoteAddress().toString() + ")公式计算结果:" + formulaStr + "=" + sum);
				byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
				// 从缓冲区中读取数据到通道发向客户端
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
package com.reactor;

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.Random;

/**
 * 客户端线程
 * 
 * @author yaosht
 *
 */
public class ClientThread implements Runnable {
	InetSocketAddress addr = null;

	public ClientThread(InetSocketAddress addr) {
		this.addr = addr;
	}

	@Override
	public void run() {
		String str;
		// 创建一个对象
		Random df = new Random();

		try {
			// 通过SocketChannel的静态方法open()创建一个SocketChannel对象
			SocketChannel socketChannel = SocketChannel.open();
			// 配置该socket为非阻塞模式
			socketChannel.configureBlocking(false);
			Selector selector = Selector.open();
			// SocketChannel连接服务器端
			// 因为前面设置的是非阻塞,所以调用这个方法会立即返回.
			// 因此如果返回true就不需要放到select中
			if (!socketChannel.connect(addr)) {
				// 向选择器selector注册该socketchannel并关注OP_CONNECT事件
				socketChannel.register(selector, SelectionKey.OP_CONNECT);
				// 进行选择
				selector.select();
				// 得到关注的事件集合
				Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = iterator.next();
					iterator.remove();
					if (selectionKey.isValid() && selectionKey.isConnectable()) {
						SocketChannel channel = (SocketChannel) selectionKey.channel();
						// 如果连接成功则完成连接
						if (channel.isConnectionPending()) {
							channel.finishConnect();
						}
						System.out.println(Thread.currentThread().getName() + "客户端连接成功");
						// 设置该socketChannel为非阻塞模式
						channel.configureBlocking(false);
						// 向选择器selector注册该socketchannel并关注OP_READ事件
						channel.register(selector, SelectionKey.OP_READ);
					}
				}
			}

			int formulaNum = 2;
			for (int j = 1; j <= formulaNum; j++) {
				// 产生两个加法随机数
				int num1 = df.nextInt(101);
				int num2 = df.nextInt(101);
				// 创建数据缓存区对象
				ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

				if (j == formulaNum) {
					str = "end";
					System.out.println(Thread.currentThread().getName() + "发送结束信号:" + str);
					// 将从终端读取到的数据放到缓冲区
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 读取缓冲区中的数据到通道中,发向服务端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);
				} else {
					// 拼接两个随机数产生加法公式
					str = num1 + "+" + num2;
					System.out.println(Thread.currentThread().getName() + "发送加法公式:" + str);
					// 将从终端读取到的数据放到缓冲区
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 读取缓冲区中的数据到通道中,发向服务端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);

					System.out.println("等待服务器端消息……");
					// 监听读事件,没有则阻塞
					selector.select();

					// 清除缓冲区
					byteBuffer.clear();
					// 读取通道中从服务器端来的数据到缓冲区中
					socketChannel.read(byteBuffer);

					byteBuffer.flip();
					// 通过缓冲区有效元素大小确定接收数组大小
					byte[] bytes = new byte[byteBuffer.remaining()];
					// 从缓冲区读取数据
					byteBuffer.get(bytes);
					String stringBuffer = new String(bytes);
					System.out.println(Thread.currentThread().getName() + "服务器端计算结果:" + stringBuffer);
				}
			}
			socketChannel.close();
			selector.close();
		} catch (Exception e) {
			System.out.println("异常:" + e);
		}

	}
}
package com.reactor;

import java.io.IOException;

/**
 * 启动服务器端线程
 * 
 * @author yaosht
 *
 */
public class StartServerReactor {

	public static void main(String[] args) throws IOException {
		new Thread(new ServerReactorThread(8080), "ServerReactorThread").start();
	}
}
package com.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;

/**
 * 客户端多线程启动
 * 
 * @author yaosht
 *
 */
public class StartClient {

	public static void main(String[] args) throws IOException {
		ClientThread clientThread = new ClientThread(new InetSocketAddress("127.0.0.1", 8080));
		for (int i = 0; i < 10; i++) {
			new Thread(clientThread, "Thread-" + i).start();
		}
	}
}

单Reactor多线程

特点:

  • 有专门一个reactor线程用于监听服务端ServerSocketChannel,接收客户端的TCP连接请求;
  • 网络IO的读/写操作等由一个worker reactor线程池负责,由线程池中的NIO线程负责监听SocketChannel事件,进行消息的读取、解码、编码和发送。
  • 一个NIO线程可以同时处理N条链路,但是一个链路只注册在一个NIO线程上处理,防止发生并发操作问题。

程序示例

package com.reactor;

import java.io.IOException;
import java.net.InetSocketAddress;
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;

/**
 * Reactor线程
 * 
 * @author yaosht
 *
 */
public class ServerReactorThread implements Runnable {

	// 选择器
	public final Selector selector;
	// ServerSocketChannel
	public final ServerSocketChannel serverSocketChannel;

	/**
	 * 构造方法
	 * 
	 * @param port
	 * @throws IOException
	 */
	public ServerReactorThread(int port) throws IOException {
		// 打开一个选择器selector
		selector = Selector.open();
		// 通过ServerSocketChannel 的open()方法创建一个ServerSocketChannel对象
		serverSocketChannel = ServerSocketChannel.open();

		InetSocketAddress inetSocketAddress = new InetSocketAddress(port);
		// 端口地址
		serverSocketChannel.socket().bind(inetSocketAddress);

		// 配置ServerSocketChannel为非阻塞模式
		serverSocketChannel.configureBlocking(false);
		// 将该channel注册到selector上并设置关注OP_ACCEPT监听事件
		SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

		// 利用selectionKey的attache功能绑定Acceptor 如果有事情,触发Acceptor
		selectionKey.attach(new Acceptor());
		System.out.println("等待客户端的消息……");
	}

	@Override
	public void run() {
		try {
			while (true) {
				// 进行选择操作
				while (selector.select() > 0) {
					Set<SelectionKey> keys = selector.selectedKeys();
					Iterator<SelectionKey> iterator = keys.iterator();
					System.out.println("有关注的事件发生:" + keys.size());
					while (iterator.hasNext()) {
						SelectionKey selectionKey = iterator.next();
						// 派发事件处理
						dispatch(selectionKey);
					}
					// 清除该集合中的所有元素
					keys.clear();

				} // while
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * 进行SelectionKey事件派发<br/>
	 * 获取attach附加的对象并进行方法调用:Acceptor或SocketHandler<br/>
	 * 
	 * @param key
	 */
	void dispatch(SelectionKey key) {
		if (key.isValid()) {
			Runnable runnable = (Runnable) key.attachment();
			// System.out.println(runnable.i);
			if (runnable != null) {
				runnable.run();
			}
		}
	}

	/**
	 * 内部类Acceptor
	 * 
	 * @author yaosht
	 *
	 */
	class Acceptor implements Runnable {
		@Override
		public void run() {
			try {
				// 接收该channel上的socket连接
				SocketChannel socketChannel = serverSocketChannel.accept();
				if (socketChannel != null) {
					// 为每一个连接上的socketChannel创建一个SocketHandler处理类
					new SocketHandler(socketChannel, selector);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
package com.reactor;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * socket读取数据处理线程<br/>
 * 在这里将读取、处理、发送放到一起了<br/>
 * 
 * @author yaosht
 *
 */
public class SocketHandler implements Runnable {
	// SocketChannel
	private SocketChannel socketChannel;
	// SelectionKey
	private SelectionKey selectionKey;

	// 这里使用了状态码来防止多线程出现数据不一致等问题
	// 该SocketChannel的事件在处理中
	static final int PROCESSING = 1;
	// 该SocketChannel的事件还在排队中
	static final int WAITING = 2;
	// volatile状态变量
	private volatile int state = WAITING;

	// 静态线程池
	public static final ExecutorService pool = Executors.newFixedThreadPool(16);

	public SocketHandler(SocketChannel socketChannel, Selector selector) throws IOException {

		this.socketChannel = socketChannel;

		// 将该channel设置为非阻塞模式
		socketChannel.configureBlocking(false);

		// 将该channel注册到selector上并设置关注OP_READ事件
		this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
		// attach(this)将自身对象绑定到key上,作用是使dispatch()函数正确使用
		selectionKey.attach(this);
		// Selector.wakeup()方法会使阻塞中的Selector.select()方法立刻返回
		selector.wakeup();
	}

	@Override
	public void run() {
		if (state == WAITING) {
			// 如果此时没有线程在处理该通道的本次读取,就提交申请到线程池进行读写操作
			pool.execute(new Process(selectionKey));
		} else {
			// 如果此时有线程正在进行读写操作,就直接return,选择器会进行下一次选择和任务分派
			System.out.println(this.socketChannel.toString() + "已经被处理中");
			return;
		}

	}

	/**
	 * 这是一个同步方法,因为在reactor中的选择器有可能会出现一种状况:
	 * 当process线程已经要对某通道进行读写的时候,有可能Selector会再次选择该通道
	 * 因为此时该process线程还并没有真正的进行读写,会导致另一线程重新创建一个process
	 * 并试图进行读写操作,此时就会出现cpu资源浪费的情况,或者出现异常,因为线程1在读取通道内容的时候
	 * 线程2就会被阻塞,而等到线程2执行操作的时候,线程1已经对通道完成了读写操做 因此可以通过设置对象状态码来防止出现这些问题
	 */
	private synchronized void handle(SelectionKey key) {
		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
		try {
			byteBuffer.clear();
			socketChannel.read(byteBuffer);
			// 从缓冲区中get数据并进行解析计算
			byteBuffer.flip();
			byte[] bytes = new byte[byteBuffer.remaining()];
			byteBuffer.get(bytes);
			String formulaStr = new String(bytes);
			System.out.println("服务器端线程:" + Thread.currentThread().getName() + "处理客户端("
					+ socketChannel.getRemoteAddress().toString() + ")发送的公式:" + formulaStr);
			if (formulaStr.equals("end")) {
				System.out.println("客户端(" + socketChannel.getRemoteAddress().toString() + ")结束通信");
				socketChannel.close();
				key.cancel();
			} else if (!formulaStr.toString().contains("+")) {
				byteBuffer.clear();
				byteBuffer.put(("客户端(" + socketChannel.getRemoteAddress().toString() + ")发送不正确的加法公式").getBytes());
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			} else {
				// 进行计算,并将结果放入到缓冲区中
				String stra = formulaStr.substring(0, formulaStr.indexOf("+"));
				String strb = formulaStr.substring(formulaStr.indexOf("+") + 1, formulaStr.length());
				int a = Integer.parseInt(stra);
				int b = Integer.parseInt(strb);
				int sum = a + b;
				byteBuffer.clear();
				System.out.println(
						"客户端(" + socketChannel.getRemoteAddress().toString() + ")公式计算结果:" + formulaStr + "=" + sum);
				byteBuffer.put(String.valueOf(formulaStr + "=" + sum).getBytes());
				// 从缓冲区中读取数据到通道发向客户端
				byteBuffer.flip();
				socketChannel.write(byteBuffer);
			}
			state = WAITING;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Handler中处理从通道读取数据到缓冲区并进行加法计算的内部线程处理类
	 * 
	 * @author yaosht
	 *
	 */
	class Process implements Runnable {

		private SelectionKey selectionKey;

		public Process(SelectionKey selectionKey) {
			this.selectionKey = selectionKey;
			state = PROCESSING;
		}

		@Override
		public void run() {
			handle(selectionKey);
		}
	}

}
package com.reactor;

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.Random;

/**
 * 客户端线程
 * 
 * @author yaosht
 *
 */
public class ClientThread implements Runnable {
	InetSocketAddress addr = null;

	public ClientThread(InetSocketAddress addr) {
		this.addr = addr;
	}

	@Override
	public void run() {
		String str;
		// 创建一个对象
		Random df = new Random();

		try {
			// 通过SocketChannel的静态方法open()创建一个SocketChannel对象
			SocketChannel socketChannel = SocketChannel.open();
			// 配置该socket为非阻塞模式
			socketChannel.configureBlocking(false);
			Selector selector = Selector.open();
			// SocketChannel连接服务器端
			// 因为前面设置的是非阻塞,所以调用这个方法会立即返回.
			// 因此如果返回true就不需要放到select中
			if (!socketChannel.connect(addr)) {
				// 向选择器selector注册该socketchannel并关注OP_CONNECT事件
				socketChannel.register(selector, SelectionKey.OP_CONNECT);
				// 进行选择
				selector.select();
				// 得到关注的事件集合
				Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
				while (iterator.hasNext()) {
					SelectionKey selectionKey = iterator.next();
					iterator.remove();
					if (selectionKey.isValid() && selectionKey.isConnectable()) {
						SocketChannel channel = (SocketChannel) selectionKey.channel();
						// 如果连接成功则完成连接
						if (channel.isConnectionPending()) {
							channel.finishConnect();
						}
						System.out.println(Thread.currentThread().getName() + "客户端连接成功");
						// 设置该socketChannel为非阻塞模式
						channel.configureBlocking(false);
						// 向选择器selector注册该socketchannel并关注OP_READ事件
						channel.register(selector, SelectionKey.OP_READ);
					}
				}
			}

			int formulaNum = 2;
			for (int j = 1; j <= formulaNum; j++) {
				// 产生两个加法随机数
				int num1 = df.nextInt(101);
				int num2 = df.nextInt(101);
				// 创建数据缓存区对象
				ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

				if (j == formulaNum) {
					str = "end";
					System.out.println(Thread.currentThread().getName() + "发送结束信号:" + str);
					// 将从终端读取到的数据放到缓冲区
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 读取缓冲区中的数据到通道中,发向服务端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);
				} else {
					// 拼接两个随机数产生加法公式
					str = num1 + "+" + num2;
					System.out.println(Thread.currentThread().getName() + "发送加法公式:" + str);
					// 将从终端读取到的数据放到缓冲区
					byteBuffer.clear();
					byteBuffer.put(str.getBytes());
					// 读取缓冲区中的数据到通道中,发向服务端
					byteBuffer.flip();
					socketChannel.write(byteBuffer);

					System.out.println("等待服务器端消息……");
					// 监听读事件,没有则阻塞
					selector.select();

					// 清除缓冲区
					byteBuffer.clear();
					// 读取通道中从服务器端来的数据到缓冲区中
					socketChannel.read(byteBuffer);

					byteBuffer.flip();
					// 通过缓冲区有效元素大小确定接收数组大小
					byte[] bytes = new byte[byteBuffer.remaining()];
					// 从缓冲区读取数据
					byteBuffer.get(bytes);
					String stringBuffer = new String(bytes);
					System.out.println(Thread.currentThread().getName() + "服务器端计算结果:" + stringBuffer);
				}
			}
			socketChannel.close();
			selector.close();
		} catch (Exception e) {
			System.out.println("异常:" + e);
		}

	}
}

 客户端启动与服务器端启动代码一样,就不贴了

主从Reactor多线程

在绝大多数场景下,Reactor多线程模型都可以满足性能需求;但是在极个别特殊场景中,一个NIO线程负责监听和处理所有的客户端连接可能会存在性能问题。

特点:

  • 服务端用于接收客户端连接的不再是个1个单独的reactor线程,而是一个boss reactor线程池;
  • 服务端启用多个ServerSocketChannel监听不同端口时,每个ServerSocketChannel的监听工作可以由线程池中的一个NIO线程完成。

参考netty学习系列二:NIO Reactor模型 & Netty线程模型

Java NIO之Reactor模式

JavaNIO--4.多线程Reactor模式

发布了243 篇原创文章 · 获赞 138 · 访问量 138万+

猜你喜欢

转载自blog.csdn.net/ystyaoshengting/article/details/104156485
今日推荐