网络编程,select多路复用的具体使用,监听accept、read、write事件,解决客户端断开,消息边界问题

Selector

selector就是管理多个channel的,并且能够管理监测这些channel上有没有事件发生,如果有事件发生,selector就能获得这些事件,我们的线程再去处理这些事件。没有事件发生的时候线程可以阻塞。

事件有四种:

  • accept 客户端发起了连接请求时触发该类型的事件
  • connect 客户端与服务器连接建立成功就会触发该事件
  • read 可读事件
  • write 可写事件

使用Selector步骤

  1. 创建Selector
  2. 创建ServerSocketChannel
  3. 将ServerSocketChannel注册进selector
  4. 得到一个SelectedKey,并指定该channel要关注的事件
  5. 写一个死循环,然后调用select()方法阻塞线程
  6. 当有事件发生是就会解除线程的阻塞,获取所有有事件发生的SelectedKeys集合,遍历,获取key
  7. 通过key进行判断事件的类型
  8. 通过key获取channel,在进行各个事件相应的处理

处理accept事件

package com.hs.netty.network;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;

/**
 * 使用select机制,服务器端的代码
 * @author hs
 * @date 2021/07/12
 */
public class ServerSocketChannelTest3 {
   
    public static void main(String[] args) throws IOException {
   
        // 创建一个select
        Selector selector = Selector.open();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        // 建立select和channel之间的联系(channel注册进select中)
        // selectKey 就是将来事件发生后可以通过它知道是哪一个channel发生的事件
        SelectionKey sscKey = serverSocketChannel.register(selector, 0, null);
        // 服务器ServerSocketChannel这个selectKey只需要关注accept事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while(true){
   
            // 调用Selector类的select()方法,该方法的作用就是如果没有事件发生 就阻塞,如果有事件发生了 才会让线程继续运行
            selector.select();

            // 处理发生的事件
            // 首先要拿到所有发生了的事件,结果是一个set集合,在之后的操作中还会在集合中进行删除操作,
            // 所以这里不能使用增强for循环,需要使用到迭代器
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            // 遍历
            while(iterator.hasNext()){
   
                // 我们可以通过这个key知道是哪个channel发生了事件,发生了什么事件。
                SelectionKey key = iterator.next();
                // 得到发生事件了的key,调用channel()方法返回的是SelectableChannel类型,
                // 还可以进行强转变为ServerSocketChannel
                ServerSocketChannel channel = (ServerSocketChannel) key.channel();
                // 执行这个accept事件,
                SocketChannel socketChannel = channel.accept();
                System.out.println("客户端成功连接到服务器---->" + socketChannel);
            }
        }
    }
}

如果我们得到了channel,却不进行事件处理的话,就又会进入一个非阻塞状态不停的死循环。这是因为如果我们处理了这个事件,执行完一次while循环后,再执行到 selector.select()时,它就会认为这个事件已经处理了,我应该给你新事件。如果不处理,就会一直拿到这个事件,一直死循环。

如果拿到了SelectionKey,处于某些原因,不想执行这个事件了 可以调用key.cancel()方法取消该事件

总结:select()方法在事件未处理时不会阻塞,事件已处理或者是取消了,并且当前没有新事件了就会阻塞,即如果有事件发生,要么处理要么取消,不能置之不理。

处理accept+read事件

现在有个问题,就是事件有多种,我们需要在while()循环中添加事件的判断,然后执行各个事件相应的业务逻辑

while(iterator.hasNext()){
   
    // 我们可以通过这个key知道是哪个channel发生了事件,发生了什么事件。
    SelectionKey key = iterator.next();

    // 有以下几个方法来判断事件的类型,判断事件的类型,然后进行相应的逻辑
    key.isAcceptable();
    key.isConnectable();
    key.isReadable();
    key.isWritable();
}

现在服务器端的代码如下:

package com.hs.netty.network;

import com.google.common.base.Charsets;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;

/**
 * 使用select机制,处理accept + read 事件 服务器端的代码
 * @author hs
 * @date 2021/07/12
 */
public class ServerSocketChannelTest4 {
   
    public static void main(String[] args) throws IOException {
   
        // 创建一个select
        Selector selector = Selector.open();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.bind(new InetSocketAddress(8080));

        // 创建一个ByteBuffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(16);

        // 建立select和channel之间的联系(channel注册进select中)
        // selectKey 就是将来事件发生后可以通过它知道是哪一个channel发生的事件
        SelectionKey sscKey = serverSocketChannel.register(selector, 0, null);
        // 服务器ServerSocketChannel这个selectKey只需要关注accept事件
        sscKey.interestOps(SelectionKey.OP_ACCEPT);

        while(true){
   
            // 调用Selector类的select()方法,该方法的作用就是如果没有事件发生 就阻塞,如果有事件发生了 才会让线程继续运行
            selector.select();

            // 处理发生的事件
            // 首先要拿到所有发生了的事件,结果是一个set集合,在之后的操作中还会在集合中进行删除操作,
            // 所以这里不能使用增强for循环,需要使用到迭代器
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            // 遍历
            while(iterator.hasNext()){
   
                // 我们可以通过这个key知道是哪个channel发生了事件,发生了什么事件。
                SelectionKey key = iterator.next();

                // 如果是accept事件就执行下面的业务逻辑,将客户端SocketChannel也注册进Selector中
                if (key.isAcceptable()) {
   
                    // 得到发生事件了的key,调用channel()方法返回的是SelectableChannel类型,
                    // 还可以进行强转变为ServerSocketChannel
                    ServerSocketChannel channel = 
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值