栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【Socket学习】2. BIO(阻塞IO)

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【Socket学习】2. BIO(阻塞IO)

【Socket学习】2. BIO(阻塞IO)
普通的IO之前看了,就没写博客儿了,那都不是这个重点  
这次重点写的是BIO(Blocking IO)
2.1 BIO的一系列操作
    应用程序向操作系统请求网络IO操作,这时应用程序就会进入阻塞状态,开始等待。操作系统收到请求后,也会等待,直到网络上有数据传到监听端口;操作系统在收到数据后,会把数据发送给应用程序;最后应用程序收到数据就会解除阻塞状态。
2.2 一些重要概念

阻塞IO 和 非阻塞IO
这两个词是用于描述应用程序的。前者阻塞IO也就是2.1里边说的应用程序发出请求就进入阻塞状态,得到响应后结束等待;而后者发出请求后继续执行(并且使用线程一直轮询,直到有IO资源准备好了)同步IO 和 非同步IO
这两个词是用于描述操作系统的。主要描述的是操作系统在收到应用程序请求的IO操作后,如果IO资源没有准备好,该如何处理。前者不响应应用程序的请求,直到IO资源准备好后;后者返回一个标记(好让程序和自己知道以后的数据往哪里通知),当IO资源准备好以后,再用事件机制返回给程序(这个事件机制是啥我还不知道) 2.2 BIO深入分析

在上一篇博客我们实现的TCP和UDP通信也是使用的BIO的通信方式,现在来分析一下BIO的通信方式.

传统的BIO的问题

同一时间,服务器只能接受来自于客户端A的请求信息;虽然客户端A和客户端B的请求是同时进行的,但客户端B发送的请求信息只能等到服务器接受完A的请求数据后,才能被接受由于服务器一次只能处理一个客户端请求,当处理完成并返回(或者异常时),才能进行第二次请求的处理。这样的处理方式izai高并发的情况下,是不能采用的

多线程方式 - 伪异步方式:

当服务器收到客户端X的请求后,(读取到所有请求数据后)将这个请求送入一个独立线程进行处理,然后主线程继续接受客户端Y的请求。客户端一侧,也可以使用一个子线程和服务器端进行通信。这样客户端主线程的其他工作就不受影响了,当服务器端有响应信息的时候再由这个子线程通过 监听模式/观察模式(等其他设计模式)通知主线程。

BIO的问题关键**不在于是否使用了多线程去处理请求,而是在于accept()、read()的这些操作都是被阻塞的**。
模拟二十个客户端并发请求,服务器单线程
客户端守护线程(SocketClientDaemon.java)
package testBSocket;

import java.util.concurrent.CountDownLatch;


public class SocketClientDaemon {
    public static void main(String[] args) throws InterruptedException {
        Integer clientNumber = 20;
        CountDownLatch countDownLatch = new CountDownLatch(clientNumber);

        //  分别开启这20个客户端
        for (int index = 0; index < clientNumber; index ++, countDownLatch.countDown()) {
            SocketClientRequestThread client = new SocketClientRequestThread(countDownLatch, index);
            new Thread(client).start();

        }
      
        //  保证守护线程启动所有的线程后,进入等待状态
        synchronized (SocketClientDaemon.class) {
            SocketClientDaemon.class.wait();
        }
    }
}
客户端模拟请求线程(SocketClientRequestThread.java)
package testBSocket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;


public class SocketClientRequestThread implements Runnable {
    
    private CountDownLatch countDownLatch;
    
    private Integer clientIndex;

    public SocketClientRequestThread() {
    }

    public SocketClientRequestThread(CountDownLatch countDownLatch, Integer clientIndex) {
        this.countDownLatch = countDownLatch;
        this.clientIndex = clientIndex;
    }

