[关闭]
@Yano 2017-08-19T09:52:55.000000Z 字数 3634 阅读 1772

Netty 之传统 BIO、NIO 编程

Netty


说明

系列文章:http://www.jianshu.com/p/594441fb9c9e

本文完全参考自《Netty权威指南(第2版)》,李林峰著。

传统 BIO 编程是什么样的?

基本的网络编程模型是Client/Server,即两个进程间相互通信,其中服务端提供位置信息(IP地址和端口号),客户端通过连接向服务器监听的地址发起连接请求,通过三次握手建立连接,之后双方就可以通过网络套接字(socket)进行通信。

Java的传统同步阻塞模型中,ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接。之后双方通过输入和输出流进行同步阻塞式通信。

BIO 通信模型

对于每个客户端,服务端都要新建一个线程。

当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数量呈1:1的关系,当线程数膨胀后,系统的性能将急剧下降。

示例

服务端在接收到字符串QUERY TIME ORDER后,返回当前日期给客户端。

TimeServer 源码

  1. public class TimeServer {
  2. public static void main(String[] args) throws IOException {
  3. int port = 8080;
  4. ServerSocket serverSocket = null;
  5. try {
  6. serverSocket = new ServerSocket(port);
  7. System.out.println("The time server is start in port : " + port);
  8. Socket socket = null;
  9. while (true) {
  10. socket = serverSocket.accept();
  11. new Thread(new TimeServerHandler(socket)).start();
  12. }
  13. } finally {
  14. if (serverSocket != null) {
  15. System.out.println("The time server close");
  16. serverSocket.close();
  17. serverSocket = null;
  18. }
  19. }
  20. }
  21. }
  1. public class TimeServerHandler implements Runnable {
  2. private Socket socket;
  3. public TimeServerHandler(Socket socket) {
  4. this.socket = socket;
  5. }
  6. @Override
  7. public void run() {
  8. BufferedReader in = null;
  9. PrintWriter out = null;
  10. try {
  11. in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
  12. out = new PrintWriter(this.socket.getOutputStream(), true);
  13. String currentTime = null;
  14. String body = null;
  15. while (true) {
  16. body = in.readLine();
  17. if (body == null) {
  18. break;
  19. }
  20. System.out.println("The time server receive order : " + body);
  21. currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date().toString() : "BAD ORDER";
  22. out.println(currentTime);
  23. }
  24. } catch (Exception e) {
  25. if (in != null) {
  26. try {
  27. in.close();
  28. } catch (IOException e1) {
  29. e1.printStackTrace();
  30. }
  31. }
  32. if (out != null) {
  33. out.close();
  34. out = null;
  35. }
  36. if (this.socket != null) {
  37. try {
  38. this.socket.close();
  39. } catch (IOException e1) {
  40. e1.printStackTrace();
  41. }
  42. this.socket = null;
  43. }
  44. }
  45. }
  46. }

TimeClient 源码

  1. public class TimeClient {
  2. public static void main(String[] args) throws Exception {
  3. int port = 8080;
  4. Socket socket = null;
  5. BufferedReader in = null;
  6. PrintWriter out = null;
  7. try {
  8. socket = new Socket("127.0.0.1", port);
  9. in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
  10. out = new PrintWriter(socket.getOutputStream(), true);
  11. out.println("QUERY TIME ORDER");
  12. System.out.println("SEND TO SERVER SUCCESSED");
  13. String resp = in.readLine();
  14. System.out.println("Now is : " + resp);
  15. } catch (Exception e) {
  16. // TODO: handle exception
  17. } finally {
  18. if (out != null) {
  19. out.close();
  20. out = null;
  21. }
  22. if (in != null) {
  23. in.close();
  24. in = null;
  25. }
  26. if (socket != null) {
  27. socket.close();
  28. socket = null;
  29. }
  30. }
  31. }
  32. }

输出结果

The time server is start in port : 8080
The time server receive order : QUERY TIME ORDER
The time server receive order : QUERY TIME ORDER

线程堆栈查看

通过jstack命令,查看当前堆栈信息:

"main" #1 prio=5 os_prio=31 tid=0x00007ffed1805000 nid=0x1c03 runnable [0x0000700007de7000]
   java.lang.Thread.State: RUNNABLE
    at java.net.PlainSocketImpl.socketAccept(Native Method)
    at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
    at java.net.ServerSocket.implAccept(ServerSocket.java:545)
    at java.net.ServerSocket.accept(ServerSocket.java:513)
    at bio.TimeServer.main(TimeServer.java:18)

我们可以看到,代码确实阻塞在accept操作上。

伪异步 I/O 编程

同步阻塞I/O当海量并发接入时,会导致线程耗尽。可以对线程模型进行优化:后端通过一个线程池来处理多个客户端的请求接入。

但是伪异步I/O也存在很多问题,因为它的本质仍然是同步阻塞。这意味着当对方发送请求或者应答消息比较缓慢,或者网络传输较慢时,读取输入流一方的通信线程会被长时间阻塞;在此期间,其他接入消息只能在消息队列中排队。

NIO 编程

首先问自己一个问题,什么才是NIO编程?对于NIO,官方的说法是:New I/O;但是更多的人喜欢称之为Non-block I/O(非阻塞I/O)。

BIO中的Socket类和ServerSocket类,对应于NIO中的SocketChannel和ServerSocketChannel两种不同的套接字通道实现,这两种套接字都支持阻塞和非阻塞两种模式。

NIO 类库简介

缓冲区 Buffer

在面向流的I/O中,可以直接读取或写入数据至Stream对象中;在NIO中,所有数据都是通过缓冲区处理的:读取数据时,直接读到缓冲区;写入数据时,写入到缓冲区。任何时候访问NIO中的数据,都是通过缓冲区进行的。

通道 Channel

传统的只有一个方向(InputStream或者OutputStream),而通道可以用于读、写或二者同时进行。

多路复用器 Selector

提供选择已经就绪的任务的能力。Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读写事件,这个Channel处于就绪状态,会被Selector轮询处理,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。


对于NIO的编程十分繁琐,就不作介绍,在接下来的文章中直接使用Netty开发。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注