JAVA中BIO

本文详细解读了BIO同步阻塞机制在服务器端的运作,通过案例演示了客户端与服务器的交互过程,并探讨了如何通过多线程处理多客户端请求以避免阻塞。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

1 BIO基本介绍

2 BIO工作机制

 3 同步阻塞案例

 4 BIO模式多发和多收消息


1 BIO基本介绍

        BIO是就是传统的io编程,其相关的类和接口在Java.io

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

2 BIO工作机制

 3 同步阻塞案例

 服务端代码:

public class Server {
    /**
     * 客户端发送消息,服务端接收消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            System.out.println("*****服务端启动******");
            //定义一个ServerSocket对象进行一个服务端的接口注册
            ServerSocket serverSocket = new ServerSocket(8888);
            //监听客户端的Socket链接请求
            Socket socket = serverSocket.accept();
            //从socket对象中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //将字节输入流包装成一个缓冲字符输入流(不能直接将字节输入流包装成缓冲字符输入流,先将字节输入流转成字符输入流,再转成缓冲字符输入流)
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("服务端收到信息:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

 客户端代码:

public class Client {
    public static void main(String[] args) throws IOException {
        //从创建Socket对象请求服务端的连接
        Socket socket = new Socket("127.0.0.1", 8888);
        //从Socket对象中获取一个字节输出流
        OutputStream os = socket.getOutputStream();
        // 将字节输入流包装成一个打印流
        PrintStream ps = new PrintStream(os);
        ps.print("你好");
        ps.flush();


    }
}

 运行结果:

 当服务端启动之后,会进入到一个暂停状态,等待客户端的接入,当客户端启动之后,二者之间进行相互通信,但是服务端等待的是一行数据,而客户端发送的却是一段文字,当客户端将文字发送出去之后,客户端宕机,服务端接收不到一行数据之后继续等待,当发现客户端宕机之后,就会抛出异常。所以这就是同步阻塞机制,当客户端宕机之后,服务端也会挂掉。

客户端改进代码:

public class Client {
    public static void main(String[] args) throws IOException {
        //从创建Socket对象请求服务端的连接
        Socket socket = new Socket("127.0.0.1", 8888);
        //从Socket对象中获取一个字节输出流
        OutputStream os = socket.getOutputStream();
        // 将字节输入流包装成一个打印流
        PrintStream ps = new PrintStream(os);
        ps.println("你好");
        ps.flush();


    }
}

 使用ps.println换行之后打印结果:

 此时,服务端接收到客户端发送的一行数据,但是还是进入了连接重置的状态,这是为什么呢?因为我们的服务端接收到消息之后,我们的while判断依然会继续等待,但我们的客户端只发送一次,发送完之后就挂机了。而服务端监听到客户端挂机,就会抛出异常。我们可对服务端代码进行改进。

改进之后代码块:

public class Server {
    /**
     * 客户端发送消息,服务端接收消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            System.out.println("*****服务端启动******");
            //定义一个ServerSocket对象进行一个服务端的接口注册
            ServerSocket serverSocket = new ServerSocket(8888);
            //监听客户端的Socket链接请求
            Socket socket = serverSocket.accept();
            //从socket对象中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //将字节输入流包装成一个缓冲字符输入流(不能直接将字节输入流包装成缓冲字符输入流,先将字节输入流转成字符输入流,再转成缓冲字符输入流)
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            if ((msg = br.readLine()) != null){
                System.out.println("服务端收到信息:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

输出结果:

 小结:

        1 在以上通信中,服务端会一直等待客户端消息,如果客户端没有进行消息的发送,服务端将一直进入到阻塞状态。

        2 同时服务端是按照行来获取消息的,这意味着客户端也必须按照行进行消息的发送,否则服务端将进入等待消息的阻塞状态。

4 BIO模式多发和多收消息

客户端代码:

public class Client {
    public static void main(String[] args) throws IOException {
        //从创建Socket对象请求服务端的连接
        Socket socket = new Socket("127.0.0.1", 8888);
        //从Socket对象中获取一个字节输出流
        OutputStream os = socket.getOutputStream();
        // 将字节输入流包装成一个打印流
        PrintStream ps = new PrintStream(os);
        Scanner scanner = new Scanner(System.in);
        while (true){
            System.out.println("请说:");
            String msg = scanner.nextLine();
            ps.println(msg);
            ps.flush();
        }


    }
}

服务端代码:

public class Server {
    /**
     * 客户端发送消息,服务端接收消息
     * @param args
     */
    public static void main(String[] args) {
        try {
            System.out.println("*****服务端启动******");
            //定义一个ServerSocket对象进行一个服务端的接口注册
            ServerSocket serverSocket = new ServerSocket(8888);
            //监听客户端的Socket链接请求
            Socket socket = serverSocket.accept();
            //从socket对象中得到一个字节输入流对象
            InputStream is = socket.getInputStream();
            //将字节输入流包装成一个缓冲字符输入流(不能直接将字节输入流包装成缓冲字符输入流,先将字节输入流转成字符输入流,再转成缓冲字符输入流)
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = br.readLine()) != null){
                System.out.println("服务端收到信息:"+msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当我们在客户端的键盘显示器上输入信息时,服务端也会相应的输出

结果:

客户端输入:

 服务端显示:

 但是我们这样写,一个服务端只能接受一个客户端的消息。演示如下:

在idea客户端的启动脚本上选择可以并发执行

 第二个客户端输入:

 第二个客户端输入之后第一个客户端输入:

 服务端结果:

可见:服务端只能接受一个客户端的请求。因为我们服务端目前只有一个线程,那么服务端如何接受多个客户端,那么我们就需要在服务端引入线程,也就是客户端没发起一个请求,服务端就创建一个新的线程来处理这个客户端的请求,这样就实现了一个客户端一个线程的模型。

 5 实现接受多个客户端

客户端代码:

public class Client {

    public static void main(String[] args) {
        try {
            //请求与服务端的socket连接
            Socket socket = new Socket("127.0.0.1", 8888);
            //得到一个打印流
            PrintStream printStream = new PrintStream(socket.getOutputStream());
            Scanner sc = new Scanner(System.in);
            while (true){
                System.out.print("请说:");
                String msg = sc.nextLine();
                printStream.println(msg);
                printStream.flush();

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端代码:

public class Server {

    public static void main(String[] args) {
        try {
            System.out.println("*******服务端启动*********");
            //注册端口
            ServerSocket ss = new ServerSocket(8888);
            //定义一个死循环,负责不断的接收客户端的Socket的连接请求
            while (true){
                Socket socket = ss.accept();
                //创建一个独立的线程来处理这个客户端socket的通信需求
                new ServerThreadReader(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

服务端线程类代码:

public class ServerThreadReader extends Thread{
    private Socket socket;
    public  ServerThreadReader(Socket socket){
        this.socket=socket;
    }


    @Override
    public void run() {

        try {
            //从socket管道中得到一个字节输入流
            InputStream is = socket.getInputStream();
            //使用缓冲字符输入流包装字节输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
            String msg;
            while ((msg = bufferedReader.readLine()) != null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

第一个客户端输入:

第二个客户端输入:

 服务端打印结果:

 由此可见,当你启动第二个客户端的时候,请求socket的时候,服务端会将socket交给一个独立的线程。

总结:

1 每个socket接收到,都会创建一个线程,线程的竞争,切换上下文影响性能。

2 每个线程都会占用栈空间和CPU资源

3 并不是每个socket都进行IO操作,无意义的线程处理;

4 客户端的并发访问量增加时,服务端将呈现1:1的线程开销,访问量越大,系统将发生线程栈溢出;线程创建失败,最终导致进程宕机或僵死,从而不能对外提供服务。

代码将会在之后的博客里继续改进。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值