Reactor模式

最经典的每个请求一个线程的模式并非没有用处,如tomcat依然使用着该模式,在某些场景下,每个请求开启一个线程的吞吐量可能还高于使用io多路复用模型。如果说服务器需要支持超大量的长期连接,10K连接,并且各个客户端不会频繁发送数据,如一个中心服务器接收全国便利店的收银机交易信息,这个就很适合使用nio。另外,在局域网下使用select/poll可能要好于epoll,针对不同的场景合理使用技术,避免过早的优化带来更多的问题,专注于业务是好的编程方式。

线程的中断:有很多方法会阻塞线程,比如Thread.join(),Object.wait(),Thread.sleep(),Selector.select()等,有时候有必要让一个线程从阻塞中唤醒,这时候就需要中断。

Thread.java

void interrupt()

Interrupts this thread.

 

static boolean interrupted()

Tests whether the current thread has been interrupted.

 

boolean isInterrupted()

Tests whether this thread has been interrupted.

io多路复用,首先了解几个类和方法

Selector

A multiplexor of SelectableChannel objects.

SelectableChannel 的多路复用器

一个channel可以被注册到多个选择器上面,比如我们开启了一个ServerSocketChannel,可以注册到多个Selector上,然后收到client连接的时候,可以轮询注册到这些selector上。虽然服务器性能很强大,但是使用一万个线程维持一万个长连接也是不现实的事,如果这一万个连接活动数很少,此种情况使用nio将会非常合适,比如每一秒活动数只有几十个几百个,那么使用区区几个线程就能很好的处理这些连接。

select()方法

阻塞,如下三种情况发生才返回

channel is selected 、this selector's wakeup method is invoked、or the current thread is interrupted

wakeup()方法可以唤醒阻塞,使用的方式在win平台和unix平台不一样,在win平台使用两个互相连接的socket模拟Pipe,然后通过在sink端写入数据来唤醒select(),而在linux是通过pipe实现的(未验证,随后验证)

 

SelectKey

attach(Object ob)

Attaches the given object to this key.

attachment()

Retrieves the current attachment.

cancel()请求当前key对应的channel从它注册的选择器上注销

Requests that the registration of this key's channel with its selector be cancelled.

 

 

单线程模式的Reactor

cb6cd722db158b68f6a91e89a6905cf4821.jpg

借助于Selector,可以在单个线程中处理连接,读取,处理,回写等操作,没有线程切换,如果处理数据的开销比较小,活动连接比较少,那么单个线程完全可以支持几百上千的连接。

Reactor.java

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;
public class Reactor implements Runnable {
    final Selector selector;
    final ServerSocketChannel serverSocket;
    public Reactor(int port) throws IOException {
        selector = Selector.open();
        serverSocket = ServerSocketChannel.open();
        serverSocket.socket().bind(new InetSocketAddress(port));
        serverSocket.configureBlocking(false);
        SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
        sk.attach(new Acceptor());
    }
    public static void main(String[] args) {
        try {
            Reactor reactor = new Reactor(9999);
            Thread r = new Thread(reactor);
            r.start();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                selector.select();
                Set selected = selector.selectedKeys();
                Iterator it = selected.iterator();
                while (it.hasNext()) {
                    dispatch((SelectionKey) it.next());
//it.remove();
                }
                selected.clear();
            }
            System.out.println("exit");
        } catch (Exception e) {
            System.out.println("exception exit");
        }
    }
    void dispatch(SelectionKey k) {
        Runnable r = (Runnable) k.attachment();
        if (r != null) {
            r.run();
        }
    }
    class Acceptor implements Runnable {
        @Override
        public void run() {
            try {
                SocketChannel c = serverSocket.accept();
                if (c != null) {
                    new Handler(selector, c);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

Handler.java

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
final class Handler implements Runnable{
    final SocketChannel socket;
    final SelectionKey sk;
    ByteBuffer input = ByteBuffer.allocate(1024*10);
    ByteBuffer output = ByteBuffer.allocate(1024*10);
    static final int READING = 0,SENDING = 1;
    int state = READING;
    Handler(Selector sel, SocketChannel c) throws IOException {
        socket = c;
        c.configureBlocking(false);
        sk = socket.register(sel,0);
        sk.attach(this);
        sk.interestOps(SelectionKey.OP_READ);
    }
    @Override
    public void run() {
        try{
            if(state == READING){
                read();
            }else if(state == SENDING){
                send();
            }
        }catch (Exception e){

        }
    }
    void read() throws IOException {
        socket.read(input);
        if(inputIsComplete()){
            process();
            state = SENDING;
            sk.interestOps(SelectionKey.OP_WRITE);
        }
    }
    void send() throws IOException {
        input.flip();
        socket.write(input);
        if(outputIsComplete()){
            input.clear();
            socket.close();
        }
    }
    boolean inputIsComplete(){
//判断是否读取到一个完整的消息
        return true;
    }
    boolean outputIsComplete(){
        return true;
    }
    void process(){
    }
}

单线程版本的缺陷也很明显,如果处理数据比较耗时,如果瞬间并发量增大,那么单线程是不能很好的工作,客户端连接超时,

然后发起重连,最终会导致服务端堆积很多待处理连接。

 

此处有个小小的疑问:SocketChannel client = serverSocket.accept();为什么要判断一下client是否是null呢,

根据api的解释,由于serversocketchannel是非阻塞的,accpet方法无论是否收到连接请求都会立刻返回,如果连接队列上有值,则从队列中取一个,如果没有连接,则返回null,可是由于serversocketchannle注册的对OP_ACCEPT事件感兴趣并且已经被此事件激活,为什么还会返回null呢。查看了源码,如果收到syn为激活事件,那么在ack+syn发送并且收到ack才视为连接就绪,连接就绪之后才加入到队列中,那么会有这种情况发生。但是这只是猜测,源码比较复杂,随着技术的积累,总能搞明白这个问题。

多Reactor模式

首先,需要将非io操作分离出去,其次,有多个Reactor让IO饱和,现在有一个mainReactor负责接收请求,有多个subReactor

负责处理连接。需要注意的是,selector在执行select方法时,当前线程被阻塞,此时不能在另一个线程调用SocketChannel.

register(selector,interest op),注册的时候需要占用同步锁,而select的时候已经进入了同步块,所以需要首先wakeup,

然后让selector调用这个socketchannel的注册。也可以使用阻塞队列或者任务队列等模式,mainReactor收到连接封装成任务放到阻塞队列中

然后subReactor从select中返回,就可以从队列中取任务,进行r/w操作。这个实现起来很简单。

附:netty的ServerBootstrap可以配置为多个reactor或者单个reactor模式,使用多个的时候,parent收到连接然后调用子

reactor去注册连接,然后剩余的读写操作都有子reactor去完成。可以查看ServerBootstrap的源码。子reactor可能会阻塞在

select()方法上,由于nioeventloop也是一个executor,所以在select之后会执行任务列表中的任务,这样就能完成注册了。

转载于:https://my.oschina.net/wuxiaofei/blog/2056311

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值