×

NIO Channel

使用NIO手写一个非阻塞的服务端和客户端

我的笔记 我的笔记 发表于2020-05-31 21:48:33 浏览3349 评论1

1人参与发表评论

服务端程序:

package com.fyd.nio;

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

/***
 * @Author付亚东
 * @Date 2020/5/26 21:59
 ****/
public class NIOServer {
    public static void main(String[] args) throws Exception{
        //创建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //的到一个Selector
        Selector selector = Selector.open();
        //监听一个端口绑定
        InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);
        serverSocketChannel.socket().bind(inetSocketAddress);
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //把serverSocketChannel注册到 selector 关心事件为 OP_ACCEPT
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //循环等待客户端链接
        while (true){
            //这里等待1秒,如果没有事件发生,下一次循环
            if(selector.select(1000)==0){
                System.out.println("服务器等待了1秒,无连接");
                continue;
            }
            //如果返回的不是0,有事件发生
            //获取到相关的selectionKeys集合
            //1、如果返回大于0,则获取到关注的事件
            //2、selector.selectedKeys 返回关注事件的集合
            //   通过selectionKeys反向获取通道
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            //遍历,使用迭代器
            Iterator<SelectionKey> selectionKeyIterator = selectionKeys.iterator();
            while (selectionKeyIterator.hasNext()){
                //获取到selectionKey
                SelectionKey selectionKey = selectionKeyIterator.next();
                //根据这个key对应的通道发生的事件做不同的处理
                if(selectionKey.isAcceptable()){//如果是这个事件OP_ACCEPT,代表有新的客户端链接
                    //给该客户端生成一个SocketChannel
                    //因为上边的判断,判定此次就是客户端链接,所以这里不会阻塞
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    //设置为非阻塞
                    socketChannel.configureBlocking(false);
                    System.out.println("客户端链接成功,"+socketChannel.hashCode());
                    //将 socketChannel 也注册到selector上,关注事件为OP_READ,
                    // 同时给这个socketChannel关联一个buffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));

                }else if(selectionKey.isReadable()){//如果是这个事件OP_READ,代表有客户端数据
                    //通过selectionKey获取channel
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                    //获取到该channel关联的buffer
                    ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
                    //读取数据
                    socketChannel.read(byteBuffer);
                    System.out.println("客户端发送的数据:"+new String(byteBuffer.array()));
                }
                //手动从集合中移除当前的selectKey,否则会出现重复操作。多线程
                selectionKeyIterator.remove();
            }
        }

    }
}

客户端程序:

package com.fyd.nio;

import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

/***
 * @Author付亚东
 * @Date 2020/5/31 21:20
 ****/
public class NIOClient {
    public static void main(String[] args) throws Exception{
        //得到一个通道
        SocketChannel socketChannel = SocketChannel.open();
        //设置非阻塞模式
        socketChannel.configureBlocking(false);
        //提供服务器端的ip和端口
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",6666);
        //链接服务器
        if(!socketChannel.connect(inetSocketAddress)){//如果链接失败
            while (!socketChannel.finishConnect()){
                System.out.println("因为链接需要时间,客户端不会阻塞,可以做其他工作");
            }
        }
        //如果链接成功,发送数据
        String msg = "你好,亚东";
        ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
        //发送数据
        socketChannel.write(byteBuffer);
        //会停在这里
        System.in.read();
    }
}

1、首先启动服务端,会打印如下日志:

image.png

2、然后启动客户端,服务端会打印接收到数据,1s后继续非阻塞等待

image.png

我的笔记博客版权我的笔记博客版权