前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Netty 之传统 BIO、NIO 编程

Netty 之传统 BIO、NIO 编程

作者头像
Yano_nankai
发布2018-10-08 10:36:17
3670
发布2018-10-08 10:36:17
举报
文章被收录于专栏:二进制文集二进制文集

说明

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

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

传统 BIO 编程是什么样的?

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

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

BIO 通信模型

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

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

示例

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

TimeServer 源码

代码语言:javascript
复制
public class TimeServer {

    public static void main(String[] args) throws IOException {
        int port = 8080;
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(port);
            System.out.println("The time server is start in port : " + port);
            Socket socket = null;
            while (true) {
                socket = serverSocket.accept();
                new Thread(new TimeServerHandler(socket)).start();
            }
        } finally {
            if (serverSocket != null) {
                System.out.println("The time server close");
                serverSocket.close();
                serverSocket = null;
            }
        }
    }
}
代码语言:javascript
复制
public class TimeServerHandler implements Runnable {

    private Socket socket;

    public TimeServerHandler(Socket socket) {
        this.socket = socket;
    }

    @Override
    public 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 currentTime = null;
            String body = null;
            while (true) {
                body = in.readLine();
                if (body == null) {
                    break;
                }
                System.out.println("The time server receive order : " + body);
                currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date().toString() : "BAD ORDER";
                out.println(currentTime);
            }
        } catch (Exception e) {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
            if (out != null) {
                out.close();
                out = null;
            }
            if (this.socket != null) {
                try {
                    this.socket.close();
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
                this.socket = null;
            }
        }
    }
}

TimeClient 源码

代码语言:javascript
复制
public class TimeClient {
    public static void main(String[] args) throws Exception {
        int port = 8080;
        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            socket = new Socket("127.0.0.1", port);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            out.println("QUERY TIME ORDER");
            System.out.println("SEND TO SERVER SUCCESSED");
            String resp = in.readLine();
            System.out.println("Now is : " + resp);
        } catch (Exception e) {
            // TODO: handle exception
        } finally {
            if (out != null) {
                out.close();
                out = null;
            }
            if (in != null) {
                in.close();
                in = null;
            }
            if (socket != null) {
                socket.close();
                socket = null;
            }
        }
    }
}

输出结果

代码语言:javascript
复制
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命令,查看当前堆栈信息:

代码语言:javascript
复制
"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开发。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2017.09.02 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 说明
  • 传统 BIO 编程是什么样的?
    • BIO 通信模型
    • 示例
      • TimeServer 源码
        • TimeClient 源码
          • 输出结果
            • 线程堆栈查看
            • 伪异步 I/O 编程
            • NIO 编程
              • NIO 类库简介
                • 缓冲区 Buffer
                • 通道 Channel
                • 多路复用器 Selector
            相关产品与服务
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档