Netty入门基础一


Netty入门基础

Netty是什么?

Netty 是一个异步事件驱动的网络应用框架,用于快速开发可维护的高性能服务器和客户端。

Netty针对java nio做了封装和改进

简单实例

socket

  • SocketServerDemo
fun main() {
    val socketDemo = SocketServerDemo()
    socketDemo.start()
}

class SocketServerDemo {
    fun start() {
        var socket = ServerSocket(9178)
        //accept()方法是阻塞式的,直到有请求进来建立连接
        val clientSocket = socket.accept()
        val input = BufferedReader(InputStreamReader(clientSocket.getInputStream()))
        val output = PrintWriter(clientSocket.getOutputStream(), true)
        var request: String?
        var response: String?

        while (true) {
            request = input.readLine()
            if ("done".equals(request)) {
                break
            }
            response = processRequest(request)
            output.println(response)
        }
    }

    private fun processRequest(request: String?): String? {
        return "响应: $request"
    }
}
  • SocketClientDemo

class SocketClientDemo(ip: String, port: Int) {

    private var client: Socket = Socket(ip, port)
    private var output: PrintWriter = PrintWriter(client.getOutputStream(), true)
    private var input: BufferedReader = BufferedReader(InputStreamReader(client.getInputStream()))

    /**
     * 发送消息
     */
    fun sendMessage(msg: String?): String? {
        output.println(msg)
        return input.readLine()
    }

    /**
     * 关闭input流
     */
    open fun stopConnection() {
        input.close()
        output.close()
        client.close()
    }

}

fun main() {
    val socketClient = SocketClientDemo("127.0.0.1", 9178)
    println(socketClient.sendMessage("msg"))
    socketClient.stopConnection()
}

通过SocketClientDemo是客户端向指定ip/port发起请求,SocketServerDemo是服务端接收请求并进行处理,具体的执行过程是

  • SocketServers上的accept()方法会一直阻塞到一个连接的建立,然后在返回一个新的socket用于客户端和服务器之间的通信
  • BufferedReader和PrintWriter都继承与Socket,BufferedReader是从字符输入流中读取文本,PrintWriter是对文本格式化打印到输出流中
  • readLine()方法是一个阻塞方法,跳出阻塞的方法是读取到一个换行符或回车符为止

传统的socket编程有一下几个缺点:

  1. 服务端大量创建线程等待响应
  2. 内存占用问题
  3. 线程上下文的切换问题
    这几个问题其实都是由于一个线程只能处理一个响应导致的,那么有没有一个线程可以处理多个响应的方法嘛?
    答案是有NIO的解决处理方案

原生NIO

NIO原指(New Input/Output)的英文缩写,但是由于这个API也已经出现的很久了不在New,现在也可以指的是非阻塞式(Non-blocking I/O)的IO
NIO的出现是依赖于操作系统底层的I/O多路复用的技术而来,epoll()可以参考《Scalable Event Multiplexing: epoll vs. kqueue》[1]

Netty实例

  • NettyServer
fun main(args: Array<String>) {
    //1. 初始化Bootstrap/boosGroup/workerGroup
    val serverBootstrap = ServerBootstrap()
    val boosGroup = NioEventLoopGroup()
    val workerGroup = NioEventLoopGroup()
    //2.设置前置条件
    serverBootstrap.group(boosGroup, workerGroup)
        .channel(NioServerSocketChannel::class.java)
        .childHandler(object : ChannelInitializer<NioSocketChannel>() {
            override fun initChannel(ch: NioSocketChannel) {
                ch.pipeline().addLast(StringDecoder())
                ch.pipeline().addLast(object : SimpleChannelInboundHandler<String?>() {
                    override fun channelRead0(ctx: ChannelHandlerContext, msg: String?) {
                        println("服务端打印$msg")
                    }
                })
            }
        })
        //3. 绑定端口
        .bind(9178)
}
  • NettyClient
fun main(args: Array<String>) {
    //1. 初始化bootstrap/group
    val bootstrap = Bootstrap()
    val group = NioEventLoopGroup()
    //2. 前置准备阶段
    bootstrap.group(group)
        .channel(NioSocketChannel::class.java)
        .handler(object : ChannelInitializer<Channel>() {
            @Throws(Exception::class)
            override fun initChannel(ch: Channel) {
                ch.pipeline().addLast(StringEncoder())
            }
        })
    //3. 设置Channel的网络连接
    val channel: Channel = bootstrap.connect("127.0.0.1", 9178).channel()
    //4. 发送数据
    while (true) {
        channel.writeAndFlush(LocalDateTime.now().toString() + ": hello world!")
        TimeUnit.SECONDS.sleep(2L)
    }
}

NettyServer的启动流程

  • 初始化Bootstrap/boosGroup/workerGroup
  • 设置前置条件
  • 绑定端口

NettyClient的启动流程

  • 初始化bootstrap/group
  • 前置准备阶段
  • 设置Channel的网络连接信息
  • 发送数据

可以看到在NettyServer/NettyClient中都会有BootstrapNioEventLoopGroup
NettyServer端中NioEventLoopGroup还分为boosworker
NettyServer端中的还会设置ChildHandler的相关内容之后会讲到

Netty组件

Netty的组件主要是分为channel回调future事件和ChannelHandler

  • channel
    channel是数据的载体,可以把数据看做人,channel类比为车辆

  • 回调
    回调指的是Netty的一些网络动作会触发接口ChannelHandler的实现,例如通道建立连接时会触发的channelActive()方法等

  • Future
    Future指的是操作完成后的结果类似于JUC中的future,Netty中也设计了一个顶层接口ChannelFuture

  • 事件和ChannelHandler
    Netty事件是由动作触发的主要有

    • 连接已激活/失效
    • 数据读取
    • 用户事件
    • 错误事件
    • 打开/关闭远程节点连接
    • 将数据写到channel

事件模型

总结

Netty是对NIO的封装和抽象,在操作对象上抽象出Future回调ChannelHandler,在操作行为上抽象出选择器事件EventLoop等概念

参考资料

java socket指南
channelRead与channelReadComplete进行对比


  1. Scalable Event Multiplexing: epoll vs. kqueue ↩︎


  TOC