Java NIO是相对于传统的IO操作而言的,因为提出了缓冲池等概念,使它的处理数据的效率大大提高;
多线程是并发处理的明智选择。
为减少系统开销,线程池是并发应用中是经常使用的技术。
而异步处理机制可以大大缩短每个请求的响应时间。
Mina2中就大量使用了这三项技术,使得它成为优秀的网络应用框架。(这一章并非描述Mina的实际应用,而是对它的内部处理机制做分析;我们对Mina的解析也只对服务端而言:因为无论是Mina也好,NIO也好,多线程也好,异步处理机制也好,都是解决高并发问题的;高并发却是对服务端而言的!因此,服务端才是重点。)
一.NIO分析
Mina是一个Java NIO框架,NIO的基本思想是:服务器程序只需要一个线程就能同时负责接收客户的连接、客户发送的数据,以及向各个客户发送响应数据。服务器程序的处理流程如下:
//阻塞 while(一直等待,直到有接收连接就绪事件、读就绪事件或写就绪事件发生){ if(有客户连接) 接收客户的连接; //非阻塞 if(某个Socket的输入流中有可读数据) 从输入流中读数据; //非阻塞 if(某个Socket的输出流可以写数据) 向输出流写数据; //非阻塞 }
而传统的并发型服务器则是采用多线程的模式响应用户请求的;
//阻塞 while(一直等待){ if(有客户连接) 启动新线程,与客户的通信; //可能会阻塞 }
但是,无论如何,服务端共同的结构如下:
a.Read Request; 接受请求
b.Decode Request; 请求值解码(读)
c.Process Service;请求处理
d.Encode Reply; 响应值编码(写)
e.Send Reply; 发送响应
是不是感觉太抽象了?OK,我们从传统的IO模式的并发服务器说起。
1.传统阻塞服务器
传统的服务端一次只能处理一个请求,其他请求需要排队等待;示例如下:
package com.bijian.study.mina.server; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Date; import org.apache.log4j.Logger; /* * 服务端只能一次处理一个客户端的请求 * 多个请求到达后需要排队 */ public class EchoServer01 { private Logger logger = Logger.getLogger(EchoServer01.class); private int PORT = 3015; private ServerSocket serverSocket; public EchoServer01() throws IOException { // 请求队列最大长度为5 serverSocket = new ServerSocket(PORT,5); logger.info("服务端启动... 端口号:" + PORT); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); logger.info("一个新的连接到达,地址为:" + socket.getInetAddress() + ":" + socket.getPort()); // 获得客户端发送信息的输入流 InputStream socketIn = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader( socketIn)); // 给客户端响应信息的输出流 OutputStream socketOut = socket.getOutputStream(); PrintWriter pw = new PrintWriter(socketOut, true); String msg = null; while ((msg = br.readLine()) != null) { logger.info("服务端接受到的信息为:" + msg); pw.println("响应信息:" + new Date().toString());// 给客户端一个日期字符串 if (msg.equals("bye")) { logger.info("客户端请求断开"); break; } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String args[]) throws IOException { new EchoServer01().service(); } }
代码就不解释啦,直接看注释吧,没有任何玄妙的地方。我们先用telnet做测试。
a.启动服务端
2016-02-18 23:02:22,832 INFO EchoServer01 - 服务端启动... 端口号:3015b.启动,cmd,telnet 127.0.0.1 3015,回车
c.测试,客户端输入
服务端响应:
2016-02-18 23:04:09,984 INFO EchoServer01 - 服务端启动... 端口号:3015 2016-02-18 23:04:24,049 INFO EchoServer01 - 一个新的连接到达,地址为:/127.0.0.1:51937 2016-02-18 23:04:26,907 INFO EchoServer01 - 服务端接受到的信息为:bijian 2016-02-18 23:04:30,463 INFO EchoServer01 - 服务端接受到的信息为:test 2016-02-18 23:04:38,055 INFO EchoServer01 - 服务端接受到的信息为:123 2016-02-18 23:04:39,569 INFO EchoServer01 - 服务端接受到的信息为:sfas 2016-02-18 23:04:43,409 INFO EchoServer01 - 服务端接受到的信息为:bye 2016-02-18 23:04:43,409 INFO EchoServer01 - 客户端请求断开 2016-02-18 23:06:19,021 INFO EchoServer01 - 一个新的连接到达,地址为:/127.0.0.1:51941d.使用客户端代码做测试;EchoClient01.java代码如下:
package com.bijian.study.mina.client; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import org.apache.log4j.Logger; /* * 使用Socket创建客户端请求 */ public class EchoClient01 { private Logger logger = Logger.getLogger(EchoClient01.class); private String HOST = "localhost"; private int PORT = 3015; private Socket socket; public EchoClient01() throws IOException { socket = new Socket(HOST, PORT); } public void talk() throws IOException { try { // 获得服务端响应信息的输入流 InputStream socketIn = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader( socketIn)); // 给服务端发送信息的输出流 OutputStream socketOut = socket.getOutputStream(); PrintWriter pw = new PrintWriter(socketOut, true); BufferedReader localReader = new BufferedReader( new InputStreamReader(System.in)); String msg = null; while ((msg = localReader.readLine()) != null) { pw.println(msg); logger.info(br.readLine()); if (msg.equals("bye")) break; } } catch (IOException e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]) throws IOException { new EchoClient01().talk(); } }先启动服务端,再启动客户端,输入,客户端输出如下:
aaa 2016-02-18 23:30:46,203 INFO EchoClient01 - response info:Thu Feb 18 23:30:46 CST 2016 bbb 2016-02-18 23:30:47,689 INFO EchoClient01 - response info:Thu Feb 18 23:30:47 CST 2016 ccc 2016-02-18 23:30:48,783 INFO EchoClient01 - response info:Thu Feb 18 23:30:48 CST 2016 bye 2016-02-18 23:30:50,361 INFO EchoClient01 - response info:Thu Feb 18 23:30:50 CST 2016 "bijian" Sid: S-1-5-21-3389202862-2257394754-1792823341-1001
2016-02-18 23:30:33,401 INFO EchoServer01 - 服务端启动... 端口号:3015 2016-02-18 23:30:39,045 INFO EchoServer01 - 一个新的连接到达,地址为:/127.0.0.1:52081 2016-02-18 23:30:46,188 INFO EchoServer01 - 服务端接受到的信息为:aaa 2016-02-18 23:30:47,689 INFO EchoServer01 - 服务端接受到的信息为:bbb 2016-02-18 23:30:48,783 INFO EchoServer01 - 服务端接受到的信息为:ccc 2016-02-18 23:30:50,345 INFO EchoServer01 - 服务端接受到的信息为:bye 2016-02-18 23:30:50,361 INFO EchoServer01 - 客户端请求断开测试吧无疑是通过的,但是有两个问题出现了:
注意看,第7个客户端请求是无法成功的,异常信息如下:
Exception in thread "main" java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at java.net.Socket.connect(Socket.java:538) at java.net.Socket.<init>(Socket.java:434) at java.net.Socket.<init>(Socket.java:211) at com.bijian.study.mina.client.EchoClient01.<init>(EchoClient01.java:27) at com.bijian.study.mina.client.EchoClient01.main(EchoClient01.java:60) "bijian" Sid: S-1-5-21-3389202862-2257394754-1792823341-1001异常提示也很明确:拒绝连接!因为我们在服务端建立时做了请求队列最大长度的限制。
public EchoServer01() throws IOException { // 请求队列最大长度为5 serverSocket = new ServerSocket(PORT,5); logger.info("服务端启动... 端口号:" + PORT); }c.服务端容易阻塞;最显著的阻塞是IO操作,比如客户端与服务端建立连接后,向服务端发送一条消息,客户端因为人为操作很久没有输入结束;此时其他的连接只好等待在队列中。
好处就是可以并发处理每一个到达的请求!服务端代码如下:
package com.bijian.study.mina.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import org.apache.log4j.Logger; import com.bijian.study.mina.handler.Server02Handler; /* * 为每个客户端分配一个线程 * 服务器的主线程负责接收客户的连接 * 每次接收到一个客户连接,就会创建一个工作线程,由它负责与客户的通信 */ public class EchoServer02 { private Logger logger = Logger.getLogger(EchoServer02.class); private int PORT = 3015; private ServerSocket serverSocket; public EchoServer02() throws IOException { serverSocket = new ServerSocket(PORT); logger.info("服务器端启动.... 端口号:" + PORT); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); // 请求到达 Thread workThread = new Thread(new Server02Handler(socket)); // 创建线程 workThread.start(); // 启动线程 } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]) throws IOException { new EchoServer02().service(); } }
package com.bijian.study.mina.handler; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.Socket; import java.util.Date; import org.apache.log4j.Logger; public class Server02Handler implements Runnable { private Logger logger = Logger.getLogger(Server02Handler.class); private Socket socket; public Server02Handler(Socket socket) { this.socket = socket; } public void run() { try { logger.info("一个新的请求达到并创建 " + socket.getInetAddress() + ":" + socket.getPort()); InputStream socketIn = socket.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader( socketIn)); OutputStream socketOut = socket.getOutputStream(); PrintWriter pw = new PrintWriter(socketOut, true); String msg = null; while ((msg = br.readLine()) != null) { logger.info("服务端受到的信息为:" + msg); pw.println(new Date()); // 给客户端响应日期字符串 if (msg.equals("bye")) break; } } catch (IOException e) { e.printStackTrace(); } finally { try { if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
启动服务端,使用EchoClient01客户端测试成功!
虽然它没有了传统阻塞服务端的单处理弊端,但是却有一个致命的危险存在:大量请求到达时,不断的创建线程,很容易耗尽系统资源造成服务器崩溃;而且每个线程的创建与销毁都很浪费资源。
解决的办法就是使用线程池!(是不是很熟悉?我们经常接触的数据库连接池就是这样实现的。)
服务端代码如下:
package com.bijian.study.mina.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import org.apache.log4j.Logger; import com.bijian.study.mina.handler.Server02Handler; import com.bijian.study.mina.pool.ThreadPool; /* * 自定义线程池 * 多线程处理客户端请求 */ public class EchoServer03 { private Logger logger = Logger.getLogger(EchoServer03.class); private int PORT = 3015; private ServerSocket serverSocket; private ThreadPool threadPool; // 线程池 private final int POOL_SIZE = 4; // 单个CPU时线程池中的工作线程个数 public EchoServer03() throws IOException { serverSocket = new ServerSocket(PORT); // 创建线程池 // Runtime的availableProcessors()方法返回当前系统的CPU格式 // 系统的CPU越多,线程池中工作线程的数目也越多 threadPool = new ThreadPool(Runtime.getRuntime().availableProcessors() * POOL_SIZE); logger.info("服务端启动.... 端口号:" + PORT); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); // 把与客户通信的任务交给线程池 threadPool.execute(new Server02Handler(socket)); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]) throws IOException { new EchoServer03().service(); } }
服务端没有什么可解释的地方,关键是线程池的实现代码:
package com.bijian.study.mina.pool; import java.util.LinkedList; import org.apache.log4j.Logger; /* * 自定义线程池 */ public class ThreadPool extends ThreadGroup { private Logger logger = Logger.getLogger(ThreadPool.class); private boolean isClosed = false; // 线程池是否关闭 // 将任务放在LinkedList中,LinkedList不支持同步, // 所以在添加任务和获取任务的方法声明中必须使用synchronized关键字 private LinkedList<Runnable> workQueue;// 表示工作队列 private static int threadPoolID; // 表示线程池ID private int threadID; // 表示工作线程ID // 构建一个线程组 public ThreadPool(int poolSize) { // poolSize是指线程池中工作线程的数目 super("ThreadPool-" + (threadPoolID++)); // 线程组名 setDaemon(true); workQueue = new LinkedList<Runnable>();// 创建工作队列 for (int i = 0; i < poolSize; i++) new WorkThread().start(); // 创建并启动工作线程(如果工作队列为空,则所有工作线程处于阻塞状态) } // 向工作队列中添加一个任务,由工作线程去执行该任务 public synchronized void execute(Runnable task) { if (isClosed) { // 线程池关闭则抛出IllegalStateException异常 throw new IllegalStateException(); } if (task != null) { workQueue.add(task); notify(); // 唤醒正在getTask()方法中等待任务的工作线程 } } // 从工作队列中取出一个任务 ----工作线程会调用此方法 protected synchronized Runnable getTask() throws InterruptedException { while (workQueue.size() == 0) { if (isClosed) return null; wait(); // 如果工作队列没有任务,就等待任务 } return workQueue.removeFirst(); } // 关闭线程池 public synchronized void close() { if (!isClosed) { isClosed = true; workQueue.clear(); // 清空工作队列 interrupt();// 中断所有工作线程,该方法继承自ThreadGroup类 } } // 等待工作线程把所有任务执行完 public void join() { synchronized (this) { isClosed = true; notifyAll(); // 唤醒还在getTask()方法中等待任务的工作线程 } // activeCount()方法是ThreadGroup类的,获得线程组中当前所有活着的工作线程数目 Thread[] threads = new Thread[activeCount()]; // enumerate方法继承自ThreadGroup类,获得线程组中当前所有活着的工作线程 int count = enumerate(threads); for (int i = 0; i < count; i++) {// 等待所有工作线程运行结束 try { threads[i].join(); // 等待工作线程运行结束 } catch (InterruptedException ex) { logger.error("工作线程出错...", ex); } } } // 内部类,工作线程 private class WorkThread extends Thread { public WorkThread() { // 加入当前的ThreadPool线程组中 // Thread(ThreadGroup group, String name) super(ThreadPool.this, "WorkThread-" + (threadID++)); } public void run() { // isInterrupted()方法继承自ThreadGroup类,判断线程是否中断 while (!isInterrupted()) { Runnable task = null; try { task = getTask(); // 得到任务 } catch (InterruptedException ex) { logger.error("获得任务异常...", ex); } // 如果getTask()返回null或者线程执行getTask()时被中断,则结束此线程 if (task == null) return; try { // 运行任务,捕获异常 task.run(); // 直接调用task的run方法 } catch (Throwable t) { logger.error("任务执行异常...", t); } }// #while end }// #run end }// # WorkThread class end }
启动服务端,使用EchoClient01客户端测试成功!
很多的服务端程序的实现思想就是基于该理念!
3.使用JDK自带线程池的阻塞服务器
上面那个多线程阻塞服务器使用的是自定义的线程池,但是它的代码可能不是很健壮,在更多的实际开发应用中,我们都是使用JDK自带的线程池的。java.util.concurrent包提供了现成的线程池的实现。
a.Executor接口表示线程池,它的execute(Runnable task)方法用来执行Runnable类型的任务。Executor的子接口
b.ExecutorService中声明了管理线程池的一些方法,比如用于关闭线程池的shutdown()方法等。
c.Executors类中包含一些静态方法,它们负责生成各种类型的线程池ExecutorService实例。
我们现在使用它来实现一个多线程阻塞服务器,服务端代码如下:
package com.bijian.study.mina.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import org.apache.log4j.Logger; import com.bijian.study.mina.handler.Server02Handler; /* * 使用JDK自带的线程池ExecutorService * 多线程处理客户端请求 */ public class EchoServer04 { private Logger logger = Logger.getLogger(EchoServer04.class); private int PORT = 3015; private ServerSocket serverSocket; private ExecutorService executorService; // 线程池 private final int POOL_SIZE = 4; // 单个CPU时线程池中的工作线程个数 public EchoServer04() throws IOException { serverSocket = new ServerSocket(PORT); // 创建线程池 // Runtime的availableProcessors()方法返回当前系统的CPU格式 // 系统的CPU越多,线程池中工作线程的数目也越多 executorService = Executors.newFixedThreadPool(Runtime.getRuntime() .availableProcessors() * POOL_SIZE); logger.info("服务端启动.... 端口号:" + PORT); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); executorService.execute(new Server02Handler(socket)); } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]) throws IOException { new EchoServer04().service(); } }
怎么样,代码很简单吧!启动服务端,使用EchoClient01.java客户端测试成功!
使用线程池时需要遵循以下原则:
(1)如果任务A在执行过程中需要同步等待任务B的执行结果,那么任务A不适合加入到线程池的工作队列中。
(2)如果执行某个任务时可能会阻塞,并且是长时间的阻塞,则应该设定超时时间,避免工作线程永久的阻塞下去而导致线程泄漏。
(3)根据任务的特点,对任务进行分类,然后把不同类型的任务分别加入到不同线程池的工作队列中,这样可以根据任务的特点,分别调整每个线程池。
(4)调整线程池的大小。线程池的最佳大小主要取决于系统的可用CPU的数目以及工作队列中任务的特点。
(5)避免任务过载。
现在基本上可以解决并发处理客户端的问题啦。但是它依然存在不足:
(1)并发量激增的情况下,一台服务器很难应付海量的多并发;这就需要提高服务器并发处理能力和服务器个数;常见的解决方案是集群。
(2)服务器的好坏有两个取决因素:一个是并发能力,一个是响应速度;在并发能力有保障的情况下,每个工作线程,大部分的处理时间都浪费在IO操作上,因为CPU的处理能力比IO快太多,而IO却存在太多的局限因素,造成线程阻塞在IO操作上,大大降低了响应速度;而且会造成资源的浪费,就好比两个同学,一个负责烧水,一个负责挑水,烧水的人一直守在炉子前等待水开,一个却一直挑水;虽然烧水的人可以腾出时间帮助挑水的人,但是他却不能这样做,因为他固定的只能负责一个任务。
对于高并发,我们很有必要提高IO的操作效率,同时也应该改善我们处理每个任务的原则,提高CPU的利用率;Java NIO就是解决方案。
aaa 2016-02-18 23:30:46,203 INFO EchoClient01 - response info:Thu Feb 18 23:30:46 CST 2016 bbb 2016-02-18 23:30:47,689 INFO EchoClient01 - response info:Thu Feb 18 23:30:47 CST 2016 ccc 2016-02-18 23:30:48,783 INFO EchoClient01 - response info:Thu Feb 18 23:30:48 CST 2016 bye 2016-02-18 23:30:50,361 INFO EchoClient01 - response info:Thu Feb 18 23:30:50 CST 2016 "bijian" Sid: S-1-5-21-3389202862-2257394754-1792823341-1001服务端输出如下:
2016-02-18 23:30:33,401 INFO EchoServer01 - 服务端启动... 端口号:3015 2016-02-18 23:30:39,045 INFO EchoServer01 - 一个新的连接到达,地址为:/127.0.0.1:52081 2016-02-18 23:30:46,188 INFO EchoServer01 - 服务端接受到的信息为:aaa 2016-02-18 23:30:47,689 INFO EchoServer01 - 服务端接受到的信息为:bbb 2016-02-18 23:30:48,783 INFO EchoServer01 - 服务端接受到的信息为:ccc 2016-02-18 23:30:50,345 INFO EchoServer01 - 服务端接受到的信息为:bye 2016-02-18 23:30:50,361 INFO EchoServer01 - 客户端请求断开测试吧无疑是通过的,但是有两个问题出现了:
注意看,第7个客户端请求是无法成功的,异常信息如下:
Exception in thread "main" java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at java.net.Socket.connect(Socket.java:538) at java.net.Socket.<init>(Socket.java:434) at java.net.Socket.<init>(Socket.java:211) at com.bijian.study.mina.client.EchoClient01.<init>(EchoClient01.java:27) at com.bijian.study.mina.client.EchoClient01.main(EchoClient01.java:60) "bijian" Sid: S-1-5-21-3389202862-2257394754-1792823341-1001异常提示也很明确:拒绝连接!因为我们在服务端建立时做了请求队列最大长度的限制。
public EchoServer01() throws IOException { // 请求队列最大长度为5 serverSocket = new ServerSocket(PORT,5); logger.info("服务端启动... 端口号:" + PORT); }c.服务端容易阻塞;最显著的阻塞是IO操作,比如客户端与服务端建立连接后,向服务端发送一条消息,客户端因为人为操作很久没有输入结束;此时其他的连接只好等待在队列中。
好处就是可以并发处理每一个到达的请求!服务端代码如下:
package com.bijian.study.mina.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import org.apache.log4j.Logger; import com.bijian.study.mina.handler.Server02Handler; /* * 为每个客户端分配一个线程 * 服务器的主线程负责接收客户的连接 * 每次接收到一个客户连接,就会创建一个工作线程,由它负责与客户的通信 */ public class EchoServer02 { private Logger logger = Logger.getLogger(EchoServer02.class); private int PORT = 3015; private ServerSocket serverSocket; public EchoServer02() throws IOException { serverSocket = new ServerSocket(PORT); logger.info("服务器端启动.... 端口号:" + PORT); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); // 请求到达 Thread workThread = new Thread(new Server02Handler(socket)); // 创建线程 workThread.start(); // 启动线程 } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]) throws IOException { new EchoServer02().service(); } }
注意看,第7个客户端请求是无法成功的,异常信息如下:
Exception in thread "main" java.net.ConnectException: Connection refused: connect at java.net.DualStackPlainSocketImpl.connect0(Native Method) at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79) at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:350) at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206) at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188) at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172) at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392) at java.net.Socket.connect(Socket.java:589) at java.net.Socket.connect(Socket.java:538) at java.net.Socket.<init>(Socket.java:434) at java.net.Socket.<init>(Socket.java:211) at com.bijian.study.mina.client.EchoClient01.<init>(EchoClient01.java:27) at com.bijian.study.mina.client.EchoClient01.main(EchoClient01.java:60) "bijian" Sid: S-1-5-21-3389202862-2257394754-1792823341-1001异常提示也很明确:拒绝连接!因为我们在服务端建立时做了请求队列最大长度的限制。
public EchoServer01() throws IOException { // 请求队列最大长度为5 serverSocket = new ServerSocket(PORT,5); logger.info("服务端启动... 端口号:" + PORT); }c.服务端容易阻塞;最显著的阻塞是IO操作,比如客户端与服务端建立连接后,向服务端发送一条消息,客户端因为人为操作很久没有输入结束;此时其他的连接只好等待在队列中。 这样的服务端我们称之为传统阻塞服务端,大凡Socket的入门示例都是这样的;但是,在实际的生产应用环境中用的非常少(注意:并不是不用哦,在特殊的环境是还是可以使用的,比如手机终端,你接听电话肯定只能一下接受一个请求,其他如短信接收,是要排队等待的)。 2.多线程阻塞服务器 在实际的应用开发中,我们更多采用的是多线程阻塞服务器,即每一个客户端请求到达,就建立一个线程单独的处理它与服务端的通信,如下图所示:
好处就是可以并发处理每一个到达的请求!服务端代码如下:
package com.bijian.study.mina.server; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import org.apache.log4j.Logger; import com.bijian.study.mina.handler.Server02Handler; /* * 为每个客户端分配一个线程 * 服务器的主线程负责接收客户的连接 * 每次接收到一个客户连接,就会创建一个工作线程,由它负责与客户的通信 */ public class EchoServer02 { private Logger logger = Logger.getLogger(EchoServer02.class); private int PORT = 3015; private ServerSocket serverSocket; public EchoServer02() throws IOException { serverSocket = new ServerSocket(PORT); logger.info("服务器端启动.... 端口号:" + PORT); } public void service() { while (true) { Socket socket = null; try { socket = serverSocket.accept(); // 请求到达 Thread workThread = new Thread(new Server02Handler(socket)); // 创建线程 workThread.start(); // 启动线程 } catch (IOException e) { e.printStackTrace(); } } } public static void main(String args[]) throws IOException { new EchoServer02().service(); } }很明显,每到达一个请求,就交给一个线程单独的处理;处理方法如下: