NIO&AIO

IO的方式通常分为几种,同步阻塞的BIO、同步非阻塞的NIO、异步非阻塞的AIO。

BIO是一个连接一个线程。

NIO(非阻塞的多路复用)是一个请求一个线程。

AIO是一个有效请求一个线程。

同步 : 自己亲自出马持银行卡到银行取钱(使用同步IO时,Java自己处理IO读写);
异步 : 委托一小弟拿银行卡到银行取钱,然后给你(使用异步IO时,Java将IO读写委托给OS处理,需要将数据缓冲区地址和大小传给OS(银行卡和密码),OS需要支持异步IO操作API);
阻塞 : ATM排队取款,你只能等待(使用阻塞IO时,Java调用会一直阻塞到读写完成才返回);
非阻塞 : 柜台取款,取个号,然后坐在椅子上做其它事,等号广播会通知你办理,没到号你就不能去,你可以不断问大堂经理排到了没有,大堂经理如果说还没到你就不能去(使用非阻塞IO时,如果不能读写Java调用会马上返回,当IO事件分发器会通知可读写时再继续进行读写,不断循环直到读写完成)

在这里插入图片描述

前引 BIO

在JDK1.4之前,我们建立网络连接的时候采用BIO模式,需要先在服务端启动一个serverSocket,然后在客户端启动socket来对服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程相应,如果没有则会一直等待或者遭到拒绝请求,如果有的话,客户端线程会等待请求结束后才继续执行。
在这里插入图片描述

在此种方式下,用户进程在发起一个IO操作以后,必须等待IO操作的完成,只有当真正完成了IO操作以后,用户进程才能运行。JAVA传统的IO模型属于此种方式!

服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

一、NIO(同步非阻塞,又称 非阻塞的多路复用)

NIO都是同步的:不管是客户端还是服务端,都一直在轮询,主动获取对方消息,只要不连通,就一直轮询,直到客户端和服务端之间连接上。

BIO和NIO一个比较重要的不同,是我们使用BIO的时候往往会引入多线程,每个连接一个单独的线程;而NIO则是使用单线程或者使用少量线程,每个连接公用一个线程

在这里插入图片描述

在此种方式下,用户进程发起一个IO操作以后边可返回做其它事情,但是用户进程需要时不时的询问IO操作是否就绪,这就要求用户进程不停的去询问,从而引入不必要的CPU资源浪费。其中目前JAVA的NIO就属于同步非阻塞IO。

服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

1.0 概念名词

1.0.1多路复用

选择器Selector是NIO中的重要技术之一。它与SelectableChannel联合使用实现了非阻塞的多路复用。使用它可以
节省CPU资源,提高程序的运行效率。

"多路"是指:服务器端同时监听多个“端口”的情况。每个端口都要监听多个客户端的连接。

服务器端的非多路复用效果

如果不使用“多路复用”,服务器端需要开很多线程处理每个端口的请求。如果在高并发环境下,造成系统性能
下降。

在这里插入图片描述

服务器端的非多路复用效果
在这里插入图片描述
使用了多路复用,只需要一个线程就可以处理多个通道,降低内存占用率,减少CPU切换时间,在高并发、
高频段业务环境下有非常重要的优势

1.0.2 Selector(选择器,又称多路复用器)

Selector被称为:选择器,也被称为:多路复用器,它可以注册到很多个Channel上,监听各个Channel上发生的
事件,并且能够根据事件情况决定Channel读写。这样,通过一个线程管理多个Channel,就可以处理大量网络连
接了。
有了Selector,我们就可以利用一个线程来处理所有的Channels。线程之间的切换对操作系统来说代价是很高的,
并且每个线程也会占用一定的系统资源。所以,对系统来说使用的线程越少越好。

1.1 NIO服务端

package com.demo.myshiro_demo.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/**
 * nio 服务端
 *
 * @author lc
 * @version 1.0
 * @date 2022/3/11 13:36
 */
