java BIO

一、传统的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可能不是很好的选择。

 

 

猜你喜欢

转载自snailxr.iteye.com/blog/2263158