Netty简介
Netty是基于Java NIO client-server的网络应用框架,使用Netty可以快速开发网络应用,例如服务器和客户端协议。Netty提供了一种新的方式来开发网络应用程序,这种新的方式使它很容易使用和具有很强的扩展性。Netty的内部实现是很复杂的,但是Netty提供了简单易用的API从网络处理代码中解耦业务逻辑。Netty是完全基于NIO实现的,所以整个Netty都是异步的。
Netty是一个事件驱动框架,什么是事件驱动呢,我的理解就是类似于js中,onclick、onblur之类的,由某些事件触发执行动作的过程,就有点类似于SAX解析,当然SAX解析也是一个事件驱动型框架,事件驱动框架的好处就在于,不会进行多余的消耗,可以精确定位到你想触发的动作,比如服务端向客户端推送消息事件,传统的做法可能是写个长轮询,事实上这个长轮询的大部分请求都是无意义的请求,白白浪费了资源,而且一般的http请求又需要封装请求头,封装握手次数,也是资源和性能上的浪费。如果使用websoket就可以像IM一样只在需要的时候进行及时会话,而且netty在没有事件触发时,线程是处于休眠状态。
我想到的使用websoket的场景,比如扫码登录,一般扫码登录的做法是,将token附带到url上然后生成二维码,扫码请求二维码后,后台记录动作,前端页面轮询后台查看扫码状态。还有一种稍微好些的做法,在页面刷新时就发送一个请求至后台,然后后台将这个请求缓存起来,直至扫码确认后,再将请求结果返回。第一种毫无疑问是效率最低的,而且也会产生延迟,第二种稍微好些,但也是可能产生很多无效的请求。比如期货股票系统,CTP高频行情是1s会产生4次,如果每次都靠浏览器向后台拉取数据,也会产生一定的延迟,经常也会发生这一perk的数据延迟至下一perk,而且用户资金状态(如浮动盈亏)需要前端实时计算,后端又需要计算一次,前端计算也是仅作为展示,实际产生的盈亏还是由后台最终计算生成,前端大量的业务计算,也会造成性能和体验上的不足,如果这些功能都采用websoket进行的话,在数据展示还有用户体验上都可以做的更好。
接下来就是一个使用netty来进行websoket会话的简单demo
1. pom.xml
<netty.version>5.0.0.Alpha2</netty.version>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>${netty.version}</version>
</dependency>
2.Handler
对事件的响应处理
public class Wshandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("@ChannelId : " + ctx.channel().id().asLongText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("@用户下线: " + ctx.channel().id().asLongText());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("@exception ");
ctx.channel().close();
}
@Override
protected void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
Channel channel = ctx.channel();
System.out.println("@" + channel.remoteAddress() + ": " + msg.text());
ctx.channel().writeAndFlush(new TextWebSocketFrame("@来自服务端: " + LocalDateTime.now()));
}
}
3.Init
初始化websoket的参数
public class WsInit extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//HttpServerCodec: 针对http协议进行编解码
pipeline.addLast("httpServerCodec", new HttpServerCodec());
//ChunkedWriteHandler分块写处理,文件过大会将内存撑爆
pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler());
/**
* 作用是将一个Http的消息组装成一个完成的HttpRequest或者HttpResponse,那么具体的是什么
* 取决于是请求还是响应, 该Handler必须放在HttpServerCodec后的后面
*/
pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(8192));
//用于处理websocket, /ws为访问websocket时的uri
pipeline.addLast("webSocketServerProtocolHandler", new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast("wshandler", new Wshandler());
}
}
4.server
启动netty服务
public class WsServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO)).childHandler(new WsInit());
ChannelFuture channelFuture = serverBootstrap.bind(6666).sync();
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
5.html
简单的websoket页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Socket</title>
<script type="text/javascript">
var websocket;
//如果浏览器支持WebSocket
if(window.WebSocket){
websocket = new WebSocket("ws://127.0.0.1:6666/ws"); //获得WebSocket对象
//当有消息过来的时候触发
websocket.onmessage = function(event){
var respMessage = document.getElementById("respMessage");
respMessage.value = respMessage.value + "\n" + event.data;
}
//连接关闭的时候触发
websocket.onclose = function(event){
var respMessage = document.getElementById("respMessage");
respMessage.value = respMessage.value + "\n断开连接";
}
//连接打开的时候触发
websocket.onopen = function(event){
var respMessage = document.getElementById("respMessage");
respMessage.value = "建立连接";
}
}else{
alert("浏览器不支持WebSocket");
}
function sendMsg(msg) { //发送消息
if(window.WebSocket){
setInterval(function(){
websocket.send(msg);
}, 250)
//if(websocket.readyState == WebSocket.OPEN) { //如果WebSocket是打开状态
// websocket.send(msg); //send()发送消息
//}
}else{
return;
}
}
</script>
</head>
<body>
<form οnsubmit="return false">
<textarea style="width: 300px; height: 200px;" name="message"></textarea>
<input type="button" οnclick="sendMsg(this.form.message.value)" value="发送"><br>
<h3>内容</h3>
<textarea style="width: 300px; height: 200px;" id="respMessage"></textarea>
<input type="button" value="清空" οnclick="javascript:document.getElementById('respMessage').value = ''">
</form>
</body>
</html>