public class TCPServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1. 开启服务器通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //2. 设置监听的端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));

        // 设置轮询,循环等待客户端连接
        serverSocketChannel.configureBlocking(false); // true 默认阻塞(只尝试一次) false非阻塞(一直等待被连接)
        while (true) {
            //3. 调用accept,等到客户端连接
            System.out.println("=================服务器等待客户端连接");
            SocketChannel accept = serverSocketChannel.accept();
            if (accept != null) {
                System.out.println("==========有客户端连接了");

                System.out.println("=============读取客户端发来的数据==============");
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024); // 缓存区
                int len = accept.read(byteBuffer);
                //可能读到的内容没有将ByteBuffer缓冲区填充满,所以我们缩小limit限定,合理利用ByteBuffer空间
                byteBuffer.flip();
                String message = new String(byteBuffer.array(),0,byteBuffer.limit());
                System.out.println("服务端读到客户端的信息为:"+message);

                System.out.println("================向客户端回写数据=============");

                ByteBuffer byteBuffer1 = ByteBuffer.wrap("你好客户端".getBytes());
                //向客户端回写数据
                accept.write(byteBuffer1);
                break; // 有客户端连接了,就不轮询了
            } else {
                System.out.println("=============没有客户端连接,睡了");
                Thread.sleep(1000l);
                System.out.println("============");
            }
        }


    }
}

1.2 NIO客户端

package com.demo.myshiro_demo.nio;

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

/**
 * nio 客户端
 *
 * @author lc
 * @version 1.0
 * @date 2022/3/11 13:47
 */
public class TCPClient {
    public static void main(String[] args) throws IOException {

        while (true) {

            try {
                // 1.打开客户端通道
                SocketChannel socketChannel = SocketChannel.open();


                // 2.连接服务端
                socketChannel.configureBlocking(true);

                System.out.println("=============准备连接服务器");
                boolean b = socketChannel.connect(new InetSocketAddress("127.0.0.1", 8888));
                System.out.println(b);
                System.out.println("===========客户端服务器连接成功");
                System.out.println("==============向服务器发送数据");
                ByteBuffer byteBuffer = ByteBuffer.wrap("你好服务器".getBytes());

                System.out.println("容量:" + byteBuffer.capacity());
                System.out.println("索引:" + byteBuffer.position());
                System.out.println("限定:" + byteBuffer.limit());

                // 往服务器端写数据
                socketChannel.write(byteBuffer);
                System.out.println("=============向服务器发送数据完毕,开始读取服务器响应回来的数据==============");
                ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024); // 缓冲区
                int len = socketChannel.read(byteBuffer1);
                //可能读到的内容没有将ByteBuffer缓冲区填充满,所以我们缩小limit限定,合理利用ByteBuffer空间
                byteBuffer1.flip();
                String message = new String(byteBuffer1.array(), 0, byteBuffer1.limit());
                System.out.println("客户端读到服务端的信息为:"+message);
                //关闭客户端通道
                socketChannel.close();
                //连接成功,结束轮询
                break;
            } catch (IOException e) {
                System.out.println("========客户端连接服务器端失败");
                e.printStackTrace();
            }

        }


    }
}

先启动服务端,在启动客户端
在这里插入图片描述
在这里插入图片描述

1.3 实现多路复用

  • 如何创建一个Selector
    Selector selector = Selector.open();

  • 注册Channel到Selector
    channel.configureBlocking(false);
    SelectionKey key =channel.register(selector,SelectionKey.OP_READ);

  • 四种不同类型的事件,而且可以使用SelectionKey的四个常量表示:

  1. 连接就绪–常量:SelectionKey.OP_CONNECT
  2. 接收就绪–常量:SelectionKey.OP_ACCEPT (ServerSocketChannel在注册时只能使用此项)
    Selector selector = Selector.open();
    channel.configureBlocking(false);
    SelectionKey key =channel.register(selector,SelectionKey.OP_READ);
  3. 读就绪–常量:SelectionKey.OP_READ
  4. 写就绪–常量:SelectionKey.OP_WRITE
    注意:对于ServerSocketChannel在注册时,只能使用OP_ACCEPT,否则抛出异常。

