BIO实现以及性能缺陷分析

java / 108人浏览 / 0人评论
网络通信IO的方式通常分为几种,同步阻塞BIO,同步非阻塞NIO,异步非阻塞AIO(NIO2)

什么是BIO?

在JDK1.4之前或者说Tomcat 7之前我们在建立网络通信的时候采用的是BIO模式,需要现在服务端启动一个ServerSocket,然后在客户端启动一个Socket来与服务端进行通信,默认情况下服务端需要对每个请求建立一堆线程等待请求,而客户端发送请求后,先咨询服务端是否有线程响应,如果没有则会一直等待或者拒绝请求,如果有的话,客户端线程会等待请求结束后继续执行。

手写一个BIO

在BIO通信模型中,我们先需要创建一个SocketServer服务端接收并输出传送过来的数据

public class SocketServer {

    static byte[] bs = new byte[1024];

    public static void main(String[] args) throws IOException {
        java.net.ServerSocket serverSocket = new java.net.ServerSocket(8888);//端口为8888
        while (true){
            Socket client = serverSocket.accept();
            client.getInputStream().read(bs);
            System.out.println(new String(bs));
        }
    }
}

创建客户端SocketClient模拟发送数据

public class SocketClient {

    public static void main(String[] args) throws IOException {
        //建立连接并且发送数据
        Socket socket = new Socket("127.0.0.1",8888);
        socket.getOutputStream().write("发送成功!".getBytes());
        socket.close();
    }
}

启动SocketServer服务端,然后启动SocketClient端发送消息,控制台输出:

发送成功!

一个简单的BIO网络通信模型就建成了,因为accept()方法和read()方法是阻塞的,一旦一个请求进来后不处理完成不会继续接下来的操作,接下来我们通过测试来验证一下

public class SocketServer {

    static byte[] bs = new byte[1024];

    public static void main(String[] args) throws IOException {
        java.net.ServerSocket serverSocket = new java.net.ServerSocket(8888);// 端口为8888
        while (true){
            System.out.println("连接等待中...");
            Socket client = serverSocket.accept();// accept:阻塞
            System.out.println("连接成功...");
            System.out.println("获取数据中...");
            client.getInputStream().read(bs);// read:阻塞
            System.out.println("获取数据成功...");
            System.out.println(new String(bs));
        }
    }
}

通过修改服务器端代码打印log验证,在每次获取连接前后及获取数据前后打印日志,启动服务端

连接等待中...

启动客户端SocketClient输出

连接等待中...
连接成功...
获取数据中...
获取数据成功...
发送成功!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
连接等待中...

接收消息并且打印了步骤,因为我们创建了一个死循环,所以继续连接等待中,这个时候重新创建一个客户端SocketClient1只连接服务端不发送数据

public class SocketClient1 {

    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",8888);
        //只建立连接不发送数据
        Scanner scanner = new Scanner(System.in);
        String next = scanner.next();
        socket.getOutputStream().write(next.getBytes());
        socket.close();
    }
}

启动客户端SocketClient1之后,SocketServer控制台输出

连接等待中...
连接成功...

之后再回过头来启动客户端SocketClient,会发现服务端会一直阻塞在连接成功...
这个时候我们为每一个连接创建一个线程可以吗?

public class SocketServer {

    static byte[] bs = new byte[1024];

    public static void main(String[] args) throws IOException {
        java.net.ServerSocket serverSocket = new java.net.ServerSocket(8888);// 端口为8888
        while (true){
            System.out.println("连接等待中...");
            Socket client = serverSocket.accept();// accept:阻塞
            System.out.println("连接成功...");
            new Thread(()->{
                try {
                    System.out.println("获取数据中...");
                    client.getInputStream().read(bs);// read:阻塞
                    System.out.println("获取数据成功...");
                } catch (IOException e) {
                    e.printStackTrace();
                }
                System.out.println(new String(bs));
            }).start();
        }
    }
}

修改代码,为每一个连接创建一个线程,之后再启动SocketClient1跟SocketClient

连接等待中...
连接成功...
获取数据中...
连接等待中...
连接成功...
获取数据中...
获取数据成功...
发送成功!                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   

通过为每一个连接创建一个线程的方式,我们看起来解决了这个问题,但是在实际的应用中,对服务器性能消耗巨大,实际应用中如果为每一个连接都创建一个线程的话,如果100000个连接,创建100000个线程,不现实。

总结

在BIO中accept()方法会一直阻塞到有连接请求,read()方法会一直阻塞到有数据传输过来,直至一个连接或者一个请求处理完成,为每一个连接都分别创建线程在实际应用落地不可取,资源开销巨大,BIO方式适用于链接数目比较小且比较固定的架构,并且对服务器资源要求高,并发局限于应用中,在JDK1.4之前是唯一选择,但程序直观简单易理解。


0 条评论

还没有人发表评论

发表评论 取消回复

记住我的信息,方便下次评论
有人回复时邮件通知我