图解io+BIO+NIO+AIO+Netty+聊天室实战

2020年开年不平凡,自己在家呆着重新学习一下io
io分为bio,nio,aio
其中netty属于nio,aio目前还没有广泛应用,将来有很大的潜力
图解io+BIO+NIO+AIO+Netty+聊天室实战_第1张图片
理解几个概念
同步和异步
阻塞和非阻塞
io多路复用
I/O多路复用,I/O是指网络I/O, 多路指多个TCP连接(即socket或者channel),复用指复用一个或几个线程。
简单来说:就是使用一个或者几个线程处理多个TCP连接 最大优势是减少系统开销小,不必创建过多的进程/线程,也不必维护这些进程/线程

Reactor反应堆模式:基于事件驱动的,常见的有redis,netty等应用
epoll
1)没fd这个限制,所支持的FD上限是操作系统的最大文件句柄数,1G内存大概支持10万个句柄
2)效率提高,使用回调通知而不是轮询的方式,不会随着FD数目的增加效率下降
3)通过callback机制通知,内核和用户空间mmap同一块内存实现

首先图解io的简单交互流程

单机内部io

图解io+BIO+NIO+AIO+Netty+聊天室实战_第2张图片
从流程图可以看出,外部交互首先是socket通过网卡,通过对应的端口进入,之后通过数据准备阶段进入内核空间,内核空间再经过数据拷贝到用户空间,进而应用程序进行处理。其他和涉及到同步和异步,阻塞和非阻塞的理解,这里就不做介绍了。

图解io+BIO+NIO+AIO+Netty+聊天室实战_第3张图片
此图是阻塞io的调用过程

网络io模型

图解io+BIO+NIO+AIO+Netty+聊天室实战_第4张图片

数据读写过程
图解io+BIO+NIO+AIO+Netty+聊天室实战_第5张图片

图解io+BIO+NIO+AIO+Netty+聊天室实战_第6张图片

java.io模型
BIO:jdk1.4之前主要网络io编程的方式,同步阻塞

bio单线程 请求阻塞和读写阻塞,一次只能服务一个客户端
图解io+BIO+NIO+AIO+Netty+聊天室实战_第7张图片

