非阻塞socket

非阻塞Socket编程中涉及的关键类包括ServerSocketChannel、SocketChannel和Selector。ServerSocketChannel作为服务器端替代类,SocketChannel用于替代Socket并与Selector注册事件。Selector通过轮询机制监控注册事件,如连接、读写等。SelectionKey则跟踪注册事件,当事件发生时,将相关信息加入selectedKeys集合。同时,Charset、CharsetDecoder和CharsetEncoder负责字符编码解码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

非阻塞socket中的几个重要类:

  • ServerSocketChannel(可以看作是ServerSocket的替代类, 需要通过open()的静态方法创建)
  • SocketChannel(Socket 的替代类)通过register方法向selector 注册一个事件

  • Selector类似一个观察者,SocketChannel向Selector注册了特定的事件,Selector就会监控这些事件是否发生。Selector内部原理实际是在做一个对所注册的channel的轮询访问,不断的轮询(目前就这一个算法),一旦轮询到一个channel有所注册的事情发生,比如数据来了,他就会站起来报告,交出一把钥匙,让我们通过这把钥匙来读取这个channel的内容。主要方法有:
        1. keys() 当前所有向Selector注册的SelectionKey的集合
        2. selectedKeys() 已注册的被Selector捕获的SelectionKey的集合
        3. open() 打开一个selector
        4. select() 如果注册的事情发生了,select() 函数返回值就会大于0
  • SelectionKey,当ServerSocketChannel或SocketChannel通过register()方法向Selector注册事件时,register()方法会创建一个SelectionKey对象,这SelectionKey  对象是用来跟踪注册事件的句柄。 在SelectionKey对象的有效期间,Selector会一直监控与SelectionKey对象 相关的事件,如果事件发生,就会把SelectionKey对象加入到selectedKeys()集合中。
在 SelectionKey 中定义了四种事件,
  1. SelectionKey.OP_ACCEPT :接收连接就绪事件,表示服务器监听到了客户连接,服务器可以接收这个连接了。常量值为 16
  2. SelectionKey.OP_CONNECT :连接就绪事件,表示客户与服务器的连接已经建立成功。常量值为 8 。
  3. SelectionKey.OP_READ :读就绪事件,表示通道中已经有了可读数据,可以执行读操作了。常量值为 1 。
  4. SelectionKey.OP_WRITE :写就绪事件,表示已经可以向通道写数据了。常量值为 4 。
上面常量分别占居不同的二进制位,可以通过二进制的或运算 “ | ” ,来将它们进行任意组合。 isAcceptable(), isReadable(),isWritable(),isConnectable()分别对应上面的四个事件。

  • Charset, CharsetDecoder,CharsetEncoder主要用来对字符编码在网络上传输,到本地之后解码输出。

这几个类之间的关系通过register方法可以看的出来
ssc = ServerSocketChannel.open();
ssc.register(selector, SelectionKey.OP_ACCEPT);
socket通道通过register方法向一个selector 注册它感兴趣的事件。


创建一个非阻塞服务器的步骤有:
  1. 打开selector, SocketServerChannel 
  2. 为SocketServerChannel 绑定一个本地地址,并设置为非阻塞模式
  3. 注册SelectionKey中的事件到selector
  4. 循环处理已注册的事件
public class NioServer {
    private static final int PORT = 3000;
    private static final String DEFAULT_CHARSET = "GBK";
    private static final int BUFFERSIZE = 1024;
    private Selector selector;
    private ServerSocketChannel ssc;
    private ByteBuffer buffer;
    private Charset charset;
    private CharsetDecoder decoder;
    private CharsetEncoder encoder;
    public NioServer() throws IOException{
        // 打开seletor
        selector = Selector.open();
        ssc = ServerSocketChannel.open();
        // 绑定一个本地地址
        ssc.socket().bind(new InetSocketAddress(PORT));
        // 设置为非阻塞模式
        ssc.configureBlocking(false);
        // 为ssc 注册接入事件
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        
        buffer = ByteBuffer.allocate(BUFFERSIZE);
        // 
        charset = Charset.forName(DEFAULT_CHARSET);
        decoder = charset.newDecoder();
        encoder = charset.newEncoder();
    }