服务器端代码:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Server {
	public static void main(String[] args) throws Exception {
		//1.同时监听三个端口:7777,8888,9999
		ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
		serverChannel1.bind(new InetSocketAddress(7777));
		serverChannel1.configureBlocking(false);
		
		ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
		serverChannel2.bind(new InetSocketAddress(8888));
		serverChannel2.configureBlocking(false);
		
		ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
		serverChannel3.bind(new InetSocketAddress(9999));
		serverChannel3.configureBlocking(false);
		
		//2.获取一个选择器
		Selector selector = Selector.open();
		//3.注册三个通道
		SelectionKey key1 = serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
		SelectionKey key2 = serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
		SelectionKey key3 = serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
		
		//4.循环监听三个通道
		while (true) {
			System.out.println("等待客户端连接...");
			int keyCount = selector.select();
			System.out.println("连接数量:" + keyCount);
			//遍历已连接的每个通道的SelectionKey
			Set<SelectionKey> keys = selector.selectedKeys();
			Iterator<SelectionKey> it = keys.iterator();
			while (it.hasNext()) {
				SelectionKey nextKey = it.next();
				System.out.println("获取通道...");
				ServerSocketChannel channel = (ServerSocketChannel)
				nextKey.channel();
				System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
				SocketChannel socketChannel = channel.accept();
				//接收数据
				ByteBuffer inBuf = ByteBuffer.allocate(100);
				socketChannel.read(inBuf);
				inBuf.flip();
				String msg = new String(inBuf.array(), 0, inBuf.limit());
				System.out.println("【服务器】接收到通道【" +
				channel.getLocalAddress() + "】的信息:" + msg);
			}
		}
	}
}

客户端代码:


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

public class Client {
	public static void main(String[] args) throws InterruptedException {
		//两个线程,模拟两个客户端,分别连接服务器的7777,8888端口
		new Thread(()->{
			try(SocketChannel socket = SocketChannel.open()) {
				System.out.println("7777客户端连接服务器......");
				socket.connect(new InetSocketAddress("localhost", 7777));
				System.out.println("7777客户端连接成功....");
				//发送信息
				ByteBuffer outBuf = ByteBuffer.allocate(100);
				outBuf.put("我是客户端,连接7777端口".getBytes());
				outBuf.flip();
				socket.write(outBuf);
			} catch (IOException e) {
				System.out.println("7777异常重连");
			}
		}).start();
		
		new Thread(()->{
			try(SocketChannel socket = SocketChannel.open()) {
				System.out.println("8888客户端连接服务器......");
				socket.connect(new InetSocketAddress("localhost", 8888));
				System.out.println("8888客户端连接成功....");
				//发送信息
				ByteBuffer outBuf = ByteBuffer.allocate(100);
				outBuf.put("我是客户端,连接8888端口".getBytes());
				outBuf.flip();
				socket.write(outBuf);
			} catch (IOException e) {
				System.out.println("8888异常重连");
			}
		}).start();
	}
}

先启动服务器,再启动客户端,打印结果:

在这里插入图片描述

问题就出现在获取selectedKeys()的集合。
第一次的7777连接,selectedKeys()获取的集合中只有一个SelectionKey对象。
第二次的8888连接,selectedKeys()获取的集合中有2个SelectionKey对象,一个是连接7777客户端的,另一
个是连接8888客户端的。而此时应该只处理连接8888客户端的,所以在上一次处理完7777的数据后,应该将
其SelectionKey对象移除。

更改服务器端代码:

