Reactor经典模式 -单线程

1.流程图

在这里插入图片描述
client 指的是连接的客户端
reactor指的是总的调度类 监听到符合监听条件的selectionkey会调用dispatch分发出去
acceptor指的是管理处理连接状态的组件
黄色的部分分别代表读、解码、处理、编码、发送。这部分可以用handler来代表,处理的是读写的操作,也是整个流程里面最耗时的操作。
单线程模式主要代表的是所有的操作都是在一个线程里面完成。
下面是各个组件的简单代码实现。(都是我自己在IDEA里面跑过的)

2.Reactor代码实现

 public class TcpReactor {

    private ServerSocketChannel serverSocketChannel;
    private Selector selector ;

    public TcpReactor(int port) throws IOException {
        serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);
        selector = Selector.open();
        SelectionKey selectionKey = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        selectionKey.attach(new Acceptor(selector,serverSocketChannel));
     }

     public  void run(){
         try {
             while (true){
                 System.out.println("监听"+serverSocketChannel.socket().getLocalPort()+"端口的信息");
                 //selector.select方法时阻塞的 如果不向阻塞可以加上时间参数
                 if (selector.select()!=0){
                     Set<SelectionKey> keys = selector.selectedKeys();
                     Iterator<SelectionKey> iterators = keys.iterator();
                     System.out.println("keys"+ JSON.toJSONString(keys));
                     while (iterators.hasNext()){
                         SelectionKey selectionKey = iterators.next();
                         System.out.println(selectionKey.isReadable());
                         dispatch(selectionKey);
                         iterators.remove();
                     }
                 }
             }
         } catch (IOException e) {
             e.printStackTrace();
         }finally {
             IOUtils.closeQuietly(serverSocketChannel);
             IOUtils.closeQuietly(selector);
         }

     }
    private void dispatch(SelectionKey next) {
        //这里是使用的多态的一种思想 就是会自动根据子类的类型去执行子类或者实现类里面的方法
        // 开始看网上的例子就是没想到多态....................
        Procrss procrss = (Procrss) next.attachment();
        try {
            procrss.process();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

3.accptor和handler都实现的接口类

public interface Procrss {
//对当前状态下的selectionKey和socketChannel进行操作
  void process () throws  Exception;
}

4.acceptor的实现类

public class Acceptor implements Procrss {

   private final  Selector selector;

   private final  ServerSocketChannel serverSocketChannel;

   public Acceptor(Selector selector, ServerSocketChannel serverSocketChannel) {
       this.selector =selector;
       this.serverSocketChannel = serverSocketChannel;
   }
   @Override
   public void process() throws IOException {
       SocketChannel socketChannel = serverSocketChannel.accept();
       if (socketChannel!=null){
           socketChannel.configureBlocking(false);
           SelectionKey selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
           selectionKey.attach(new TcpHanler(selectionKey,socketChannel));
       }
   }
}

5handler的实现类

public class TcpHanler implements  Procrss {

    private SelectionKey selectionKey;

    private SocketChannel socketChannel;

    private int state =0;

    public TcpHanler(SelectionKey selectionKey, SocketChannel socketChannel) {
        this.selectionKey = selectionKey;
        this.socketChannel=socketChannel;
    }
    @Override
    public  void process(){
        try {
              readAndSend();
        } catch (Exception e) {
            e.printStackTrace();
            //如果服务端关闭了就会出现java.net.SocketException: Software caused connection abort: recv failed异常
            IOUtils.closeQuietly(socketChannel);
        }
    }


    private void readAndSend() throws IOException {
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        int read = socketChannel.read(byteBuffer);
        //这里需要注意 read的返回值问题
        //如果对方主动关闭了socket 那么会返回-1
        //如果对方没有主动关闭 它不会返回-1

        //有三种情况会返回0
        // 1.socketChannel里面没有数据可读了
        // 2.Buffer里面position和limit相等为0了
        // 3. 客户端的程序发送完毕
        while (read!=-1&&read!=0){
            System.out.println(read);
            //切换状态 记得调用flip方法 否则无法读取到数据
            byteBuffer.flip();
            System.out.println(socketChannel.socket().getRemoteSocketAddress().toString()+":"+ Charset.forName("utf-8").decode(byteBuffer).toString());
            byteBuffer.compact();
            read = socketChannel.read(byteBuffer);
        }
        //如果是1 代表socket客户端已经关闭 就不发送消息了
        if (read==-1){
            socketChannel.close();
            selectionKey.cancel();
        }else{
            //send只要tcp缓冲区没有满 都是可以写数据的 一般不注册
            send();
        }
    }

    private void send() throws IOException {
        String str = "Your message has sent to "
                + socketChannel.socket().getLocalSocketAddress().toString() + "\r\n";
        System.out.println(" start sending back");
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(str.getBytes("utf-8"));
        while (byteBuffer.hasRemaining()){
            byteBuffer.flip();
            socketChannel.write(byteBuffer);
        }
        System.out.println(" finish sending back");
       /* state=0;*/
    }
}

6.客户端代码

public static boolean testClient() {
        boolean f =false;
        try {
            System.out.println("===========");
            Socket client = new Socket("10.252.69.254", 5063);
            System.out.println("Connected to 10.252.69.254");
            PrintWriter out = new PrintWriter(client.getOutputStream());
            BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
            String input;
            boolean result = true;
            while(result) {
                out.println("i am sending");
                out.flush();
                String a=in.readLine();
                System.out.println(a);
                f=(a!=null);
                result=false;
            }
            client.close();
            System.out.println("client stop.");
        } catch (UnknownHostException u) {
            System.err.println("Don't know about host: " );
        } catch (IOException e) {
           e.printStackTrace();
        }
        return f;
    }
 

7.服务端的启动方法

    public static void main(String[] args)  {
        try {
            TcpReactor tcpReactor = new TcpReactor(5063);
            tcpReactor.run();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

8.写程序时候遇到的几点问题

1.客户端发送的数据。服务端已经接收到,但是无法读出来。

解决:没有调用buffer.flip方法切换到读状态

2.运行的时候报异常

 java.net.SocketException:Software caused connection abort: recv failed

原因:客户端已经关闭的情况下,服务端仍然试图发送数据
解决:判断一下是否已经关闭
加上代码

 if (read==-1){
            socketChannel.close();
            selectionKey.cancel();
        }

客户端判断服务端是否关闭的方式

     Socket client = new Socket("10.252.69.254", 5063);
    int available = client.getInputStream().available();
   //如果avalible的在值大于0 那么代表代表服务端没有关闭

9.耗时测试


    public static void main(String[] args) throws InterruptedException {
    Long start = System.currentTimeMillis();
       final AtomicInteger atomicInteger = new AtomicInteger(0);
        for (int i=0;i<100;i++){
               Thread thread= new Thread(new Runnable() {
                    @Override
                    public void run() {
                        boolean result =testClient();
                        if (result){
                            atomicInteger.getAndIncrement();
                        }
                    }
                });
              thread.start();
              thread.join();
          }
        System.out.println("一共成功接收到响应的数目"+atomicInteger.get());
        System.out.println("消耗的时间为"+(System.currentTimeMillis()-start));
    	}

100个线程的时候

一共成功接收到响应的数目100
消耗的时间为366

500个线程

一共成功接收到响应的数目500
消耗的时间为1451

1000个线程

一共成功接收到响应的数目1000
消耗的时间为2874

5000个线程

一共成功接收到响应的数目5000
消耗的时间为9070

下节课讲一下多线程模式下Reactor的设计来提高响应的速度。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值