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编程有一下几个缺点:
- 服务端大量创建线程等待响应
- 内存占用问题
- 线程上下文的切换问题
这几个问题其实都是由于一个线程只能处理一个响应导致的,那么有没有一个线程可以处理多个响应的方法嘛?
答案是有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中都会有Bootstrap和NioEventLoopGroup
NettyServer端中NioEventLoopGroup还分为boos和worker
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进行对比