欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

java BIO

程序员文章站 2022-04-03 12:28:33
...

一、传统的BIO编程

先用BIO实现一个简单功能:

server端:监听,打印客户端发送过来的内容,并将原内容回复给客户端。

客户端:向服务端发送内容,并打印服务端返回的内容。

服务端代码:

 

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
 
public class BioServer {
    private ServerSocket server;
 
    public BioServer(int port) throws IOException {
        server = new ServerSocket(port);
    }
 
    public void listen() throws IOException {
        System.out.println("server started.........................");
        Socket socket = null;
        try {
            while (true) {
                socket = server.accept();
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                PrintWriter out = new PrintWriter(new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())), true);
                while (true) {
                    String text = in.readLine();
                    System.out.println("text from client: " + text);
                    out.println(text);
                    if ("exit".equals(text)) {
                        //out.println("exit");
                        socket.close();
                        break;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            server.close();
        }
    }
 
    public static void main(String[] args) throws IOException {
        BioServer server = new BioServer(9000);
        server.listen();
    }
}

 客户端代码:

 

 

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class BioClient {
    private Socket socket;
 
    public BioClient(String host, int port) throws UnknownHostException, IOException {
        socket = new Socket(host, port);
 
    }
 
    public void send() throws IOException {
        try {
            PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
            out.println("呵呵呵");
            out.println("hello server");
            out.println("哈哈哈");
            out.println("exit");
            out.flush();
            InputStream inputStream=socket.getInputStream();
            BufferedReader in = new BufferedReader(
                        new InputStreamReader(inputStream));
            while (true) {
                String text = in.readLine();
                System.out.println(text);
                if ("exit".equals(text)||"busy".equals(text)) {
                    break;
                }
            }
        } finally {
            socket.close();
        }
    }
 
    public static void main(String[] args) throws UnknownHostException, IOException {
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
 
                @Override
                public void run() {
                    BioClient client;
                    try {
                        client = new BioClient("127.0.0.1", 9000);
                        client.send();
                    } catch (UnknownHostException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
 
            }).start();
        }
 
    }
}

 这种写法是一个服务线程为多个客户端服务。服务端执行了socket = server.accept()后服务端才能与客户端建立连接,否则客户端一直阻塞等待连接建立,server端没有阻塞在server.accept方法时客户端如果请求连接就会报connection refused异常。当服务端接受了一个客户端的连接就执行22-34行之间的代码开始为客户端服务,服务完成后再继续调用socket = server.accept()并接待下一个客户端。缺点很明显,只要服务端还没有处理完上一个客户端的请求,别的客户端的请求就必须要先阻塞在那里等待。就好像是有一个售票口只有一个售票员,而在外面有一堆等着买票的人一样,只有一个人买到票并离开了,售票员才能为下一个人服务。

 

升级一下程序,使每个客户端都有一个服务线程为其服务:

服务端代码:

 

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
 
public class BioServer {
    private ServerSocket server;
 
    public BioServer(int port) throws IOException {
        server = new ServerSocket(port);
    }
 
    public void listen() throws IOException {
        System.out.println("server started.........................");
        Socket socket = null;
        try {
            while (true) {
                socket = server.accept();
                new Thread(new BioServerThread(socket)).start();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            server.close();
        }
    }
     
    public static void main(String[] args) throws IOException {
        BioServer server = new BioServer(9000);
        server.listen();
    }
}

 

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
 
public class BioServerThread implements Runnable {
    private Socket socket;
 
    public BioServerThread(Socket socket) throws IOException {
        this.socket = socket;
    }
 
    @Override
    public void run() {
        try {
            BufferedReader in = new BufferedReader(
              new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())), true);
            while (true) {
                String text = in.readLine();
                System.out.println(Thread.currentThread().getName() + " text from client: " + text);
                out.println(text);
                if ("exit".equals(text)) {
                    //out.println("exit");
                    socket.close();
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 这样一来每一个请求服务端都会单独打开一个线程,但是如果客户端同时请求太多,则会同时打开很多线程,可能会达到系统处理能力的上限从而导致系统崩溃。想象一下为每一个买票的人配一个售票员的场景吧/(ㄒoㄒ)/~~。

 

可以通过线程池的方式来解决为每个客户端分配一个线程的问题,通过调节线程池的大小使得性能和响应之间达到一个比较好的平衡。

重写服务端代码:

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
import snailxr.bio.thread.BioServerThread;
 
public class BioServer {
    private ServerSocket server;
    public BioServer(int port) throws IOException {
        server = new ServerSocket(port);
    }
 
    public void listen() throws IOException {
        BlockingQueue<Runnable> block = new ArrayBlockingQueue<Runnable>(100);
        /**
         * 
         * 1)当池子大小小于corePoolSize就新建线程,并处理请求
         * 2)当池子大小等于corePoolSize,把请求放入workQueue中,池子里的空闲线程就去从workQueue中取任务并处理
         * 3)当workQueue放不下新入的任务时,新建线程入池,并处理请求,
         * 如果池子大小撑到了maximumPoolSize就用RejectedExecutionHandler来做拒绝处理
         */
        ThreadPoolExecutor pool = new ThreadPoolExecutor(1, // corePoolSize
                                                            // 池中保存的线程数,包括空闲线程
                2,// maximumPoolSize池中允许的最大线程数
                30, // 当线程数大于核心时,此为终止前 多余的空闲线程等待新任务的最长时间,单位由下个参数设置。
                TimeUnit.MINUTES, // 时间的单位
                block, new RejectedExecutionHandler() {
 
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        System.out.println(r.getClass().getName());
                        System.out.println("无法继续提供服务....................");
 
                        try {
 
                            BioServerThread bioServer = (BioServerThread) r;
                            Socket socket = bioServer.getSocket();
                            PrintWriter out = new PrintWriter(new BufferedWriter(
                                    new OutputStreamWriter(socket.getOutputStream())), true);
                            InputStream in=socket.getInputStream();
                            out.println("busy");
                            while(true){
                                if(in.read()<0){
                                    socket.close();//让客户端先关闭
                                    break;
                                }
                            }
                        } catch (IOException e) {
                            // TODO Auto-generated catch block
                            e.printStackTrace();
                        }
                    }
                });// 任务队列
        System.out.println("server started.........................");
        Socket socket = null;
        try {
            while (true) {
                socket = server.accept();
                BioServerThread thread = new BioServerThread(socket);
                pool.execute(thread);
                System.out.println(pool.getActiveCount());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            server.close();
            pool.shutdown();
        }
    }
 
    public static void main(String[] args) throws IOException {
        BioServer server = new BioServer(9000);
        server.listen();
    }
}

 通过上面的程序我们可以看到bio的api比较简单,适用于连接数量比较少的架构。即使是使用了多线程去处理建立连接后的操作,但是由于bio程序在read和write时都会阻塞线程,直到有数据可读或可写,对线程资源造成了极大的浪费,所以如果并发要求比较高的话,bio可能不是很好的选择。