一 . WebSocket简介
在HTTP1.0和HTTP1.1协议中,实现服务端主动的发送消息到网页或者APP上,是比较困难的,因为HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快, 尤其是现在IM(即时通信)几乎是很多APP都需要实现的功能,我们往往采用一种轮询的方式让终端去请求服务器获取对应的数据,相信很多做过IM通信的朋友应该深有感触,其实大多数的轮询都是无效的(即没有获得到任何的数据);另外一个方面,每一次轮询都是一个完整的Http请求,而根据Http协议,每一次请求都要在Header中携带大量的参数,这无疑对带宽也是一种极大的消耗。
HTML5的诞生为我们带来的WebSocket,这是一个振奋人心的事情,WebSocket是基于Http协议的一种长连接协议,有了这种协议,我们就可以实现服务端主动往客户端发送消息的功能。有关WebSocket协议的相关信息请读者查询相关的文档,在笔者的博文中不再作过多的赘述。因为笔者最近在学习Netty, 所以今天我们就来说说Netty对WebSocket的支持。
二 . Netty对WebSocket的整合
开发环境: [注意,netty5已经被原著删除所以我们使用4.1版本,使用的同学需要注意]
使用gradle进行版本管理
JDK1.8
IDEA 2018
1. gradle配置
plugins {
id 'java'
}
group 'com.netty'
version '1.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
compile(
"io.netty:netty-all:4.1.5.Final"
)
}
2. 服务端代码
2.1 服务端启动类
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
/**
* 10.netty对webSocket的支持
*
* @author Driss
* @time 2018/9/8 下午12:19
* @email [email protected]
*/
public class MyServer {
public static void main(String args[]) throws Exception {
EventLoopGroup boosGrop = new NioEventLoopGroup();
EventLoopGroup workerGrop = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
//使用服务端初始化自定义类WebSocketChannelInitaializer
serverBootstrap.group(boosGrop, workerGrop).channel(NioServerSocketChannel.class).childHandler(new WebSocketChannelInitaializer());
//使用了不同的端口绑定方式
ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
//关闭连接
channelFuture.channel().closeFuture().sync();
} finally {
//优雅关闭
boosGrop.shutdownGracefully();
workerGrop.shutdownGracefully();
}
}
}
2.2 服务端管道初始化
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 Driss
* @time 2018/9/8 下午12:23
* @email [email protected]
*/
public class WebSocketChannelInitaializer extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//HttpServerCodec: 针对http协议进行编解码
pipeline.addLast(new HttpServerCodec());
//ChunkedWriteHandler分块写处理,文件过大会将内存撑爆
pipeline.addLast(new ChunkedWriteHandler());
/**
* 作用是将一个Http的消息组装成一个完成的HttpRequest或者HttpResponse,那么具体的是什么
* 取决于是请求还是响应, 该Handler必须放在HttpServerCodec后的后面
*/
pipeline.addLast(new HttpObjectAggregator(8192));
//用于处理websocket, /ws为访问websocket时的uri
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//自定义的处理器
pipeline.addLast(new TextWebSocketFrameHandler());
}
}
2.3 自定义的处理类
package com.netty1.fifthexample;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.time.LocalDateTime;
/**
* 针对websocket的自定义处理器
*
* @author Driss
* @time 2018/9/9 下午1:39
* @email [email protected]
*/
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("收到消息: " + msg.text());
//读取收到的信息写回到客户端
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间: " + LocalDateTime.now()));
}
/**
* 连接建立时
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAddred " + ctx.channel().id().asLongText());
}
/**
* 连接关闭时
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved " + ctx.channel().id().asLongText());
}
/**
* 异常发生时
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常发生");
ctx.close();
}
}
3. 页面代码
webSocket
4. 测试 --后台打印效果结果
注意: 页面每次刷子都没去关闭通道重新连接,见下面打印即知
/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/bin/java "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=51098:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_181.jdk/Contents/Home/lib/tools.jar:/Users/weitao/nettyDemo1/out/production/classes:/Users/weitao/.gradle/caches/modules-2/files-2.1/io.netty/netty-all/4.1.5.Final/b5ad7d9b6b866b37277498aec62b098b939e78b6/netty-all-4.1.5.Final.jar com.netty1.fifthexample.MyServer
handlerAddred acde48fffe001122-000005fa-00000001-d3dfac96feaf7f09-724f9e09
handlerRemoved acde48fffe001122-000005fa-00000001-d3dfac96feaf7f09-724f9e09
handlerAddred acde48fffe001122-000005fa-00000002-0b9ff20e16b0be45-edeaa52c
handlerRemoved acde48fffe001122-000005fa-00000002-0b9ff20e16b0be45-edeaa52c
handlerAddred acde48fffe001122-000005fa-00000003-55383c7e62b33bef-b9beeca6
handlerRemoved acde48fffe001122-000005fa-00000003-55383c7e62b33bef-b9beeca6
handlerAddred acde48fffe001122-000005fa-00000004-ea2f27506b7a9016-daaff20c
handlerRemoved acde48fffe001122-000005fa-00000004-ea2f27506b7a9016-daaff20c
handlerAddred acde48fffe001122-000005fa-00000005-e8468d72e37b8faa-b6ab030c
收到消息: 5656
收到消息: 哈哈哈
收到消息: 哈哈哈111
5. 测试--前台效果结果