首页 > 技术文章 > Java IO介绍之BIO同步阻塞模型,C10K, C10M问题

lwdesire 2021-10-18 21:01 原文

JavaIO介绍之BIO 同步阻塞模型

同步与异步调用

何为同步?

  • 同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为

何为异步?

  • 异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而,异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作
  • 图解

同步/异步不能与阻塞/非阻塞混淆

  • 阻塞和非阻塞 强调的是程序在等待调用结果(消息,返回值)时的状态. 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。 对于同步调用来说,很多时候当前线程还是激活的状态,只是从逻辑上当前函数没有返回而已,即同步等待时什么都不干,白白占用着资源。

  • 同步和异步强调的是消息通信机制 (synchronous communication/ asynchronous communication)。所谓同步,就是在发出一个"调用"时,在没有得到结果之前,该“调用”就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由“调用者”主动等待这个“调用”的结果。而异步则是相反,"调用"在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在"调用"发出后,"被调用者"通过状态、通知来通知调用者,或通过回调函数处理这个调用

了解完概念后继续看BIO对应的Java BIO编程模型

  • 在JDK1.4之前,建立网络通信采用的BIO模型,为啥称之为同步阻塞模型, accept,read/write,connect,客户端发起请求后,必须有个对应服务端线程来处理客户端请求, 而线程资源是有限的,一旦客户端并发请求过大,经典面试题会到C10k,C10M问题
  • Socket Client
package com.cnblogs.cx.net;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class TCPClient {
    public static void main(String[] args) throws IOException {
        String host = "127.0.0.1";
        Socket socket = new Socket(InetAddress.getByName(host),9999);

        OutputStream outputStream = socket.getOutputStream();
        /*FileInputStream fis = new FileInputStream(new File("test.png"));


        byte[] buffer = new byte[1024];
        int len ;
        while(( len = fis.read(buffer))!=-1){
            outputStream.write(buffer,0,len);
        }*/
        //while(true) {
            Scanner scanner = new Scanner(System.in);
            String inStr = scanner.nextLine();
            outputStream.write(inStr.getBytes());
           // socket.shutdownOutput();

            InputStream inputStream = socket.getInputStream();
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            byte[] bs = new byte[1024];
            int len2;
            while ((len2 = inputStream.read(bs)) != -1) {
                bos.write(bs, 0, len2);
            }
            System.out.println(bos.toString());
        //}

        outputStream.close();
        inputStream.close();
        bos.close();
        socket.close();
    }
}
  • Socket Server
package com.cnblogs.cx.net;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);

        Socket socket = null;
        InputStream is = null;
        ByteArrayOutputStream bos = null;
        OutputStream os = null;
        while(true) {
             socket = serverSocket.accept();

             is = socket.getInputStream();

             bos = new ByteArrayOutputStream();
             byte[] by = new byte[1024];
             int len;
             while ((len = is.read(by)) != -1) {
                bos.write(by, 0, len);
             }
             System.out.println(bos.toString());
            /*FileOutputStream fos = new FileOutputStream(new File("recive.jpg"));

            byte[] by = new byte[1024];
            int len ;
            while ((len = is.read(by))!=-1){
                fos.write(by,0,len);
            }*/

            Scanner scanner = new Scanner(System.in);
            String str =  scanner.nextLine();
            os = socket.getOutputStream();
            os.write("str".getBytes());
        }


      /*  is.close();
        bos.close();
        os.close();
        socket.close();
        serverSocket.close();*/

    }
}
  • 为了提高客户端的并发请求,优化方式可以将阻塞的地方拆分,引入多线程; Java编程模型解决方案如下:
  • 服务端编程:
public class MutilThreadBIOServer {
    public static void main(String[] args) {
        try {
            //创建serverSocket实例
            ServerSocket serverSocket = new ServerSocket();

            //绑定端口
            serverSocket.bind(new InetSocketAddress(9999));
            System.out.println("服务端启动了");

            while (true) {
                //等待多个客户端的连接
                Socket socket = serverSocket.accept();
                System.out.println("客户端:"+socket.getRemoteSocketAddress()+" 上线了");

                //将Socket实例交给子线程处理
                new ServerHandler(socket).start();
            }

            //关闭资源
//            serverSocket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class ServerHandler extends Thread{
    private Socket socket;

    /**
     * 构造函数,将socket实例传递给子线程
     * @param socket
     */
    public ServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            //读取客户端的消息
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //回复消息
            OutputStream outputStream = socket.getOutputStream();
            String msg = null;
            while ((msg = reader.readLine())!= null) {
                System.out.println("客户端:"+socket.getRemoteSocketAddress()+" 消息:"+msg);

                //给客户端回复消息
                outputStream.write((msg+"\n").getBytes());

                //循环结束条件
                if ("".equals(msg) || "exit".equals(msg)) break;
            }

            //关闭资源
            reader.close();
            outputStream.close();
            System.out.println("客户端:"+socket.getRemoteSocketAddress()+" 关闭");
            socket.close();

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  • 客户端编程
//启动socket实例
        Socket socket = new Socket();

        try {
            //连接服务端
            socket.connect(new InetSocketAddress("127.0.0.1",9999));
            System.out.println("客户端连接服务端成功");

            //进行读写操作
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write("hello\n".getBytes());
            outputStream.flush();

            //读数据
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg = reader.readLine();
            System.out.println(msg);

            //关闭资源
            reader.close();
            outputStream.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

总结: 了解Java BIO同步阻塞模型, 熟悉Socket同步阻塞通讯方式,C10K,C10M问题。

推荐阅读