Java通讯模型(BIO、NIO、AIO)笔记

Java通讯模型(BIO、NIO、AIO)


1、常规技术:Spring系统、ORM组件、服务支持
数据库表的CRUD处理(重复且大量的编写),这种开发好像不是开发的感觉
2、未来的开发人才到底应该具备哪些技能 —— 架构师

A、可以完成项目,同时可以很好的沟通;
B、掌握各种常规的开发技术,并且掌握一些服务组件的使用(需要有好的运维);
C、良好的代码设计能力 —— 代码重用与标准设定;
D、非常清楚底层通讯机制,并且可以根据实际的业务需求,进行底层通讯协议的定义。

3、网络通讯的核心思想:请求-响应
网络七层模型:
应用层、表示层、回话层、传输层(数据段)、网络层(数据包)、数据链路层(数据帧)、物理层(比特流)
TCP协议是整个现代项目开发的基础,包括HTTP协议也都是在TCP基础上实现的。

案例:采用一个标准的ECHO程序
客户端输入一个内容,随后服务器端接收到之后进行数据的返回,在数据前面追加有"【ECHO】"的信息。
“telnet 主机名称 端口号”,主要是进行TCP协议的通讯,而对于服务器端是如何实现的并不关注。

BIO程序实现

【JDK 1.0】实现BIO程序开发:同步阻塞IO操作,每一个线程都只会管理一个客户端的连接,这种操作的本质是存在有程序阻塞的问题。
线程的资源总是在不断的进行创建,并且所有的数据接收里面(Scanner、PrintStrean简化了),网络的数据都是长度限制的,
传统的数据是需要通过字节数组的形式搬砖完成的。
BIO:是需要对数据采用蚂蚁搬家的模式完成的
程序问题:性能不高,多线程的利用率不高,如果大规模的用户访问,有可能会造成服务器端资源的耗尽。
服务器端

/**
 * 实现服务端的编写开发,采用BIO(阻塞模式)实现开发的基础结构
 */
public class EchoServer {
    public static void main(String[] args) throws Exception{
        new EchoServerHandle();
    }
}
class EchoServerHandle implements AutoCloseable{
    ServerSocket serverSocket;
    public EchoServerHandle() throws Exception{
        // 进行服务器端Socket的启动
        this.serverSocket = new ServerSocket(ServerInfo.PORT);
        System.out.println("ECHO服务器端已经启动,该服务在" + ServerInfo.PORT + "端口上监听...");
        this.clientConnect();
    }
    private void clientConnect() throws Exception{
        boolean serverFlag = true;
        if(serverFlag){
            Socket client = this.serverSocket.accept(); // 等待客户端的连接
            new Thread(() -> {
                try {
                    // 服务端的输入为客户端的输出
                    Scanner scan = new Scanner(client.getInputStream());
                    // 服务端的输出为客户端的输入
                    PrintStream out = new PrintStream(client.getOutputStream());
                    // 设置分隔符
                    scan.useDelimiter("\n");
                    boolean clientFlag = true;
                    while(clientFlag){
                        if(scan.hasNext()){// 现在有内容
                            String inputData = scan.next(); // 获得输入数据
                            inputData = inputData.replaceAll("\r|\n", "");
                            System.out.println(inputData);
                            if("exit".equalsIgnoreCase(inputData)){// 信息结束
                                // 这里一定需要提供一个换行的机制,否则Scanner不好读取
                                out.println("[ECHO] Bye Bye ...");
                                clientFlag = false; // 结束循环
                            }else{
                                // 回应信息
                                out.println("[ECHO] " + inputData);
                            }
                        }
                    }
                    client.close();
                }catch (Exception e){}
            }).start();//启动线程
        }
    }
    @Override
    public void close() throws Exception {
        this.serverSocket.close();
    }
}

客户端

/**
 * 实现客户端的编写开发
 */
public class EchoClient {
    public static void main(String[] args) {
        try(EchoClientHandle echo = new EchoClientHandle()) {

        }catch (Exception e){}
    }
}
class EchoClientHandle implements AutoCloseable{
    private Socket client;
    public EchoClientHandle() throws Exception{
        this.client = new Socket(ServerInfo.ECHO_SERVER_HOST, ServerInfo.PORT);
        System.out.println("已经成功的连接到了服务器,可以进行消息的发送处理");
        this.accessServer();
    }
    /**
     * 数据交互处理
     * @throws Exception
     */
    private void accessServer() throws Exception{
        // 服务器端的输出为客户端的输入
        Scanner scan = new Scanner(this.client.getInputStream());
        // 向服务端发送内容
        PrintStream out = new PrintStream(this.client.getOutputStream());
        scan.useDelimiter("\n");
        boolean flag = true;
        while (flag){
            String data = InputUtil.getString("请输入要发送的数据信息:");
            out.println(data); // 先把内容发送到服务器端上
            if("exit".equalsIgnoreCase(data)) {
                flag = false; // 结束循环
            }
            if(scan.hasNext()){
                System.out.println(scan.next());
            }
        }
    }
    @Override
    public void close() throws Exception {
        this.client.close();
    }
}

