• 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();
    	}
    }
  • 相关阅读:
    redis之windows安装
    ubuntu安装 ssh
    less乱码ESC显示问题
    linux 命令/目录 名称 英文单词 缩写 助记
    设计模式-策略模式
    设计模式-责任链模式
    ApplicationContexAware的作用
    ApplicationEvent事件机制
    mybatis注解版延迟加载、立即加载、一级缓存、二级缓存
    mybatis xml版延迟加载、立即加载、一级缓存、二级缓存
  • 原文地址:https://www.cnblogs.com/gmhappy/p/11864093.html
Copyright © 2020-2023  润新知