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可能不是很好的选择。
上一篇: unity异步加载场景