public class Server {
	public static void main(String[] args) throws Exception {
		//1.同时监听三个端口:7777,8888,9999
		ServerSocketChannel serverChannel1 = ServerSocketChannel.open();
		serverChannel1.bind(new InetSocketAddress(7777));
		serverChannel1.configureBlocking(false);
		
		ServerSocketChannel serverChannel2 = ServerSocketChannel.open();
		serverChannel2.bind(new InetSocketAddress(8888));
		serverChannel2.configureBlocking(false);
		
		ServerSocketChannel serverChannel3 = ServerSocketChannel.open();
		serverChannel3.bind(new InetSocketAddress(9999));
		serverChannel3.configureBlocking(false);
		
		//2.获取一个选择器
		Selector selector = Selector.open();
		
		//3.注册三个通道
		SelectionKey key1 = serverChannel1.register(selector, SelectionKey.OP_ACCEPT);
		SelectionKey key2 = serverChannel2.register(selector, SelectionKey.OP_ACCEPT);
		SelectionKey key3 = serverChannel3.register(selector, SelectionKey.OP_ACCEPT);
		
		//4.循环监听三个通道
		while (true) {
			System.out.println("等待客户端连接...");
			int keyCount = selector.select();
			System.out.println("连接数量:" + keyCount);
			//遍历已连接的每个通道的SelectionKey
			Set<SelectionKey> keys = selector.selectedKeys();
			Iterator<SelectionKey> it = keys.iterator();
			
			while (it.hasNext()) {
				SelectionKey nextKey = it.next();
				System.out.println("获取通道...");
				ServerSocketChannel channel = (ServerSocketChannel) nextKey.channel();
				System.out.println("等待【" + channel.getLocalAddress() + "】通道数据...");
				SocketChannel socketChannel = channel.accept();
				//接收数据
				ByteBuffer inBuf = ByteBuffer.allocate(100);
				socketChannel.read(inBuf);
				inBuf.flip();
				String msg = new String(inBuf.array(), 0, inBuf.limit());
				System.out.println("【服务器】接收到通道【" + channel.getLocalAddress() +"】的信息:" + msg);
				//移除此SelectionKey
				it.remove();
			}
		}
	}
}

二、AIO(异步非阻塞)

异步:指的是,不管是客户端还是服务端,不用轮询,先干别的事儿,什么时候通知我连接交相,我再什么时候交互

与NIO不同,当进行读写操作时,只需直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。即可以理解为, read/write方法都是异步的,完成后会主动调用回调函数。 在JDK1.7中,这部分内容成为AIO。

在这里插入图片描述

在此种模式下,用户进程只需要发起一个IO操作然后立即返回,等IO操作真正的完成以后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理就好了,不需要进行实际的IO读写操作,因为真正的IO读取或者写入操作已经由内核完成了。目前Java中还没有支持此种IO模型。

服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是有OS先完成了再通知服务器应用去启动线程进行处理。

在JDK1.7中, 主要在Java.nio.channels包下增加了下
面四个异步通道:
AsynchronousSocketChannel
AsynchronousServerSocketChannel
AsynchronousFileChannel
AsynchronousDatagramChannel

在AIO socket编程中,服务端通道是AsynchronousServerSocketChannel,这个类提供了一个open()静态工厂,
一个bind()方法用于绑定服务端IP地址(还有端口号),另外还提供了accept()用于接收用户连接请求。在客户端使
用的通道是AsynchronousSocketChannel,这个通道处理提供open静态工厂方法外,还提供了read和write方法。
在AIO编程中,发出一个事件(accept read write等)之后要指定事件处理类(回调函数),AIO中的事件处理类
是CompletionHandler<V,A>,这个接口定义了如下两个方法,分别在异步操作成功和失败时被回调。
void completed(V result, A attachment);
void failed(Throwable exc, A attachment);

2.1 AIO服务端

创建AIO的服务器端:
    1.相关的类:
        java.nio.channels.AsynchronousServerSocketChannel:用于面向流的侦听套接字的异步通道。
    2.获取对象的方法:
        static AsynchronousServerSocketChannel open() 打开异步服务器套接字通道。
    3.成员方法:
        void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler)
        参数:
            A attachment:附件,可以传递null
            CompletionHandler handler:事件处理类,用于处理accept方法监听到的ScoketChannel对象
            CompletionHandler也叫回调函数,客户端请求服务器之后,会自动执行CompletionHandler接口中的方法
            CompletionHandler接口中的方法:
                void completed(AsynchronousSocketChannel result, A attachment);客户端连接成功执行的方法
                    
                result就是我们连接之后接收到的客户端对象
                    
                void failed(Throwable exc, A attachment);客户端连接失败执行的方法
     4.实现步骤:
        1.创建异步非阻塞AsynchronousServerSocketChannel服务器对象
        2.使用AsynchronousServerSocketChannel对象中的方法bind绑定指定的端口号
        3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端请求   


