本文参考自:《Netty权威指南》
一、添加依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.42.Final</version>
</dependency>
二、新建一个ServerBootstrap对象,并设置WebSocketServerHandler
public class Server {
public static void run(final int port) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
// 加入http的解码器
ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
// 加入ObjectAggregator解码器,作用是他会把多个消息转换为单一的FullHttpRequest或者FullHttpResponse
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
// 加入http的编码器
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
// 加入chunked 主要作用是支持异步发送的码流(大文件传输),但不专用过多的内存,防止java内存溢出
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
// 加入自定义处理文件服务器的业务逻辑handler
ch.pipeline().addLast("webSocketServerHandler", new WebSocketServerHandler());
}
});
ChannelFuture future = b.bind(port).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
三、新增自定义Handler处理WebSocket连接(继承自SimpleChannelInboundHandler)
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
private WebSocketServerHandshakerFactory factory;
private WebSocketServerHandshaker handshaker;
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object o) throws Exception {
if (null == factory) {
factory = new WebSocketServerHandshakerFactory("ws://localhost:8081/websocket", null, false);
}
//传统的Http接入
if (o instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) o);
} else if (o instanceof WebSocketFrame) {//WebSocket接入
handleWebsocketFrame(ctx, (WebSocketFrame) o);
}
}
/**
* 传统的Http接入
*
* @param ctx
* @param req
*/
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {
//如果Http解码失败,返回Http异常
if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
handshaker = factory.newHandshaker(req);
if (null == handshaker) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), req);
}
}
/**
* WebSocket接入
*
* @param ctx
* @param frame
*/
private void handleWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception{
//判断是否是关闭链路的指令
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
//判断是否是ping消息,是则返回pong消息
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
//判断是否是文本消息
if (frame instanceof TextWebSocketFrame) {
String request = ((TextWebSocketFrame) frame).text();
ctx.channel().write(new TextWebSocketFrame("欢迎使用Netty WebSocket服务:" + request));
return;
}
//判断是否是二进制数据(功能强大,拓展性强)
if (frame instanceof BinaryWebSocketFrame) {
ByteBuf byteBuf = frame.content();
// OutputStream outputStream = new ByteArrayOutputStream();
// byteBuf.readBytes(outputStream, byteBuf.capacity());
}
}
private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
if (200 != res.status().code()) {
ByteBuf byteBuf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(byteBuf);
byteBuf.release();
HttpUtil.setContentLength(res, res.content().readableBytes());
}
//如果是非Keep-Alive,关闭连接
ChannelFuture channelFuture = ctx.channel().writeAndFlush(req);
if (!HttpUtil.isKeepAlive(req) || 200 != res.status().code()) {
channelFuture.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}