    @Override
    public void run() {
        Socket socket = null;
        OutputStream clientRequest = null;
        InputStream clientResponse = null;

        try {
            socket = new Socket("localhost", 88);
            clientRequest = socket.getOutputStream();
            clientResponse = socket.getInputStream();
            //  等待,直到SocketClientDaemon将所有线程启动完毕后,然后再让所有线程一起发送请求,模拟并发
            this.countDownLatch.await();
            clientRequest.write(("这是 " + this.clientIndex + " 号客户端发送的请求").getBytes());
            clientRequest.flush();
            System.out.println(this.clientIndex + " 号客户端请求发送完成,等待服务器返回信息");

            int maxLen = 1024;
            byte[] contextBytes = new byte[maxLen];
            int realLen;
            StringBuilder message = new StringBuilder("");
            while ((realLen = clientResponse.read(contextBytes, 0, maxLen)) != -1) {
                message.append(new String(contextBytes, 0, realLen));
            }
            System.out.println("接收到来自服务器的消息: " + message);
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        } finally {
            try {
                if (clientRequest != null) {
                    clientRequest.close();
                }
                if (clientResponse != null) {
                    clientResponse.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
单线程服务器(SocketServer1.java)
package testBSocket;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


public class SocketServer1 {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(88);
        try {
            while (true) {
                Socket socket = serverSocket.accept();
                InputStream in = socket.getInputStream();
                OutputStream out = socket.getOutputStream();
                Integer sourcePort = socket.getPort();
                int maxLen = 2048;
                byte[] contextBytes = new byte[maxLen];
                int realLen = in.read(contextBytes, 0, maxLen);
                String message = new String(contextBytes, 0, realLen);
                System.out.println("服务器收到来自于端口" + sourcePort + "的消息: " + message);
                //  发送信息
                out.write("回发响应消息".getBytes());
                out.close();
                in.close();
                socket.close();
            }
        } finally {
            if (serverSocket != null) {
                serverSocket.close();
            }
        }
    }
}

对服务器端DEBUG走一边就会发现这实际上服务器端还是一个一个接受信息,在一调用accept()方法或read()方法便会阻塞。

多线程优化服务器端

客户端代码不变

多线程服务器端(SocketServer2.java)
package testBSocket;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;


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

        try {
            while(true) {
                Socket socket = serverSocket.accept();
                //当然业务处理过程可以交给一个线程(这里可以使用线程池),并且线程的创建是很耗资源的。
                //最终改变不了.accept()只能一个一个接受socket的情况,并且被阻塞的情况
                SocketServerThread socketServerThread = new SocketServerThread(socket);
                new Thread(socketServerThread).start();
            }
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            if(serverSocket != null) {
                serverSocket.close();
            }
        }
    }
}


class SocketServerThread implements Runnable {
    private Socket socket;

    public SocketServerThread (Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        InputStream in = null;
        OutputStream out = null;
        try {
            //下面我们收取信息
            in = socket.getInputStream();
            out = socket.getOutputStream();
            Integer sourcePort = socket.getPort();
            int maxLen = 1024;
            byte[] contextBytes = new byte[maxLen];
            //使用线程,同样无法解决read方法的阻塞问题,
            //也就是说read方法处同样会被阻塞,直到操作系统有数据准备好
            int realLen = in.read(contextBytes, 0, maxLen);
            //读取信息
            String message = new String(contextBytes , 0 , realLen);

            //下面打印信息
            System.out.println("服务器收到来自于端口: " + sourcePort + "的信息: " + message);

            //下面开始发送信息
            out.write("回发响应信息!".getBytes());
        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            //试图关闭
            try {
                if(in != null) {
                    in.close();
                }
                if(out != null) {
                    out.close();
                }
                if(this.socket != null) {
                    this.socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

尽管服务器使用多线程实现了去处理这些请求(伪异步),但还是会在accept()、read()方法上被阻塞。

这就是异步IO模式,就是为了解决这样的并发性存在的

这是accept方法的源码

    
    public Socket accept() throws IOException {
        if (isClosed())
            throw new SocketException("Socket is closed");
        if (!isBound())
            throw new SocketException("Socket is not bound yet");
        Socket s = new Socket((SocketImpl) null);
        implAccept(s);
        return s;
    }

这个方法的描述里写着

Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.

监听向这个socket建立的连接并且接受socket连接。这个方法会阻塞直到连接建立完成

这涉及到了阻塞式同步IO的工作原理

服务器线程发起一个accept动作,询问操作系统是否有新的socket套件则信息从端口XXX发过来。

注意是操作系统。

这说明socket套接字的IO模式支持是基于操作系统的,所以同步IP/异步IO的支持就是需要操作系统级别的了

如果操作系统发现没有套接字从指定的端口XX来,那么操作系统就会等待。这样serverSocket.accept()方法就会一直等待。这就是为甚么accept()方法会阻塞,他的内部实现是使用的操作系统的同步IO

参考文章:

https://www.pdai.tech/md/java/io/java-io-bio.html

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/709017.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号