public class Server {
    public static void main(String[] args)throws Exception {
//        1.创建异步非阻塞AsynchronousServerSocketChannel服务器对象
        AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
//        2.使用AsynchronousServerSocketChannel对象中的方法bind绑定指定的端口号
        channel.bind(new InetSocketAddress(8888));
        System.out.println("========accept方法开始执行了========");
//        3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端请求
        channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("有客户端连接成功");
            }

            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("有客户端连接失败!");
            }
        });
        System.out.println("=============accept方法执行完毕=============");
        //假如accept方法之后还有好多代码要执行,为了先不让服务器结束,我们先加个while(true){}
        while(true){}
    }
}

现在看不出效果,也调用不了回调函数,以为没有客户端,所以接下来写个客户端

1.启动服务端你之后,调用的accept不会等待,如果没有客户端执行,先执行accept方法外面的代码,这是非阻塞的体现

2.2 AIO客户端


创建AIO的客户端:
    和客户端想关的类:
        java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
    获取对象的方法:
        static AsynchronousSocketChannel open() 打开异步套接字通道。
    成员方法:
        abstract Future<Void> connect(SocketAddress remote) 连接服务器的方法,参数传递服务器的ip地址和端口号
    使用步骤:
        1.创建异步非阻塞AsynchronousSocketChannel客户端
        2.使用AsynchronousSocketChannel客户端对象中的方法connect连接服务器
public class Client {
    public static void main(String[] args)throws Exception {
        //1.创建异步非阻塞AsynchronousSocketChannel客户端
        AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
        //2.使用AsynchronousSocketChannel客户端对象中的方法connect连接服务器
        channel.connect(new InetSocketAddress("127.0.0.1",8888));
    }
}

2.3利用AIO完成异步阻塞的读写

//服务端
/*
  AsynchronousSocketChannel客户端对象中的方法

  Future<Integer> read​(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
  +:Future中的方法->get();可用于获取客户端的信息
*/
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞服务器AsynchronousServerSocketChannel对象
        AsynchronousServerSocketChannel channel = AsynchronousServerSocketChannel.open();
        //给AsynchronousServerSocketChannel对象绑定指定的端口号
        channel.bind(new InetSocketAddress(8888));
        System.out.println("accept方法开始执行....");
        //使用AsynchronousServerSocketChannel对象中的方法accept监听请求的客户端对象
        channel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //客户端连接成功执行的方法
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("有客户端连接成功!");
                //获取客户端中发送的数据
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                /*
                   result就是我们连接之后接收到的客户端对象
                */
                Future<Integer> future = result.read(buffer);//是一个阻塞的方法,没读到写过来的数据,就一直阻塞
                Integer len = null;
                try {
                    len = future.get();//读取客户端中发送的信息
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                String msg = new String(buffer.array(),0,len);
                System.out.println("服务器读取到客户端的信息:"+msg);
            }
            //客户端连接失败执行的方法
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("有客户端连接失败!");
            }
        });
        System.out.println("accept方法执行结束....");
        while (true){}
    }
}


    创建AIO的客户端:
    和客户端相关的类:
        java.nio.channels.AsynchronousSocketChannel:用于面向流的连接插座的异步通道。
    获取对象的方法:
        static AsynchronousSocketChannel open​() 打开异步套接字通道。
    成员方法:
        Future<Void> connect​(SocketAddress remote) 连接此频道。
        Future<Integer> write​(ByteBuffer src) 从给定的缓冲区向该通道写入一个字节序列。
        Future<Integer> read​(ByteBuffer dst) 从该通道读取到给定缓冲区的字节序列。
