文章目录
一 netty介绍
Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。
也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
“快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
优点:
(1) Netty提供了简单易用的API
(2) 基于事件驱动的编程方式来编写网络通信程序
(3) 更高的吞吐量
(4) 学习难度低
应用场景:
JavaEE: Dubbo
大数据:Apache Storm(Supervisor worker进程间的通信也是基于Netty来实现的)
二 BIO、NIO、AIO介绍与区别
阻塞与非阻塞
主要指的是访问IO的线程是否会阻塞(或者说是等待)
线程访问资源,该资源是否准备就绪的一种处理方式
同步和异步
主要是指的数据的请求方式
同步和异步是指访问数据的一种机制
BIO
同步阻塞IO,Block IO,IO操作时会阻塞线程,并发处理能力低。
我们熟知的Socket编程就是BIO,一个socket连接一个处理线程(这个线程负责这个Socket连接的一系列数据传输操作)。阻塞的原因在于:操作系统允许的线程数量是有限的,多个socket申请与服务端建立连接时,服务端不能提供相应数量的处理线程,没有分配到处理线程的连接就会阻塞等待或被拒绝。
NIO
同步非阻塞IO,None-Block IO
NIO是对BIO的改进,基于Reactor模型。我们知道,一个socket连接只有在特点时候才会发生数据传输IO操作,大部分时间这个“数据通道”是空闲的,但还是占用着线程。NIO作出的改进就是“一个请求一个线程”,在连接到服务端的众多socket中,只有需要进行IO操作的才能获取服务端的处理线程进行IO。这样就不会因为线程不够用而限制了socket的接入。
AIO(NIO 2.0)
异步非阻塞IO
这种IO模型是由操作系统先完成了客户端请求处理再通知服务器去启动线程进行处理。AIO也称NIO2.0,在JDK7开始支持。
从上厕所浅谈三种IO
BIO :上厕所 厕所满了就一直站在那里等待
NIO:如果厕所满了就玩会手机聊聊天 做其他的事情 不会等待
AIO:如果厕所有坑 就主动发送一个消息通知你来上厕所
三 Netty Reactor模型 - 单线程模型、多线程模型、主从多线程模型介绍
1 单线程模型
用户发起IO请求到Reactor线程
Ractor线程将用户的IO请求放入到通道,然后再进行后续处理
处理完成后,Reactor线程重新获得控制权,继续其他客户端的处理
针对每一个请求建立一个管道来进行通信
这种模型一个时间点只有一个任务在执行,这个任务执行完了,再去执行下一个任务。
- 但单线程的Reactor模型每一个用户事件都在一个线程中执行:
- 性能有极限,不能处理成百上千的事件
- 当负荷达到一定程度时,性能将会下降
- 某一个事件处理器发生故障,不能继续处理其他事件
2 Reactor多线程模型
Reactor多线程模型是由一组NIO线程来处理IO操作(之前是单个线程),所以在请求处理上会比上一中模型效率更高,可以处理更多的客户端请求。
这种模式使用多个线程执行多个任务,任务可以同时执行
但是如果并发仍然很大,Reactor仍然无法处理大量的客户端请求
3 Reactor主从多线程模型
这种线程模型是Netty推荐使用的线程模型
这种模型适用于高并发场景,一组线程池接收请求,一组线程池处理IO。
从大保健浅谈netty Reactor三种模型
单线程模型:来了很多客人 她既是招待员又是技师
多线程模式:招待员一个 Reactor线程池有很多技师 专门来大保健
主从多线程:Reactor线程池有很多专门的招待员 批量接入客人 然后另一个Reactor线程池很多技师 批量服务这些客人
四 基于web socket简单聊天DEMO实现
导入依赖
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
</dependencies>
编写netty server
package com.xiepanpan.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* @author: xiepanpan
* @Date: 2020/8/1 0001
* @Description:
*/
public class WebSocketNettyServer {
public static void main(String[] args) {
//创建两个线程池
//主线程池
NioEventLoopGroup mainGroup = new NioEventLoopGroup();
//从线程池
NioEventLoopGroup subGroup = new NioEventLoopGroup();
try {
//创建netty服务器启动对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
//初始化服务器启动对象
serverBootstrap
.group(mainGroup,subGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WebSocketChannelInitializer());
//绑定服务器端口 以同步的方式启动服务器
ChannelFuture future = serverBootstrap.bind(9090).sync();
//等待服务器关闭
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//关闭服务器
mainGroup.shutdownGracefully();
mainGroup.shutdownGracefully();
}
}
}
编写通道初始化器
package com.xiepanpan.netty;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* @author: xiepanpan
* @Date: 2020/8/1 0001
* @Description: 通道初始化器 用来加载通道处理器(ChannelHandler)
*/
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
/**
* 初始化通道
* 在这个方法中加载对应的ChannelHandler
* @param socketChannel
* @throws Exception
*/
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//添加一个http的编解码器
pipeline.addLast(new HttpServerCodec());
//添加一个用于支持大数据流的支持
pipeline.addLast(new ChunkedWriteHandler());
//添加一个聚合器 这个聚合器主要是将HTTPMessage聚合成FullHTTPRequest
pipeline.addLast(new HttpObjectAggregator(1024*64));
//需要指定接收请求的路由
//必须使用以ws后缀结尾的url才能访问
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//添加自定义的Handler
pipeline.addLast(new ChatHandler());
}
}
自定义聊天处理器
package com.xiepanpan.netty;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.EventExecutorGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author: xiepanpan
* @Date: 2020/8/1 0001
* @Description: 自定义聊天处理器 TextWebSocketFrame: 在netty中,是用于为websocket专门处理文本的对象,frame是消息的载体
*
*/
public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
//用来保存所有的客户端连接
private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-mm-dd hh:MM");
/**
* 当Channel中有新的事件消息会自动调用
* @param channelHandlerContext
* @param textWebSocketFrame
* @throws Exception
*/
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
//获取客户端发送过来的文本消息
String text = textWebSocketFrame.text();
System.out.println("接收到消息数据:"+text);
for (Channel channel: channels) {
channel.writeAndFlush(new TextWebSocketFrame(sdf.format(new Date())+""+text));
}
}
//当有新的客户端连接服务器后 会自动调用这个方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//将新的通道加入到channels中
channels.add(ctx.channel());
}
}
前端chat.html
WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。Websocket是应用层第七层上的一个应用层协议,它必须依赖 HTTP 协议进行一次握手,握手成功后,数据就直接从 TCP 通道传输,与 HTTP 无关了。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在线聊天室</title>
</head>
<body>
<input type="text" id="message" >
<input type="button" value="发送消息" onclick="sendMsg()">
接收到的消息:
<p id="server_message" style="background-color: chartreuse"></p>
<script>
var webSocket = null;
if (window.WebSocket) {
webSocket = new WebSocket("ws://127.0.0.1:9090/ws");
webSocket.onopen = function () {
console.log("建立连接");
}
webSocket.onclose = function () {
console.log("断开连接");
}
webSocket.onmessage = function (e) {
console.log("接收到服务器消息:"+e.data);
var server_message =document.getElementById("server_message");
server_message.innerHTML += e.data + "<br/>";
}
}
else {
alert("当前浏览器不支持web socket")
}
function sendMsg() {
var message = document.getElementById("message");
webSocket.send(message.value);
}
</script>
</body>
</html>
效果:
点关注不迷路:
生吃个人 我很抱歉 陌生人 看到这里就点个赞吧。