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