//客户端
public class TCPClient {
    public static void main(String[] args) throws IOException {
        //创建异步非阻塞客户端AsynchronousSocketChannel对象
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        //使用AsynchronousSocketChannel对象中的方法connect连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
        //连接服务器成功,给服务器发送数据
        ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
        socketChannel.write(buffer);
    }
}

2.4利用AIO完成异步非阻塞的读写

服务端:

AsynchronousSocketChannel中的方法
    void read​(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler) 是一个非阻塞的方法
        参数
                ByteBuffer dst - 要传输字节流缓冲区
                long timeout - 完成I/O操作的最长时间
                TimeUnit unit - timeout参数的时间单位
                attachment - 要附加到I/O操作的对象; 可以是null
                CompletionHandler handler - 消费结果的处理程序,回调函数
                            客户端给服务器发送数据之后,就会执行回调函数中的方法
//服务端
public class TCPServer {
    public static void main(String[] args) throws IOException {
        //1.创建异步非阻塞AsynchronousServerSocketChannel服务器对象
        AsynchronousServerSocketChannel serverSocketChannel = AsynchronousServerSocketChannel.open();
        //2.使用AsynchronousServerSocketChannel对象中的方法bind绑定指定的端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));
        //3.使用AsynchronousServerSocketChannel对象中的方法accept监听客户端请求
        serverSocketChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
            //客户端连接成功执行的方法
            @Override
            public void completed(AsynchronousSocketChannel result, Object attachment) {
                System.out.println("有客户端连接成功!");
             //获取客户端发送的数据
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                //Future<Integer> future = result.read(buffer);//是一个阻塞的方法,没有读取到客户端发送的数据,会阻塞

                /*
                    void read​(ByteBuffer dst, long timeout, TimeUnit unit, A attachment, CompletionHandler<Integer,? super A> handler) 是一个非阻塞的方法
                    参数
                        ByteBuffer dst - 要传输字节的缓冲区
                        long timeout - 完成I / O操作的最长时间->
                                       等待客户端发送数据,等客户端10秒
                                       假如客户端经过5秒钟给服务端发数据,服务端设置的是等客户端10秒
                                       那么5秒在我服务端容忍的10秒钟之内,那么将调用completed方法
                                       正常读取到数据
                                       
                                       假如客户端发数据超出了服务端设置的10秒钟,那么会自动调用 
                                       failed方法
                                        
                        TimeUnit unit - timeout参数的时间单位
                        attachment - 要附加到I / O操作的对象; 可以是null
                        CompletionHandler handler - 消费结果的处理程序,回调函数
                            客户端给服务器发送数据之后,就会执行回调函数中的方法
                 */
                result.read(buffer, 10, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
                    @Override
                    public void completed(Integer result, Object attachment) {
                        System.out.println("读取客户端发送数据成功,执行的方法");
                        buffer.flip();//缩小limit的范围
                        String msg = new String(buffer.array(), 0, buffer.limit());
                        System.out.println("服务器读取到客户端发送的信息:"+msg);
                    }

                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("读取客户端发送数据失败,执行的方法");
                    }
                });
                System.out.println("readu读取数据的方法执行完毕!");
            }

            //客户端连接失败执行的方法
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("有客户端连接失败!");
            }
        });
        System.out.println("accept方法执行结束...");
        while (true){}
    }
}
//客户端
public class TCPClient {
    public static void main(String[] args) throws IOException, InterruptedException {
        //1.创建异步非阻塞AsynchronousSocketChannel客户端
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        //2.使用AsynchronousSocketChannel客户端对象中的方法connect连接服务器
        socketChannel.connect(new InetSocketAddress("127.0.0.1",8888));
        //连接服务器成功,给服务器发送数据
        Thread.sleep(1000*11);
        ByteBuffer buffer = ByteBuffer.wrap("你好服务器".getBytes());
        socketChannel.write(buffer);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LC超人在良家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值