NIO+reactor模式的网络服务器设计方案

第一种方案:采用基于异步事件通知的reactor模式仅仅用一个线程来并发的为多个连接服务;

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

  • 分而治之:

一个connection里发生的完整的网络处理过程一般分为accept、read、decode、compute、encode、send这几步。Reactor将每个步骤映射为一个task,服务端的线程执行的最小逻辑单元不再是一次完整的网络处理过程,而是更小的task,且采用非阻塞的执行方式;

  • 事件驱动:

每个task对应一个特定的事件,当task准备就绪时,对应的事件通知就会发出。Reactor收到事件后,分发给绑定了对应的事件的Handler执行task;


下图描述了单线程版本的reactor模式结构图:

关键概念:

  • Reactor:负责响应事件,分发给绑定了该事件的handler执行task
  • Handler:绑定了某类事件,负责执行该事件对应的task
  • Acceptor:Handler的一种,绑定了connect事件。它在系统启动的时候就已经绑定了connect事件,并注册到reactor中。当有客户端发起connect请求时,reactor会收到accept事件,然后分发给acceptor。acceptor首先会accept新的连接,然后新建一个handler,将其与该连接上的read事件绑定,并注册到reator中

2、基于reactor的网络交互

客户端连接服务器过程:

  1. 服务器将绑定了accept事件的Acceptor注册到Reactor中,准备accept新的connection;
  2. 服务器启动Reactor的事件循环处理功能(注意:该循环会阻塞,直到收到事件);
  3. 客户端connect服务器;
  4. Reactor响应accept事件,分发给Acceptor,Acceptor确定建立一个新的连接;
  5. Acceptor创建一个handler专门服务该connection后续的请求;
  6. Handler绑定该connection的read事件,并将自己注册到Reactor中;

服务器处理客户端请求过程

  1. 客户端发送请求;
  2. 当客户端的请求数据到达服务器时,Reactor响应read事件,分发给绑定了read事件的handler;
  3. Handler执行task,读取客户端的请求数据(此处是非阻塞读,即如果当前操作会导致当前线程阻塞,当前操作会立即返回,并重复执行第2、3步,直到客户端的请求读取完毕);
  4. 解析客户端请求数据;
  5. 读取文件;
  6. Handler重新绑定write事件;
  7. 当connection可以开始write的时候,Reactor响应write事件,分发给绑定了write事件的handler;
  8. Handler执行task,向客户端发送文件(此处是非阻塞写,即如果当前操作会导致当前线程阻塞,当前操作会立即返回,并重复执行第7、8步,直到文件全部发送完毕);

注意:上述两个过程都是在服务器的一个线程里完成的,该线程响应所有客户端的请求。譬如服务端在处理客户端A的请求时,如果在第2步read事件还没有就绪(或者在第3步读取数据的时候发生阻塞了),则该线程会立即重新回到客户端连接服务器过程的第2步(即事件循环处理),等待新的事件通知。如果此时客户端B请求连接,则该线程会响应B的连接请求,这样就实现了一个线程同时为多个连接服务的效果。

3、NIO的几个关键概念

  • Selector:

Reactor里的一个核心组成部分,通过调用selector.select()方法,可以知道感兴趣的IO事件里哪个已经ready,该方法是阻塞的,直到有IO事件ready;通过调用selector.selectedKeys()方法,可以获取到selectionKey对象,这些对象关联已经ready的IO事件。

  • SelectionKey:

当selector注册一个channel时,会产生一个该对象,譬如SelectionKey selectionKey = channel.register(selector, selectionKey.OP_ACCEPT)。它维护着channel、selector、IO事件、Handler之间的关系。通过调用attach方法,可以绑定一个handler,譬如:selectionKey.attach(new Acceptor())。

  • ServerSocketChannel:

类似于ServerSocket,唯一的区别在于:ServerSocketChannel可以使用selector,而且可以设置为非阻塞模式。

  • SocketChannel:

类似于Socket,唯一的区别在于:SocketChannel可以使用selector,而且可以设置为非阻塞模式

  • ByteBuffer:数据缓冲器,是NIO里将数据移入移出channel的唯一方式。

3、Code

服务端代码:

import java.io.IOException;
import java.net.InetAddress;
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.nio.channels.spi.SelectorProvider;
import java.util.Iterator;

/**
 * NIO服务端
 * 单线程代码如下(单线程版本)
 */
public class NioServer implements Runnable {

	private InetAddress hostAddress;
	private int port;

	private ServerSocketChannel serverChannel;

	private Selector selector;