    private void listen() throws IOException{
        while (true){
            int i = selector.select();
            if (i > 0){
                // 已注册的事件集合
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()){
                    SelectionKey sk = iter.next();
                    // 每次取出一个事件,就移除
                    iter.remove();
                    
                    handleSelectionKey(sk);
                }
            }
        }
    }
    
    // 分别对应SelectionKey的四种事件
    private void handleSelectionKey(SelectionKey sk) throws IOException{
        if(sk.isAcceptable()){
            // 接收连接就绪
            handleAccept(sk);
        } else if (sk.isReadable()){
            // 读就绪
            handleRead(sk);
        } else if (sk.isWritable()){
            // 写就绪
            
        }
    }
    
    private void handleAccept(SelectionKey sk) throws IOException{
        ServerSocketChannel ssc = (ServerSocketChannel)sk.channel();
        SocketChannel sc = ssc.accept();   // 得到客户端socket
        sc.configureBlocking(false);
        // 连接后, 为客户端注册读事件
        sc.register(selector, SelectionKey.OP_READ);
    }
    
    private void handleRead(SelectionKey sk) throws IOException{
        SocketChannel sc = (SocketChannel) sk.channel();
        buffer.clear();
        int r = sc.read(buffer);
        if (r == -1){
            sc.close();
            return;
        } 
        buffer.flip();
        // 解码从客户端传过来的数据
        CharBuffer charBuffer = decoder.decode(buffer);
        System.out.println("client says: " + charBuffer.toString());
        
        // 向客户端发送编码后的信息
        sc.write(encoder.encode(CharBuffer.wrap("服务器写")));
    }
    
    public static void main(String args[]) throws IOException{
        System.out.println("server listen...");
        new NioServer().listen();
    }
}

创建非阻塞的客户端与服务器几乎是一样, 只不过是把ServerSocketChannel改成SocketChannel

public class NioClient {
    private static final int PORT = 3000;
    private static final String DEFAULT_CHARSET = "GBK";
    private static final int BUFFERSIZE = 1024;
    private InetSocketAddress sia;
    private Selector selector;
    private ByteBuffer buffer;
    private Charset charset;
    private CharsetDecoder decoder;
    private CharsetEncoder encoder;

    public NioClient() throws IOException {
        SocketChannel sc= SocketChannel.open();
        sc.configureBlocking(false);
        selector = Selector.open();
        // 注册连接事件
        sc.register(selector, SelectionKey.OP_CONNECT);
        buffer = ByteBuffer.allocate(BUFFERSIZE);
        
        charset = Charset.forName(DEFAULT_CHARSET);
        decoder = charset.newDecoder();
        encoder = charset.newEncoder();
        
        // 连接到服务器
        sia = new InetSocketAddress("localhost", PORT);
        sc.connect(sia);
    }
    
    public void connect() throws IOException{
        while (true){
            int i = selector.select();
            if (i > 0){
                Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
                while (iter.hasNext()){
                    SelectionKey key = iter.next();
                    iter.remove();
                    
                    handleSelectionKey(key);
                }
            }
        }
    }
    
    public void handleSelectionKey(SelectionKey key) throws IOException{
        if (key.isConnectable()){
            SocketChannel sc = (SocketChannel) key.channel();
            if (sc.isConnectionPending()){
                sc.finishConnect();
            }
            sc.write(encoder.encode(CharBuffer.wrap("客户端写")));
            // 注册读事件
            sc.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()){
             SocketChannel sc = (SocketChannel) key.channel();
             buffer.clear();
             int r = sc.read(buffer);
             if (r == -1){
                 sc.close();
                 return;
             }
             buffer.flip();
             CharBuffer charBuffer = decoder.decode(buffer);
             System.out.println("server says: " + charBuffer.toString());
        }
    }
    
    public static void main(String args[]) throws IOException{
        System.out.println("client connect...");
        new NioClient().connect();
    }
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值