java 高性能Server —— Reactor模型单线程版

NIO模型

NIO模型示例如下:

  • Acceptor注册Selector,监听accept事件
  • 当客户端连接后,触发accept事件
  • 服务器构建对应的Channel,并在其上注册Selector,监听读写事件
  • 当发生读写事件后,进行相应的读写处理

NIO优缺点

  • 优点
    • 性能瓶颈高
  • 缺点
    • 模型复杂
    • 编码复杂
    • 需处理半包问题

NIO的优缺点和BIO就完全相反了!性能高,不用一个连接就建一个线程,可以一个线程处理所有的连接!相应的,编码就复杂很多,从上面的代码就可以明显体会到了。还有一个问题,由于是非阻塞的,应用无法知道什么时候消息读完了,就存在了半包问题!

半包问题

简单看一下下面的图就能理解半包问题了!

我们知道TCP/IP在发送消息的时候,可能会拆包(如上图1)!这就导致接收端无法知道什么时候收到的数据是一个完整的数据。例如:发送端分别发送了ABC,DEF,GHI三条信息,发送时被拆成了AB,CDRFG,H,I这四个包进行发送,接受端如何将其进行还原呢?在BIO模型中,当读不到数据后会阻塞,而NIO中不会!所以需要自行进行处理!例如,以换行符作为判断依据,或者定长消息发生,或者自定义协议!

NIO虽然性能高,但是编码复杂,且需要处理半包问题!为了方便的进行NIO开发,就有了Reactor模型!

Reactor模型

Reactor中的组件

  • Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;
  • Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;
  • Acceptor:Handler的一种,绑定了ACCEPT事件,当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。

对应上面的NIO代码来看:

  • Reactor:相当于有分发功能的Selector
  • Acceptor:NIO中建立连接的那个判断分支
  • Handler:消息读写处理等操作类

代码实现

服务端 Reactor.java

package com.test1;

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;

public class Reactor implements Runnable {

	private ServerSocketChannel serverSocketChannel = null;

	private Selector selector = null;

	public Reactor() {
		try {
			selector = Selector.open();
			serverSocketChannel = ServerSocketChannel.open();
			serverSocketChannel.configureBlocking(false);
			serverSocketChannel.socket().bind(new InetSocketAddress(8888));
			SelectionKey selectionKey = serverSocketChannel.register(selector,
					SelectionKey.OP_ACCEPT);
			selectionKey.attach(new Acceptor());
			System.out.println("服务器启动正常!");
		} catch (IOException e) {
			System.out.println("启动服务器时出现异常!");
			e.printStackTrace();
		}
	}

	public void run() {
		while (true) {
			try {
				selector.select();

				Iterator<SelectionKey> iter = selector.selectedKeys()
						.iterator();
				while (iter.hasNext()) {
					SelectionKey selectionKey = iter.next();
					dispatch((Runnable) selectionKey.attachment());
					iter.remove();
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public void dispatch(Runnable runnable) {
		if (runnable != null) {
			runnable.run();
		}
	}

	public static void main(String[] args) {
		new Thread(new Reactor()).start();
	}

	class Acceptor implements Runnable {
		public void run() {
			try {
				SocketChannel socketChannel = serverSocketChannel.accept();
				if (socketChannel != null) {
					System.out.println("接收到来自客户端("
							+ socketChannel.socket().getInetAddress()
									.getHostAddress() + ")的连接");
					new Handler(selector, socketChannel);
				}

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

class Handler implements Runnable {

	private static final int READ_STATUS = 1;

	private static final int WRITE_STATUS = 2;

	private SocketChannel socketChannel;

	private SelectionKey selectionKey;

	private int status = READ_STATUS;

	public Handler(Selector selector, SocketChannel socketChannel) {
		this.socketChannel = socketChannel;
		try {
			socketChannel.configureBlocking(false);
			selectionKey = socketChannel.register(selector, 0);
			selectionKey.interestOps(SelectionKey.OP_READ);
			selectionKey.attach(this);
			selector.wakeup();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void run() {
		try {
			if (status == READ_STATUS) {
				read();
				selectionKey.interestOps(SelectionKey.OP_WRITE);
				status = WRITE_STATUS;
			} else if (status == WRITE_STATUS) {
				process();
				selectionKey.cancel();
				System.out.println("服务器发送消息成功!");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void read() throws IOException {
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		socketChannel.read(buffer);
		System.out.println("接收到来自客户端("
				+ socketChannel.socket().getInetAddress().getHostAddress()
				+ ")的消息:" + new String(buffer.array()));
	}

	public void process() throws IOException {
		String content = "Hello World!";
		ByteBuffer buffer = ByteBuffer.wrap(content.getBytes());
		socketChannel.write(buffer);
	}
}

客户端 Client.java

package com.test1;

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.Scanner;
import java.util.Set;

public class Client {

	ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
	ByteBuffer readBuffer = ByteBuffer.allocate(1024);

	public void start() throws IOException {
		// 打开socket通道
		SocketChannel sc = SocketChannel.open();
		// 设置为非阻塞
		sc.configureBlocking(false);
		// 连接服务器地址和端口
		//sc.connect(new InetSocketAddress("localhost", 8001));
		sc.connect(new InetSocketAddress("localhost", 8888));
		// 打开选择器
		Selector selector = Selector.open();
		// 注册连接服务器socket的动作
		sc.register(selector, SelectionKey.OP_CONNECT);

		Scanner scanner = new Scanner(System.in);
		while (true) {
			// 选择一组键,其相应的通道已为 I/O 操作准备就绪。
			// 此方法执行处于阻塞模式的选择操作。
			selector.select();
			// 返回此选择器的已选择键集。
			Set<SelectionKey> keys = selector.selectedKeys();
			System.out.println("keys=" + keys.size());
			Iterator<SelectionKey> keyIterator = keys.iterator();
			while (keyIterator.hasNext()) {
				SelectionKey key = keyIterator.next();
				keyIterator.remove();
				// 判断此通道上是否正在进行连接操作。
				if (key.isConnectable()) {
					sc.finishConnect();
					sc.register(selector, SelectionKey.OP_WRITE);
					System.out.println("server connected...");
					break;
				} else if (key.isWritable()) { // 写数据
					System.out.print("please input message:");
					String message = scanner.nextLine();
					// ByteBuffer writeBuffer =
					// ByteBuffer.wrap(message.getBytes());
					writeBuffer.clear();
					writeBuffer.put(message.getBytes());
					// 将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
					writeBuffer.flip();
					sc.write(writeBuffer);

					// 注册写操作,每个chanel只能注册一个操作,最后注册的一个生效
					// 如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来
					// int interestSet = SelectionKey.OP_READ |
					// SelectionKey.OP_WRITE;
					// 使用interest集合
					sc.register(selector, SelectionKey.OP_READ);
					sc.register(selector, SelectionKey.OP_WRITE);
					sc.register(selector, SelectionKey.OP_READ);

				} else if (key.isReadable()) {// 读取数据
					System.out.print("receive message:");
					SocketChannel client = (SocketChannel) key.channel();
					// 将缓冲区清空以备下次读取
					readBuffer.clear();
					int num = client.read(readBuffer);
					System.out.println(new String(readBuffer.array(), 0, num));
					// 注册读操作,下一次读取
					sc.register(selector, SelectionKey.OP_WRITE);
				}
			}
		}
	}

	public static void main(String[] args) throws IOException {
		new Client().start();
	}
}

 

posted on 2018-08-28 11:51  疯狂的小萝卜头  阅读(184)  评论(0编辑  收藏  举报