	public NioServer(InetAddress hostAddress, int port) throws IOException {
		this.hostAddress = hostAddress;
		this.port = port;
		// 初始化selector,绑定服务端监听套接字、感兴趣事件及对应的handler
		this.selector = initSelector();
	}

	private Selector initSelector() throws IOException {
		// 创建一个selector
		Selector socketSelector = SelectorProvider.provider().openSelector();
		// 创建并打开SeverSocketChannel
		serverChannel = ServerSocketChannel.open();
		// 设置为非阻塞
		serverChannel.configureBlocking(false);
		// 绑定端口
		serverChannel.socket().bind(new InetSocketAddress(hostAddress, port));
		// 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听客户端连接事件
		SelectionKey selecttionKey = serverChannel.register(socketSelector,
				SelectionKey.OP_ACCEPT);
		// 绑定handler
		selecttionKey.attach(new Acceptor());
		return socketSelector;
	}

	/**
	 * 处理OP_ACCEPT事件的handler
	 */
	class Acceptor implements Runnable {

		@Override
		public void run() {
			try {
				accept();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

	}

	private void accept() throws IOException {
		System.out.println("connect");
		// 建立连接
		SocketChannel socketChannel = serverChannel.accept();
		System.out.println("connected");
		// 设置为非阻塞
		socketChannel.configureBlocking(false);
		// 创建Handler,专门处理该连接后续发生的OP_READ和OP_WRITE事件
		new Handler(selector, socketChannel);
	}

	@Override
	public void run() {
		while (true) {
			try {
				// 选择事件已经ready的selectionKey,该方法是阻塞的,只有至少存在selectionKey
				// 或者wakeup方法被调用,或者当前线程被中断,才返回
				selector.select();
				// 循环处理每一个事件
				Iterator<SelectionKey> items = selector.selectedKeys()
						.iterator();
				while(items.hasNext()){
					SelectionKey key = items.next();
					items.remove();
					if(!key.isValid()){
						continue;
					}
					//事件处理分发
					dispatch(key);
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	private void dispatch(SelectionKey sk){
		// 获取绑定的handler
		Runnable r = (Runnable) sk.attachment();
		if(r != null){
			r.run();
		}
	}
	
	public static void main(String[] args) {
		try {
			// 启动服务器
			new Thread(new NioServer(null, 9090)).start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}

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


public class Handler implements Runnable {
	
	final SocketChannel socketChannel;
	final SelectionKey key;
	static final int MAXIN = 8192, MAXOUT = 11240 * 1024;
	ByteBuffer readBuffer = ByteBuffer.allocate(MAXIN);
	ByteBuffer outBuffer = ByteBuffer.allocate(MAXOUT);
	static final int READING = 0;
	static final int SENDING = 1;
	int state = READING;
	
	public Handler(Selector selector, SocketChannel socketChannel) throws ClosedChannelException{
		this.socketChannel = socketChannel;
		// 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听该连接上的read事件
		this.key = socketChannel.register(selector, SelectionKey.OP_READ);
		// 绑定handler
		this.key.attach(this);
	}
	
	/**
	 * 处理write
	 * @throws IOException
	 */
	private void write() throws IOException{
		this.socketChannel.write(outBuffer);
		if(outBuffer.remaining() > 0){
			return;
		}
		state = READING;
		key.interestOps(SelectionKey.OP_READ);
	}
	
	/**
	 * 处理read
	 * @throws IOException
	 */
	private void read() throws IOException{
		readBuffer.clear();
		int numRead;
		try {
			numRead = this.socketChannel.read(readBuffer);
		} catch (IOException e) {
			key.cancel();
			this.socketChannel.close();
			return;
		}
		if(numRead == -1){
			this.socketChannel.close();
			key.cancel();
			return;
		}
		// 处理数据
		process(numRead);
	}
	
	/**
	 * 处理数据
	 * @param numRead
	 */
	private void process(int numRead){
		byte[] dataCopy = new byte[numRead];
		System.arraycopy(readBuffer.array(), 0, dataCopy, 0, numRead);
		System.out.println(new String(dataCopy));
		outBuffer = ByteBuffer.wrap(dataCopy);
		state = SENDING;
		// 设置Key的interest set为监听该连接上的write事件
		key.interestOps(SelectionKey.OP_WRITE);
	}
	
	@Override
	public void run() {
		try {
			if(state == READING){
				read();
			}else if(state ==  SENDING){
				write();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

客户端代码:

import java.io.IOException;
import java.net.InetAddress;
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.nio.channels.spi.SelectorProvider;
import java.util.Iterator;

/**
 * Nio客户端
 * 
 * @author soft
 * 
 */
public class NioClient implements Runnable {

	private InetAddress hostAddress;
	private int port;
	private Selector selector;
	private ByteBuffer readBuffer = ByteBuffer.allocate(8192);
	private ByteBuffer outBuffer = ByteBuffer.wrap(("nice to meet you")
			.getBytes());

	public NioClient(InetAddress hostAddress, int port) throws IOException {
		this.hostAddress = hostAddress;
		this.port = port;
		this.initSelector();
	}

	private void initSelector() throws IOException {
		// 创建一个selector
		selector = SelectorProvider.provider().openSelector();
		// 打开socketChannel
		SocketChannel socketChannel = SocketChannel.open();
		// 设置为非阻塞
		socketChannel.configureBlocking(false);
		// 连接指定IP和端口的地址
		socketChannel
				.connect(new InetSocketAddress(this.hostAddress, this.port));
		// 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听服务端
		// 已建立连接的事件
		socketChannel.register(selector, SelectionKey.OP_CONNECT);
	}

	public static void main(String[] args) {
		try {
			NioClient client = new NioClient(
					InetAddress.getByName("localhost"), 9090);
			new Thread(client).start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		while (true) {
			try {
				this.selector.select();
				Iterator<?> selectedKeys = this.selector.selectedKeys()
						.iterator();
				while (selectedKeys.hasNext()) {
					SelectionKey key = (SelectionKey) selectedKeys.next();
					selectedKeys.remove();

					if (!key.isValid()) {
						continue;
					}

					if (key.isConnectable()) {
						this.finishConnection(key);
					} else if (key.isReadable()) {
						read(key);
					} else if (key.isWritable()) {
						write(key);
					}
				}
			} catch (Exception e) {
			}
		}
	}

	private void finishConnection(SelectionKey key) {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		try {
			// 判断连接是否建立成功,不成功会抛异常
			socketChannel.finishConnect();
		} catch (IOException e) {
			key.cancel();
			return;
		}
		// 设置Key的interes set为OP_WRITE事件
		key.interestOps(SelectionKey.OP_WRITE);
	}

	/**
	 * 处理read
	 * 
	 * @param key
	 * @throws IOException
	 */
	private void read(SelectionKey key) throws IOException {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		this.readBuffer.clear();
		int numRead;
		try {
			numRead = socketChannel.read(readBuffer);
		} catch (Exception e) {
			key.cancel();
			socketChannel.close();
			return;
		}
		if (numRead == 1) {
			System.out.println("close connection");
			socketChannel.close();
			key.cancel();
			return;
		}
		// 处理响应
		this.handleResponse(socketChannel, readBuffer.array(), numRead);
	}

	/**
	 * 处理响应
	 * @param socketChannel
	 * @param data
	 * @param numRead
	 * @throws IOException 
	 */
	private void handleResponse(SocketChannel socketChannel, byte[] data,
			int numRead) throws IOException {
		byte[] rspData = new byte[numRead];
		System.arraycopy(data, 0, rspData, 0, numRead);
		System.out.println(new String(rspData));
		socketChannel.close();
		socketChannel.keyFor(selector).cancel();
	}
	
	/**
	 * 处理write
	 * @param key
	 * @throws IOException 
	 */
	private void write(SelectionKey key) throws IOException{
		SocketChannel socketChannel = (SocketChannel) key.channel();
		socketChannel.write(outBuffer);
		if(outBuffer.remaining() > 0){
			return;
		}
		// 设置Key的interest set为OP_READ事件
		key.interestOps(SelectionKey.OP_READ);
	}
	
}

4、优缺点

单线程版本的Reactor最大的优势是:不需要做并发控制,简化了实现。缺点是不能充分利用多核CPU的优势,因为只有一个线程,该线程需要执行所有的操作:accept、read、decode、compute、encode、send,而其中的decode、compute、encode如果很耗时,则该线程就不能及时的响应其他客户端的请求。


第二种方案,Worker threads;

Reactor所在的线程只需要专心的响应客户端的请求:accept、read、send。对数据的具体处理过程则交给另外的线程池。这样可以提高服务端对客户端的响应速度,但同时增加了复杂度,也没有充分利用到多核的优势,因为reactor只有一个,譬如同一时刻只能read一个客户端的请求数据。


第三种方案,Multiple reactor threads;

采用多个reactor,每个reactor都在自己单独的线程里执行。如果是多核,则可以同时响应多个客户端的请求。(Netty采用的类似这种方式,boss线程池就是多个MainReactor,worker线程池就是多个subReactor)













  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小,或重叠度很大,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)需要满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值