NIO程序实现

【JDK 1.4】提供了一个java.nio的开发包,从这一时代开始,Jav提升了IO的处理效率,同时也提升了网络模型的处理效率, 同时NIO里面采用的是同步非阻塞IO操作。NIO的出现在当时来讲,给Java带来了一个最伟大的通讯利器(已经接近了底层的传输性能)。
NIO里面提倡使用Buffer来代替传统的数组操作(蚂蚁搬家),可以减少数组的操作复杂度,利用缓存数据的保存于方便的清空操作进行处理。
Reactor模型提倡的是:公共注册,统一操作,所以会提供有一系列的Channel(通道)。
服务器端

/**
 * 基于NIO实现数据的交互处理模式
 */
public class EchoServer {
    public static void main(String[] args) throws Exception {
        new EchoServerHandle();
    }
}
/**
 * 实现一个专门用于客户端请求处理的线程对象
 */
class ClientChannelThread implements Runnable{
    private SocketChannel channel;
    private boolean flag = true; // 循环处理的标志
    public ClientChannelThread(SocketChannel channel){
        this.channel = channel;
        System.out.println("客户端与服务器端连接成功,可以进行数据的交互操作了...");
    }
    /**
     * 真正的通讯处理的核心需要通过run()方法来进行操作
     */
    @Override
    public void run(){
        // NIO是基于Buffer缓冲操作实现的功能,需要将输入的内容保存到缓存中
        ByteBuffer buffer = ByteBuffer.allocate(50); // 开辟一个50大小的缓存空间
        try {
            while(this.flag){
                // 先清空缓存操作,可以进行缓存空间的重复使用
                buffer.clear();
                // 服务器端读取客户端发送来的数据
                int readCount = this.channel.read(buffer);
                System.out.println(readCount);
//                buffer.flip();
                // 将缓存区中保存的内容转为字节数组之后进行存储
                String readMessage = new String(buffer.array(), 0, readCount).trim();
                System.out.println("[服务器端接收消息] " + readMessage); // 输出一下提示信息
                // 在进行整个通讯过程里,分隔符是一个绝对重要的概念,如果不能够很好的处理分隔符,那么无法进行有效的通讯
                String writeMessage = "[ECHO] " + readMessage + "\n"; // 进行消息的回应处理
                if("exit".equalsIgnoreCase(readMessage)){
                    writeMessage = "[ECHO] Bye Bye ..."; // 结束消息
                    this.flag = false;
                    // 现在我们的数据是在字符串中,如果要回应消息,需要将内容保存到Buffer中
                }
                buffer.clear(); // 将已经处理完的内容清除
                buffer.put(writeMessage.getBytes()); // 保存回应信息
                buffer.flip(); // 重置缓冲区
                this.channel.write(buffer);
            }
            this.channel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * 定义服务器端的处理类
 */
class EchoServerHandle implements AutoCloseable{
    private ExecutorService executor;
    // 服务器端的通讯通道
    private ServerSocketChannel serverChannel;
    //
    private Selector selector;
    // 客户端的通讯通道
    private SocketChannel clientChannel;
    public EchoServerHandle() throws Exception {
        // 当前执行的线程只允许有5个
        this.executor = Executors.newFixedThreadPool(5);
        // 打开服务器的连接通道
        this.serverChannel = ServerSocketChannel.open();
        // 设置为非阻塞模式
        this.serverChannel.configureBlocking(false);
        // 绑定端口
        this.serverChannel.bind(new InetSocketAddress(ServerInfo.PORT));
        // NIO 中的Reactor模型重点在于所有的Channel需要向Selector之中注册
        // 获取Selector的实例
        this.selector = Selector.open();
        // 注册
        this.serverChannel.register(this.selector, SelectionKey.OP_ACCEPT);//服务器端需要进行接收
        System.out.println("服务器端程序启动,改程序在" + ServerInfo.PORT + "端口上进行监听...");
        this.clientHandle();
    }
    public void clientHandle() throws Exception{
        int keySelect = 0; // 保存当前的状态
        while ((keySelect = this.selector.select())>0){// 需要进行连接等待
            // 获取全部的连接通道信息
            Set<SelectionKey> selectionKeys = this.selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while(iterator.hasNext()){
                // 获取每一个通道
                SelectionKey selectionKey = iterator.next();
                if(selectionKey.isAcceptable()){ // 该通道为接收状态
                    this.clientChannel = this.serverChannel.accept(); // 等待连接
                    if(this.clientChannel != null){// 当前已经有连接了
                        // 线程池将该连接提交给客户端的通道线程去处理
                        this.executor.submit(new ClientChannelThread(this.clientChannel));
                    }
                }
                // 移出此通道
                iterator.remove();
            }
        }
    }
    @Override
    public void close() throws Exception {
        this.executor.shutdown(); // 关闭线程池
        this.serverChannel.close(); // 关闭服务端通道
    }
}

客户端

/**
 *  进行NIO客户端的连接访问
 */
public class EchoClient{
    public static void main(String[] args) throws Exception{
        new EchoClientHandle();
    }
}
class EchoClientHandle implements AutoCloseable{
    private SocketChannel clientChannel;
    public EchoClientHandle() throws Exception{
        // 创建一个客户端的通道实例
        this.clientChannel = SocketChannel.open();
        // 设置要连接的主机信息,包括主机名称以及端口号
        this.clientChannel.connect(new InetSocketAddress(ServerInfo.ECHO_SERVER_HOST, ServerInfo.PORT));
        this.accessServer();
    }
    /**
     * 访问服务器端
     */
    public void accessServer() throws Exception{
        ByteBuffer buffer = ByteBuffer.allocate(50); // 开辟缓冲区
        boolean flag = true;
        while (flag){
            buffer.clear();
            String msg = InputUtil.getString("请输入要发送的内容:");
            buffer.put(msg.getBytes());
            buffer.flip();
            this.clientChannel.write(buffer); // 发送出具内容
            // 当消息发送过去之后还需要进行返回内容的接收处理
            buffer.clear();
            // 将内容读取到缓冲区,并返回个数
            int readCount = this.clientChannel.read(buffer);
            buffer.flip();
            System.out.println(new String(buffer.array(), 0, readCount));
            if("exit".equalsIgnoreCase(msg)){
                flag = false;
            }
        }
    }
    @Override
    public void close() throws Exception {
        this.clientChannel.close();
    }
}

AIO程序实现

【JDK 1.7】AIO,异步非阻塞IO通讯,需要采用大量的回调处理模式,所以需要使用:
public interface CompletionHandler<V, A>
服务器端

/**
 * 基于AIO实现数据的交互处理模式
 */
public class EchoServer {
    public static void main(String[] args) throws Exception{
        new Thread(new AIOServerThread()).start();
    }
}
/**
 * 1、实现客户端连接回调的处理操作
 */
class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AIOServerThread>{

    @Override
    public void completed(AsynchronousSocketChannel result, AIOServerThread attachment) {
        System.out.println("客户端与服务器端连接成功,可以进行数据的交互操作了...");
        // 接收连接对象
        attachment.getServerChannel().accept(attachment, this);
        ByteBuffer buffer = ByteBuffer.allocate(50);
        result.read(buffer, buffer, new EchoHandler(result));
    }

    @Override
    public void failed(Throwable exc, AIOServerThread attachment) {
        System.out.println("服务器端客户端连接失败...");
        attachment.getLatch().countDown();  // 恢复执行
    }
}
/**
 * 2、实现客户端的回应处理操作
 */
class EchoHandler implements CompletionHandler<Integer, ByteBuffer>{
    // 客户端的通道
    private AsynchronousSocketChannel clientChannel;
    private boolean exit = false; // 进行操作结束的标志
    public EchoHandler(AsynchronousSocketChannel clientChannel){
        this.clientChannel = clientChannel;
    }
    @Override
    public void completed(Integer result, ByteBuffer buffer) {
        buffer.flip();
        String readMessage = new String(buffer.array(), 0, buffer.remaining()).trim();
        System.out.println("[服务器端接收到消息内容]" + readMessage);
        String resultMessage = "[ECHO] " + readMessage + "\n";//回应消息
        if("exit".equalsIgnoreCase(readMessage)){
            resultMessage = "[ECHO] Bye Bye..." + "\n";
        }
        this.echoWrite(resultMessage);
    }
    private void echoWrite(String result){
        ByteBuffer buffer = ByteBuffer.allocate(50);
        buffer.put(result.getBytes());
        buffer.flip();
        this.clientChannel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer buffer) {
                if(buffer.hasRemaining()){ // 当前有数据
                    EchoHandler.this.clientChannel.write(buffer, buffer, this);
                }else {
                    if (EchoHandler.this.exit == false){//需要继续操作
                        ByteBuffer readBuffer = ByteBuffer.allocate(50);
                        EchoHandler.this.clientChannel.read(readBuffer, readBuffer, new EchoHandler(EchoHandler.this.clientChannel));
                    }
                }
            }
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    EchoHandler.this.clientChannel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.clientChannel.close();// 关闭通道
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
/**
 * AIO线程处理类
 */
class AIOServerThread implements Runnable{
    private AsynchronousServerSocketChannel serverChannel;
    private CountDownLatch latch; // 进行线程等待操作
    public AIOServerThread() throws Exception{
        this.latch = new CountDownLatch(1); // 设置一个线程等待个数
        this.serverChannel = AsynchronousServerSocketChannel.open();
        // 绑定服务端口
        this.serverChannel.bind(new InetSocketAddress(ServerInfo.PORT));
        System.out.println("服务器端程序启动,改程序在" + ServerInfo.PORT + "端口上进行监听...");
    }
    public AsynchronousServerSocketChannel getServerChannel(){
        return serverChannel;
    }
    public CountDownLatch getLatch(){
        return latch;
    }
    @Override
    public void run(){
        // 等待客户端的连接
        this.serverChannel.accept(this, new AcceptHandler());
        try {
            this.latch.await(); // 进入等待时机
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客户端

/**
 * 基于AIO实现客户端的连接访问
 */
public class EchoClient {
    public static void main(String[] args) throws Exception{
        AIOClientThread client = new AIOClientThread();
        new Thread(client).start();
        while (client.sendMessage(InputUtil.getString("请输入要发送的信息:"))){
        }
    }
}
/**
 * 客户端读操作
 */
class ClientReadHandler implements CompletionHandler<Integer, ByteBuffer>{
    private AsynchronousSocketChannel clientChannel;
    private CountDownLatch latch;
    public ClientReadHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch){
        this.clientChannel = clientChannel;
        this.latch = latch;
    }
    @Override
    public void completed(Integer result, ByteBuffer buffer) {
        buffer.flip();
        String receiveMessage = new String(buffer.array(), 0, buffer.remaining());
        System.out.println(receiveMessage);
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.clientChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.latch.countDown();
    }
}
/**
 * 客户端写操作
 */
class ClientWriteHandler implements CompletionHandler<Integer, ByteBuffer>{
    private AsynchronousSocketChannel clientChannel;
    private CountDownLatch latch;
    public ClientWriteHandler(AsynchronousSocketChannel clientChannel, CountDownLatch latch){
        this.clientChannel = clientChannel;
        this.latch = latch;
    }
    @Override
    public void completed(Integer result, ByteBuffer buffer) {
        if(buffer.hasRemaining()){
            this.clientChannel.write(buffer, buffer, this);
        }else {
            ByteBuffer readBuffer = ByteBuffer.allocate(50);
            this.clientChannel.read(readBuffer, readBuffer, new ClientReadHandler(this.clientChannel, this.latch));
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.clientChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        this.latch.countDown();
    }
}
class AIOClientThread implements Runnable{
    private AsynchronousSocketChannel clientChannel;
    private CountDownLatch latch;
    public AIOClientThread() throws Exception{
        // 打开客户端的连接通道
        this.clientChannel = AsynchronousSocketChannel.open();
        // 连接服务端
        this.clientChannel.connect(new InetSocketAddress(ServerInfo.ECHO_SERVER_HOST, ServerInfo.PORT));
        this.latch = new CountDownLatch(1);
    }
    @Override
    public void run(){
        try {
            this.latch.await();
            this.clientChannel.close();
        }catch (Exception e){}
    }
    /**
     * 进行消息的发送处理
     * @param msg   输入的交互信息
     * @return  是否停止交互的处理
     */
    public boolean sendMessage(String msg) throws Exception{
        ByteBuffer buffer = ByteBuffer.allocate(50);
        buffer.put(msg.getBytes());
        buffer.flip();
        this.clientChannel.write(buffer, buffer, new ClientWriteHandler(this.clientChannel, this.latch));
        if("exit".equalsIgnoreCase(msg)){
            return false;
        }else {
            return true;
        }
    }
}

总结:BIO(同步阻塞IO):在进行处理的时候是通过一个线程进行操作,并且IO实现通讯的时候采用的是阻塞模式;
例如:水壶烧水,在BIO的世界里,烧水这一过程你需要从头一直监视到结尾
NIO(同步非阻塞IO):不断的进行烧水状态的判断,同时你可以做其他的事情;
AIO(异步非阻塞IO):烧水的过程你不用关注,如果水一旦烧好了,就会给你一个反馈。

以上所讲解的程序都属于最为基础的通讯模型,但是如果你真的只是依靠这样的开发操作去编写程序,那么基本上就决定你的项目八成会失败。
真实的项目之中如果要想实现这些通讯的操作一般要考虑:粘包和拆包、序列化、HTTP协议如何去写、WebSocket的定义实现。都需要你去精通这些协议。

为了简化这些操作在实际的项目里面,强烈建议,这些底层功能都通过Netty包装。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

讲文明的喜羊羊拒绝pua

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

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

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

打赏作者

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

抵扣说明:

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

余额充值