【网络编程】(四)BIO传统版、多线程版、线程池版对比

来源:互联网 发布:linux cat 多个文件 编辑:程序博客网 时间:2024/05/24 07:40
BIO:JDK1.4以前我们使用都是BIO   阻塞IO

阻塞到我们的读写方法 , 阻塞到线程来提供性能.对于线程的开销本来就是性能的浪费.


举例一[传统版]

Server.java

public class Server {@SuppressWarnings("resource")public static void main(String[] args) throws Exception {//创建socket服务,监听10101端口ServerSocket server=new ServerSocket(10101);System.out.println("服务器启动!");while(true){//获取一个套接字(阻塞)final Socket socket = server.accept();System.out.println("来个一个新客户端!");//业务处理handler(socket);}}/** * 读取数据 */public static void handler(Socket socket){try {byte[] bytes = new byte[1024];InputStream inputStream = socket.getInputStream();while(true){//读取数据(阻塞)int read = inputStream.read(bytes);if(read != -1){System.out.println(new String(bytes, 0, read));}else{break;}}} catch (Exception e) {e.printStackTrace();}finally{try {System.out.println("socket关闭");socket.close();} catch (IOException e) {e.printStackTrace();}}}}

说明:单线程情况下只能有一个客户端!

阻塞点
server.accept();
inputStream.read(bytes);



举例二[多线程版]

每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接。


Server.java

import java.io.IOException;import java.net.ServerSocket;import java.net.Socket;public class Server {final static int PROT = 8765;public static void main(String[] args) {ServerSocket server = null;try {server = new ServerSocket(PROT);System.out.println(" server start .. ");//进行阻塞Socket socket = server.accept();//新建一个线程执行客户端的任务new Thread(new ServerHandler(socket)).start();} catch (Exception e) {e.printStackTrace();} finally {if(server != null){try {server.close();} catch (IOException e) {e.printStackTrace();}}server = null;}}


ServerHandler.java

package bhz.bio;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class ServerHandler implements Runnable{private Socket socket ;public ServerHandler(Socket socket){this.socket = socket;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));out = new PrintWriter(this.socket.getOutputStream(), true);String body = null;while(true){body = in.readLine();if(body == null) break;System.out.println("Server :" + body);out.println("服务器端回送响的应数据.");}} catch (Exception e) {e.printStackTrace();} finally {if(in != null){try {in.close();} catch (IOException e) {e.printStackTrace();}}if(out != null){try {out.close();} catch (Exception e) {e.printStackTrace();}}if(socket != null){try {socket.close();} catch (IOException e) {e.printStackTrace();}}socket = null;}}}


Client.java

package bhz.bio;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class Client {final static String ADDRESS = "127.0.0.1";final static int PORT = 8765;public static void main(String[] args) {Socket socket = null;BufferedReader in = null;PrintWriter out = null;try {socket = new Socket(ADDRESS, PORT);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);//向服务器端发送数据out.println("接收到客户端的请求数据...");String response = in.readLine();System.out.println("Client: " + response);} catch (Exception e) {e.printStackTrace();} finally {if(in != null){try {in.close();} catch (IOException e) {e.printStackTrace();}}if(out != null){try {out.close();} catch (Exception e) {e.printStackTrace();}}if(socket != null){try {socket.close();} catch (IOException e) {e.printStackTrace();}}socket = null;}}}



问题:每当有一个新的客户端请求接入时,服务端必须创建一个新的线程处理新接入的客户端链路,一个线程只能处理一个客户端连接。在高性能服务器应用领域,往往需要面向成千上万个客户端的并发连接,这种模型显然无法满足高性能、高并发接入的场景。系统会发生线程堆栈溢出、创建新线程失败等问题,并最终导致进程宕机或者僵死,不能对外提供服务。


举例三[线程池版]

采用线程池和任务队列可以实现一种叫做伪异步的IO通信框架


为了改进一线程一连接模型,后来又演进出了一种通过线程池和消息队列实现N个线程处理M个客户端的模型。当有新的客户端接入的时候,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable接口)投递到后端的线程池中进行处理,JDK的线程池维护一个消息队列和N个活跃线程对消息队列中的任务进行处理。通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽。由于它的底层通信机制依然使用同步阻塞IO,所以被称为 “伪异步”。


Server.java

package bhz.bio2;import java.io.BufferedReader;import java.io.PrintWriter;import java.net.ServerSocket;import java.net.Socket;public class Server {final static int PORT = 8765;public static void main(String[] args) {ServerSocket server = null;BufferedReader in = null;PrintWriter out = null;try {server = new ServerSocket(PORT);System.out.println("server start");Socket socket = null;HandlerExecutorPool executorPool = new HandlerExecutorPool(50, 1000);while(true){socket = server.accept();executorPool.execute(new ServerHandler(socket));}} catch (Exception e) {e.printStackTrace();} finally {if(in != null){try {in.close();} catch (Exception e1) {e1.printStackTrace();}}if(out != null){try {out.close();} catch (Exception e2) {e2.printStackTrace();}}if(server != null){try {server.close();} catch (Exception e3) {e3.printStackTrace();}}server = null;}}}


ServerHandler.java

package bhz.bio2;import java.io.BufferedReader;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;public class ServerHandler implements Runnable {private Socket socket;public ServerHandler (Socket socket){this.socket = socket;}@Overridepublic void run() {BufferedReader in = null;PrintWriter out = null;try {in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));out = new PrintWriter(this.socket.getOutputStream(), true);String body = null;while(true){body = in.readLine();if(body == null) break;System.out.println("Server:" + body);out.println("Server response");}} catch (Exception e) {e.printStackTrace();} finally {if(in != null){try {in.close();} catch (Exception e1) {e1.printStackTrace();}}if(out != null){try {out.close();} catch (Exception e2) {e2.printStackTrace();}}if(socket != null){try {socket.close();} catch (Exception e3) {e3.printStackTrace();}}socket = null;}}}


HandlerExecutorPool.java

package bhz.bio2;import java.util.concurrent.ArrayBlockingQueue;import java.util.concurrent.ExecutorService;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class HandlerExecutorPool {private ExecutorService executor;public HandlerExecutorPool(int maxPoolSize, int queueSize){        //maxPoolSize线程池中最大的线程数//如果线程在120s之内是空闲的,则线程回收    //queueSize    //举例:有150个客户端向服务器发起socket连接,但是笔记本只支持8个线程。剩下的142个socket中,有queueSize(100)个放在等待队列中,剩下的42个小于maxPoolSize(50),所以新建42个线程。this.executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(),maxPoolSize, 120L, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(queueSize));}public void execute(Runnable task){this.executor.execute(task);}}


Client.java

package bhz.bio2;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.PrintWriter;import java.net.Socket;import java.net.UnknownHostException;public class Client {final static String ADDRESS = "127.0.0.1";final static int PORT =8765;public static void main(String[] args) {Socket socket = null;BufferedReader in = null;PrintWriter out = null;try {socket = new Socket(ADDRESS, PORT);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));out = new PrintWriter(socket.getOutputStream(), true);out.println("Client request");String response = in.readLine();System.out.println("Client:" + response);}  catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {if(in != null){try {in.close();} catch (Exception e1) {e1.printStackTrace();}}if(out != null){try {out.close();} catch (Exception e2) {e2.printStackTrace();}}if(socket != null){try {socket.close();} catch (Exception e3) {e3.printStackTrace();}}socket = null;}}}

总结

传统版:单线程模型下,只能有一个客户端。

多线程版:一线程一连接模型。为每个请求都创建一个独立线程造成的线程资源耗尽问题。无法满足高性能、高并发接入的场景。

线程池版:伪异步的IO通信框架,采用线程池+任务队列实现。但是由于它底层的通信依然采用同步阻塞模型,因此无法从根本上解决问题。


参考文章:

《《Netty 权威指南》—— 传统的BIO编程》
《《Netty 权威指南》—— 伪异步IO编程》

阅读全文
0 0