bio多线程 请求阻塞和读写阻塞,一个线程服务一个客户端,可以服务多个客户端
图解io+BIO+NIO+AIO+Netty+聊天室实战_第8张图片
图解io+BIO+NIO+AIO+Netty+聊天室实战_第9张图片
BIO聊天室
TalkServerBIO bio服务端
//聊天室服务端bio实现
public class TalkServerBIO {
//客户端列表
public static List userList = new ArrayList<>();

public static void main(String[] args) throws IOException {

    //服务端端口设置
    ServerSocket serverSocket = new ServerSocket(9999);
    System.out.println("美女聊天室等待您的连接......");
    while (true){
        Socket socket = serverSocket.accept();
        System.out.println("尊敬的客户欢迎您的到来");
        MyChannel myChannel = new MyChannel(socket);
        new Thread(myChannel).start();
        userList.add(myChannel);
    }
}

}
TalkClientBIO bio客户端实现
//聊天室客户端bio实现
@Slf4j
public class TalkClientBIO {

private static File file = new File("Client.propertise");

private static Properties properties = new Properties();

private static String HOST = "";

private static int PORT =0;

//初始化端口启动信息
static {
    if(!file.exists()){
        try {
            file.createNewFile();
            FileOutputStream fos = new FileOutputStream(file);
            properties.setProperty("hostname","localhost");
            properties.setProperty("port","9999");
            properties.store(fos,null);
            fos.close();
        } catch (IOException e) {
            log.error("创建文件失败");
            e.printStackTrace();
        }
    }
    try {
        properties.load(new FileInputStream(file));
        HOST = properties.getProperty("hostname");
        PORT  = Integer.parseInt(properties.getProperty("port"));
    } catch (IOException e) {
        e.printStackTrace();
    }
}
public static void main(String[] args) {
    try {
        Socket socket = new Socket(HOST,PORT);
        Scanner input = new Scanner(System.in);
        System.out.println("客户端已经启动,请输入您的昵称: ");
        String name = input.next();
        System.out.println("已经进入聊天室!");
        new Thread(new SendUtil(socket),name).start();
        new Thread(new ReceiveUtil(socket),name).start();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}
MyChannel 消息通道的实现
//server服务端的管道通讯实现
@Slf4j
public class MyChannel implements Runnable {

//输入流
private DataInputStream dis;
//输出流
private DataOutputStream dos;
//管道关闭标识
private boolean isStop = Boolean.FALSE;

/**
 * 构造函数获取连接相关信息
 * @param client
 */
public MyChannel(Socket client){

    try {
        dis = new DataInputStream(client.getInputStream());
        dos = new DataOutputStream(client.getOutputStream());
    } catch (IOException e) {
        e.printStackTrace();
        log.info("管道消息异常{}",client.getInetAddress());
        colse();
    }
}
@Override
public void run() {
    while (!isStop){
        sendOther();
    }

}

/**
 *
 * 获取消息
 * @return
 */
public String receiveMsg(){

    String msg ="";

    try {
        msg = dis.readUTF();
        System.out.println(Thread.currentThread().getName()+":"+msg);
    } catch (IOException e) {
        colse();
        TalkServerBIO.userList.remove(this);
    }
    return msg;
}

public void sendMsg(String s){

    if(!StringUtils.isEmpty(s)){
        try {
            dos.writeUTF(s);
            dos.flush();
        } catch (IOException e) {
            e.printStackTrace();
            isStop = true;
            colse();
        }

    }

}

/**
 * 给其他客户端发送消息
 */
public void sendOther(){
    String s = this.receiveMsg();
    List userList = TalkServerBIO.userList;
    for(MyChannel myChannel:userList){
      if(this == myChannel){
          continue;
      }
      //给其他人发送消息
      myChannel.sendMsg(s);
    }
}

/**
 * 关闭连接
 */
public void colse(){
    isStop = true;
    if(null!=dis){
        try {
            dis.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
    if(null!=dos){
        try {
            dos.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

}

//发送信息工具类
public class SendUtil implements Runnable {

private DataOutputStream dos = null;
private boolean isStop = Boolean.FALSE;
private BufferedReader br = null;

public SendUtil(Socket socket){

    br = new BufferedReader(new InputStreamReader(System.in));
    try {
        dos =  new DataOutputStream(socket.getOutputStream());
    } catch (IOException e) {
        e.printStackTrace();
        colse();
    }
}

/**
 * 发送消息
 */
public void sendMessage(){
    try {
        dos.writeUTF(Thread.currentThread().getName()+"-->说:"+br.readLine());
        dos.flush();
    } catch (IOException e) {
        colse();
        e.printStackTrace();
    }
}
@Override
public void run() {

    while (!isStop){
        this.sendMessage();
    }
}

/**
 * 关闭连接
 */
public void colse(){
    isStop = true;
    if(null!=dos){
        try {
            dos.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

}
//获取消息工具类
public class ReceiveUtil implements Runnable {

private DataInputStream ios = null;
private boolean isStop = Boolean.FALSE;
private BufferedReader br = null;

public ReceiveUtil(Socket socket){
    try {
        ios = new DataInputStream(socket.getInputStream());
    } catch (IOException e) {
        e.printStackTrace();
        colse();
    }
}
public void receiveMessage(){
    String msg ="";
    try {
        msg = ios.readUTF();
        System.out.println(msg);
    } catch (IOException e) {
        e.printStackTrace();
    }

}
@Override
public void run() {
    while (!isStop){
        this.receiveMessage();
    }
}


/**
 * 关闭连接
 */
public void colse(){
    isStop = true;
    if(null!=ios){
        try {
            ios.close();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }
}

}
本地开启了3个客户端 1个服务端实现了多人在线聊天
图解io+BIO+NIO+AIO+Netty+聊天室实战_第10张图片

通过这个例子可以看出,每新增一个客户端就会服务端就会新起一个线程,那么在大量客户请求的时候,会出现服务器线程资源不够使用,导致服务不可用.即使使用线程池也改变不了根本问题。如果客户端对应的线程一直没有读写操作,则线程一直是阻塞状态,极大浪费资源。

NIO模型出现了,jdk1.4之后,特点同步非阻塞

图解io+BIO+NIO+AIO+Netty+聊天室实战_第11张图片

nio聊天室实现
//服务端
@Slf4j
public class TalkServerNIO {
//定义端口号
private static int PORT = 8848;
//用于字符集解码
private Charset charset = Charset.forName(“UTF-8”);
//用于接收数据的缓冲区
private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
//用于发送数据的缓冲区
private ByteBuffer sBuffer = ByteBuffer.allocate(1024);
//用于存放客户端socketchannel集合
private Map clientMap = new HashMap<>();
//用于监听通道事件
private static Selector selector;

public static void main(String[] args) {
    TalkServerNIO talkServerNIO = new TalkServerNIO(8848);
    talkServerNIO.listen();
}
public TalkServerNIO(int port) {

    PORT = port;
    try {
        init();
    } catch (Exception e) {
        e.printStackTrace();
        log.error("服务器初始化异常", e);
    }
}

//初始化服务器,注册侦听事件
private void init() throws IOException {

    //打开一个serverSocketChannel
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    //通道设置为非阻塞
    serverSocketChannel.configureBlocking(false);
    //获取该通道的serverSocket
    ServerSocket serverSocket = serverSocketChannel.socket();
    //绑定端口号
    serverSocket.bind(new InetSocketAddress(PORT));
    //打开侦听选择器
    selector = Selector.open();
    //注册连接侦听事件到选择器上面
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    System.out.println("服务器启动,端口号为:" + PORT);
}

//服务器轮询监听,select方法会一直阻塞直到有关事件发送或者超时
public void listen() {

    while (true) {

        try {
            // log.info("等待连接请求.....");
            selector.select();//返回值为本次触发的事件数
            //获取事件集合
            Set selectionKeys = selector.selectedKeys();
            //处理各个事件
            selectionKeys.forEach(selectionKey -> handle(selectionKey));
            selectionKeys.clear();//清除处理过的事情
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//处理对应事件
private void handle(SelectionKey selectionKey) {

    // log.info("有请求进来,需要处理....");
    //有客户端请求连接
    if (selectionKey.isAcceptable()) {
        //获取连接请求的channel
        ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
        try {
            SocketChannel clent = server.accept();
            clent.configureBlocking(false);
            clent.register(selector, SelectionKey.OP_READ);
            clientMap.put(getClientName(clent), clent);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //客户端法消息了
    else if (selectionKey.isReadable()) {

        //客户端通道
        SocketChannel client = (SocketChannel) selectionKey.channel();
        //清空读缓存
        rBuffer.clear();

        try {
            int bytes = client.read(rBuffer);
            if (bytes > 0) {
                //设置换从区初始位置开始读取数据
                rBuffer.flip();
                //解码缓冲区数据
                String receiveText = String.valueOf(charset.decode(rBuffer));
                System.out.println(client.toString() + ":" + receiveText);
                dispatch(client, receiveText);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

//转发消息给其他客户端,相当于bio重的sendOther
private void dispatch(SocketChannel clinet, String info) {
    if (!clientMap.isEmpty()) {

        for (Map.Entry entry : clientMap.entrySet()) {
            SocketChannel temp = entry.getValue();
            if (!clinet.equals(temp)) {
                sBuffer.clear();
                sBuffer.put(charset.encode(getClientName(clinet) + ":" + info));
                sBuffer.flip();
                try {
                    temp.write(sBuffer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }
}
//生成客户端名称或者昵称
private String getClientName(SocketChannel client) {

    Socket socket = client.socket();
    return "[" + socket.getInetAddress().toString().substring(1) + ":" + Integer.toHexString(client.hashCode()) + "]";
}

}

客户端的实现

@Slf4j
public class TalkClientNIO {

//服务器地址
private InetSocketAddress SERVER = null;
//用于接收数据的缓冲区
private ByteBuffer rBuffer = ByteBuffer.allocate(1024);
//用于发送数据的缓冲区
private ByteBuffer sBuffer = ByteBuffer.allocate(1024);

//用于监听通道事件
private static Selector selector;
//用于编码或者解码
private Charset charset = Charset.forName("UTF-8");

public TalkClientNIO(int port){

    SERVER = new InetSocketAddress("127.0.0.1",port);
    //初始化客户端
    init();

}

//初始化客户端
private void init(){

    try {
        //打开SocketChannel
        SocketChannel socketChannel = SocketChannel.open();
        //非阻塞
        socketChannel.configureBlocking(false);
        //打开监听通道
        selector = Selector.open();
        //将通道注册在选择器上面且侦听连接事件
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        //进行连接
        socketChannel.connect(SERVER);
        while (true){
            //调用select方法选择有事情调用的通道
            selector.select();
            Set selectionKeys = selector.selectedKeys();
            //进行处理
            selectionKeys.forEach(selectionKey -> handle(selectionKey));
            selectionKeys.clear();//清理处理过的事件
        }

    } catch (IOException e) {
        e.printStackTrace();
    }
}

public void handle(SelectionKey selectionKey){
    //连接就绪事件
    if(selectionKey.isConnectable()){
        //获取有连接事件的channel
        SocketChannel client  = (SocketChannel) selectionKey.channel();
        //如果正在连接状态
        if(client.isConnectionPending()){
            try {
                //表示连接成功
                client.finishConnect();
                System.out.println("完成和服务端的连接...");
                //启动线程侦听客户端的输入
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        while (true){
                            sBuffer.clear();
                            Scanner scanner = new Scanner(System.in);
                            String sendText = scanner.next();
                            sBuffer.put(charset.encode(sendText));
                            sBuffer.flip();
                            try {
                                client.write(sBuffer);
                            }catch (Exception e){
                                log.error("输入消息异常...");
                            }
                        }
                    }
                }).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                //注册可读事件
                client.register(selector,SelectionKey.OP_READ);
            } catch (ClosedChannelException e) {
                e.printStackTrace();
            }
        }

    }
    //可读事件有从服务端传过来的消息,读取输入到屏幕上
    else if(selectionKey.isReadable()){
        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
        rBuffer.clear();
        try {
            int count = socketChannel.read(rBuffer);
            if(count>0){
                String receiveText = new String(rBuffer.array(),0,count);
                System.out.println(receiveText);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public static void main(String[] args) {
    TalkClientNIO talkClientNI = new TalkClientNIO(8848);
}

}
nio的Reactor的模型
Reactor可以分为两部分:
一部分是Reactor自身,处理客户端的连接和请求
一部分是io处理
1.单Reactor单线程
所有的请求全部由主线程来响应
客户端发送请求:主线程多路复用,从selector中循环进行侦听连接accept,然后注册获取连接的客户端进行read监听客户端连接上之后,进行写操作,服务端读数据.
图解io+BIO+NIO+AIO+Netty+聊天室实战_第12张图片
2.单Reactor多线程
使用线程池来处理业务io
图解io+BIO+NIO+AIO+Netty+聊天室实战_第13张图片
3.主从Reactor多线程
图解io+BIO+NIO+AIO+Netty+聊天室实战_第14张图片

java nio缺点
1.学习难度大,需要熟练掌握技能socketchannel,buffer等
2.开发复杂很多业务处理需要经验,比如网络中断,网络异常等
3.臭名昭著的epoll空轮询bug导致系统瘫痪

netty是一个高性能的网络通讯框架,有jboss公司开发维护。
线程模型:主从Reactor多线程基础上面的改进
使用了高效的零拷贝技术:
mmp,sendfile两种实现方式
正常流程中读写操作需要4次拷贝和3次上下文切换
mmp改进之后是3次拷贝3次上下文切换
sendfile 在linux 2.4之前 3次拷贝2次上下文切换
sendfile在linux2.4之后 2次拷贝 2次上下切换
做到真正的零拷贝:所谓零拷贝是指cpu参与的内存拷贝
dma拷贝是不可避免的,dma是指directory memory access

netty 聊天室实战
NettyServer
public class NettyServer {

private  static int PORT = 8848;
public NettyServer(int port){

    PORT = port;
}
public void start() {

    //线程组线程的大小默认值为cup*2 看实际情况来设置,如果请求并发量不高,处理io比较耗时,
    //可以 bossgroup设置少一些,workergroup少一些
    //接收客户端连接的处理线程组
    //获取底层的cpu核数NettyRuntime.availableProcessors() * 2
    System.out.println("cpu个数:"+NettyRuntime.availableProcessors());

    EventLoopGroup bossGroup = new NioEventLoopGroup();
    //工作线程组 主要负责网络io读写交互
    EventLoopGroup workerGroup = new NioEventLoopGroup();


    //netty用于启动nio服务器的启动类,降低开发复杂度
    ServerBootstrap bootstrap = new ServerBootstrap();
    //AbstractBootstrap 设置bossGroup
    //重要概念链式编程 设置线程组
    bootstrap = bootstrap.group(bossGroup,workerGroup);
    //设置channel模式
    bootstrap = bootstrap.channel(NioServerSocketChannel.class);
    //设置处理器的回调请求
    bootstrap = bootstrap.childHandler(new NettyServerInitalizer());
    //设置NioServerSocketChannel的tcp参数
   // bootstrap = bootstrap.option(ChannelOption.SO_BACKLOG,1024);
   // bootstrap = bootstrap.option(ChannelOption.SO_KEEPALIVE,true);

    //绑定侦听端口,调用sync同步阻塞方法等待绑定操作完成,完成后返回channelfuture
    try {
        ChannelFuture future = bootstrap.bind(PORT).sync();
        System.out.println("服务器启动......");
        //调用sync方法进行阻塞,等待服务端链路关闭之后main函数才能退出
        future.channel().closeFuture().sync();
        System.out.println("服务器关闭......");
    } catch (InterruptedException e) {
        e.printStackTrace();
    }finally {
        //优雅退出,释放线程资源
        workerGroup.shutdownGracefully();
        bossGroup.shutdownGracefully();
    }
}
public static void main(String[] args) {

    new NettyServer(PORT).start();

}

}

public class NettyServerInitalizer extends ChannelInitializer {

@Override
protected void initChannel(SocketChannel ch) throws Exception {

    System.out.println("有客户端连接:"+ch.remoteAddress());

    ChannelPipeline channelPipeline = ch.pipeline();
    //设置解码器
    channelPipeline.addLast("decode",new StringDecoder());
    //设置编码器
    channelPipeline.addLast("encode",new StringEncoder());

    //设置事件响应的处理器
    channelPipeline.addLast("handler",new NettyServerHandler());
}

}

public class NettyServerHandler extends SimpleChannelInboundHandler {

//定义通道组,实例化一个全局实例
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
/**
 * 当用客户端连接时,handlerAdder会执行,把客户端的通道记录下来,加入队列
 * @param ctx
 * @param msg
 * @throws Exception
 */

public void handlerAdded(ChannelHandlerContext ctx, String msg) throws Exception {

    //获取客户端通道
    Channel currentClient = ctx.channel();
    //全部客户端通道
    for(Channel channel:channels){
        if(channel!=currentClient){
            channel.writeAndFlush("[欢迎"+currentClient.remoteAddress()+"]进入聊天室\n");
        }
    }
    //把最新的客户端加入队列
    channels.add(currentClient);
}


/**
 * 当客户端有数据写入的时候触发
 * @param ctx
 * @param msg
 * @throws Exception
 */
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {

    //获取客户端通道
    Channel currentClient = ctx.channel();
    //全部客户端通道
    for(Channel channel:channels){
        if(channel!=currentClient){
             channel.writeAndFlush("[用户:"+currentClient.remoteAddress()+"说]"+msg
             +"\n");
        }else {
            channel.writeAndFlush("[我说:]"+msg +"\n");
        }
    }
    //把最新的客户端加入队列
    channels.add(currentClient);
}

/**
 * 客户端离开的时候进行的处理
 * @param ctx
 */
public void handlerRemoved(ChannelHandlerContext ctx){
    //获取客户端通道
    Channel leaveClient = ctx.channel();
    //通知其他客户端离开
    for(Channel channel:channels){
        if(leaveClient!=channel){
            channel.writeAndFlush("[再见:"+leaveClient.remoteAddress()+"]离开聊天室\n");
        }
    }

}

/**
 * 当前用户在线
 * @param ctx
 */
public void channelActive(ChannelHandlerContext ctx){

    Channel currentChannel = ctx.channel();
    System.out.println("["+currentChannel.remoteAddress()+"]在线");
}

/**
 * 当前用户离线
 * @param ctx
 */
public void channelInactive(ChannelHandlerContext ctx){

    Channel currentChannel = ctx.channel();
    System.out.println("["+currentChannel.remoteAddress()+"]离线");
}

/**
 * 客户端通讯异常
 * @param ctx
 * @param throwable
 */
public void exceptionCaught(ChannelHandlerContext ctx,Throwable throwable){

    Channel currentChannel = ctx.channel();
    System.out.println("["+currentChannel.remoteAddress()+"]通讯异常");
    ctx.close();

}

}

//client端实现

public class NettyClient {

private  String HOST;

private  int PORT;


public NettyClient(String host,int port){
    HOST = host;
    PORT = port;
}

public void start(){

    /**
     * 事件循环组
     */
    EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    //客户端启动
    Bootstrap bootstrap = new Bootstrap();
    //客户端添加线程组
    bootstrap = bootstrap.group(eventLoopGroup);
    //设置客户端通道
    bootstrap = bootstrap.channel(NioSocketChannel.class);
    //设置客户端事件处理器
    bootstrap.handler(new NettyClientInitalizer());

    try {
        //进行服务器连接获取客户端channel
        Channel channel = bootstrap.connect(HOST,PORT).sync().channel();
        //获取系统输入的信息
        BufferedReader input = new BufferedReader(new InputStreamReader(System.in));

        while (true){
            channel.writeAndFlush(input.readLine()+"\n");
        }
    } catch (InterruptedException | IOException e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    new NettyClient("localhost",8848).start();
}

}

public class NettyClientInitalizer extends ChannelInitializer {

@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {

    System.out.println("客户端开始启动....");

    //socket信息通道
    ChannelPipeline channelPipeline = socketChannel.pipeline();
    //设置解码器
    channelPipeline.addLast("decode",new StringDecoder());
    //设置编码器
    channelPipeline.addLast("encode",new StringEncoder());
    //设置处理器
    channelPipeline.addLast("handler",new NettyClientHandler());

}

}

public class NettyClientHandler extends SimpleChannelInboundHandler {

@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println(msg);
}

}

图解io+BIO+NIO+AIO+Netty+聊天室实战_第15张图片

AIO实现聊天室
服务端
public class AIOServer {

private static final String LOCALHSOT="localhost";

private static final int DEFAULT_PORT=8848;
//推出标识
private static final String QUIT="quit";

private static final int BUFFER_SIZE=1024;

private static final int THREADPOLL_SIZE = 8;
//异步通道组
private AsynchronousChannelGroup channelGroup;
//异步的ServerSocketChannel类比于ServerSocketChannel
private AsynchronousServerSocketChannel serverSocketChannel;
//编码格式
private Charset charset = Charset.forName("UTF-8");
//已经连接的客户端集合
private List connectedClients;

private int port;


public AIOServer(){
    this(DEFAULT_PORT);
}

public AIOServer(int port){
    this.port = port;
    this.connectedClients = new ArrayList<>();
}

/**
 * 申请退出
 * @param msg
 * @return
 */
private boolean readtToQuit(String msg){
    return QUIT.equals(msg);
}

/**
 * 关闭流
 * @param closeable
 */
private void close(Closeable closeable){

    if(closeable!=null){

        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

/**
 * 新增连接的客户端
  * @param clientHandler
 */

private synchronized void addClient(ClientHandler clientHandler){

    connectedClients.add(clientHandler);
    System.out.println("已经连接服务器.");

}

private synchronized void removeClient(ClientHandler clientHandler){

  connectedClients.remove(clientHandler);

  System.out.println("已经断开连接.");

}

/**
 * 解码接收的消息
 * @param byteBuffer
 * @return
 */

private String receive(ByteBuffer byteBuffer){

   CharBuffer charBuffer = charset.decode(byteBuffer);
   return String.valueOf(charBuffer);

}

/**
 * 获取名称
 * @param clinetChannel
 * @return
 */

private String getClinetName(AsynchronousSocketChannel clinetChannel){

   int clientPort = -1;

   try {
       InetSocketAddress inetSocketAddress = (InetSocketAddress) clinetChannel.getRemoteAddress();
       port = inetSocketAddress.getPort();
   } catch (IOException e) {
       e.printStackTrace();
   }
   return "客户端["+port+"]";

}

/**
 * 向其他客户端发送消息
 * @param clientChannel
 * @param msg
 */

private synchronized void dispatch(AsynchronousSocketChannel clientChannel,String msg){

   for(ClientHandler clientHandler:connectedClients){
       if(!clientHandler.clientChannel.equals(clientChannel)){
           ByteBuffer byteBuffe =
                   charset.encode(getClinetName(clientHandler.clientChannel)+":"+msg);
           clientHandler.clientChannel.write(byteBuffe,null,clientHandler);
       }

   }

}

/**
 * 事件完成后的处理
 */
private class ClientHandler implements CompletionHandler {

    private AsynchronousSocketChannel clientChannel;


    public ClientHandler(AsynchronousSocketChannel channel){

        this.clientChannel = channel;

    }

    @Override
    public void completed(Integer result,Object attachment){

        ByteBuffer buffer = (ByteBuffer) attachment;

        if(buffer!=null){//发送了读事件
            if(result<=0){
                //客户通道发送了异常,从列表中移除
                //从列表中删除该对象
               removeClient(this);
            }else {

                buffer.flip();//转化为读操作
                String msg = receive(buffer);//从buffer中读取消息
                System.out.println(getClinetName(clientChannel)+":"+msg);
                //转发消息
                dispatch(clientChannel,msg);
                buffer.clear();//情况缓冲区
                //退出
                if(readtToQuit(msg)){
                    removeClient(this);
                }else {
                    clientChannel.read(buffer,buffer,this);
                }
            }
        }
    }
    @Override
    public void failed(Throwable exc,Object attachment){

        System.out.println("读写失败:"+exc);

    }
}

//服务端连接事件处理
private class AcceptHandler implements CompletionHandler{

    @Override
    public void completed(AsynchronousSocketChannel clientChannel, Object attachment) {

        if(serverSocketChannel.isOpen()){//判断连接是否打开
                   serverSocketChannel.accept(null,this);
        }
        if(clientChannel!=null&&clientChannel.isOpen()){

            ClientHandler clientHandler = new ClientHandler(clientChannel);
            ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE);
            //将客户添加到列表中
            addClient(clientHandler);

            clientChannel.read(byteBuffer,byteBuffer,clientHandler);

        }

    }

    @Override
    public void failed(Throwable exc, Object attachment) {
       System.out.println("读写失败: "+exc);
    }

}

private void start(){
//启动一个固定大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(THREADPOLL_SIZE);

   try {
       //将线程池加入异步通道,进行资源共享
       channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
       //异步通道打开
       serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
       serverSocketChannel.bind(new InetSocketAddress(LOCALHSOT,port));
       System.out.println("服务启动,监听端口: "+port);
       while (true){
           serverSocketChannel.accept(null,new AcceptHandler());
           System.in.read();//阻塞调用,防止占用系统资源,一致调用accept函数
       }
   } catch (IOException e) {
       e.printStackTrace();
   }finally {
       close(serverSocketChannel);
   }

}

public static void main(String[] args) {
    AIOServer server = new AIOServer(8848);
    server.start();
}

}

客户端
public class AIOClient {

private static final String LOCALHSOT ="localhost";

private static final  int DEFAUT_PORT=8848;
//退出标识
private static final String QUIT="quit";

private static final int BUFFER=1024;

private String hsot;

private int port;


private AsynchronousSocketChannel clientChannel;

private Charset charset = Charset.forName("UTF-8");


public AIOClient(){
    this(LOCALHSOT,DEFAUT_PORT);
}
public AIOClient(String host,int port){
     this.hsot = host;
     this.port = port;
}

//关闭流并且释放与之关联的资源
private void close(Closeable closeable){

    if(closeable!=null){

        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
//判断客户端是否准备退出
public boolean redatQuit(String msg){

    return QUIT.equals(msg);
}

private void start(){

    //打开连接通道
    try {
        clientChannel = AsynchronousSocketChannel.open();
        //获取一个连接
        Future future = clientChannel.connect(new InetSocketAddress(hsot,port));
        try {
            future.get();//阻塞式调用
            //启动一个新的线程,处理用户的输入
            new Thread(new AioClientHandler(this)).start();
            //申请缓存
            ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER);
            while (true){
                //启动异步读操作,从通道读取的数据放入换冲区
                Future readResult = clientChannel.read(byteBuffer);
               //阻塞拿结果
                int result = readResult.get();
                if(result<=0){
                    System.out.println("服务端通讯异常,断开连接");
                    close(clientChannel);
                    //非正常退出
                    System.exit(1);
                }else {
                      byteBuffer.flip();//重置读取位置
                      //消息解码
                      String msg = String.valueOf(charset.decode(byteBuffer));
                      byteBuffer.clear();
                      System.out.println(msg);
                }

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}
public  void send(String msg){
    if(msg.isEmpty()){
        return;
    }
    //消息加密
    ByteBuffer byteBuffer = charset.encode(msg);
    //阻塞发送消息
    Future writeResult = clientChannel.write(byteBuffer);

    try {
        //获取发送消息结果
        writeResult.get();
    } catch (InterruptedException e) {
        e.printStackTrace();
        System.out.println("消息发送失败");
    } catch (ExecutionException e) {
        e.printStackTrace();
        System.out.println("消息发送失败");
    }
}

public static void main(String[] args) {
    AIOClient aioClient = new AIOClient();
    aioClient.start();
}

}

查看结果:
图解io+BIO+NIO+AIO+Netty+聊天室实战_第16张图片

图解io+BIO+NIO+AIO+Netty+聊天室实战_第17张图片

图解io+BIO+NIO+AIO+Netty+聊天室实战_第18张图片

你可能感兴趣的:(java,内核,网络,netty)