本章主要内容
- ChannelHandler,Decoder和Encoder
现在很多地方都可以看到“real-time-web”这个术语。大部分用户在访问网站时希望能实时获取信息。
Netty提供了很多WebSocket相关的支持,包括不同的版本。在你的应用中可以无脑使用这些技术。因为你不需要详细了解那些底层协议相关的知识,只需要使用它提供的简单的API即可。
本章我们将学习开发一个关于WebSocket的例子,来理解如何在实际项目中使用它。你也可以将例子中的一些代码集成到自己的应用中,复用这些代码。
一、挑战
为了说明WebSocket的实时性,这个例子我们将会用WebSocket实现一个WEB版本的即时通讯应用,类似QQ那样。这个应用很类似QQ中的群聊天功能,一个用户发消息,其他用户都能收到。
从上图可以看出,一个用户发了消息,然后服务端转发给其他用户。不过我们主要实现服务器端部分,因为客户端我们会使用浏览器。现在就开始实现这个应用。
二、实现
WebSocket使用HTTP升级机制将HTTP连接转成WebSocket连接。因此WebSocket应用都是先从HTTP开始,然后升级到WebSocket。升级发生的时间是应用指定的。有的是上来就升级,有的是访问了指定的URL之后才升级。
这个例子中,当URL以/ws结尾时才开始升级到WebSocket,否则就返回一个网页给客户端。一旦升级之后就通过WebSocket传输数据。
主要的逻辑都通过ChannelHandler来实现,方便以后复用。下一小节就详细介绍这些ChannelHandler。
2.1、处理HTTP请求
上一小节说过,服务端将同时处理HTTP请求和WebSocket请求,因为服务端也要返回HTML页面,比如聊天室页面,用于客户端展示。
因此我们需要编写一个ChannelInboundHandler,处理客户端的HTTP请求,消息实体使用FullHttpRequest。
- package com.nan.netty.chatroom;
-
- import io.netty.buffer.Unpooled;
- import io.netty.channel.*;
- import io.netty.handler.codec.http.*;
- import io.netty.handler.ssl.SslHandler;
- import io.netty.handler.stream.ChunkedNioFile;
-
- import java.io.RandomAccessFile;
-
- public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
-
- private final String wsUri;
-
- public HttpRequestHandler(String wsUri) {
- this.wsUri = wsUri;
- }
-
- @Override
- public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
- if (wsUri.equalsIgnoreCase(request.uri())) {
- //如果是WebSocket请求,则保留数据并传递到下一个ChannelHandler
- ctx.fireChannelRead(request.retain());
- } else {
- if (HttpUtil.is100ContinueExpected(request)) {
- //收到100-continue,则返回给客户端100
- send100Continue(ctx);
- }
- boolean keepAlive;
- ChannelFuture future;
- try (RandomAccessFile file = new RandomAccessFile(this.getClass().getResource("/").getPath() + "../resources/index.html", "r")) {
- HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK);
- response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
- response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());
- keepAlive = HttpUtil.isKeepAlive(request);
- if (keepAlive) {
- //如果需要keep-alive,则添加相应头信息
- response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
- }
- ctx.write(response);
- if (ctx.pipeline().get(SslHandler.class) == null) {
- //不用加密使用零内存复制发送文件
- future = ctx.writeAndFlush(new DefaultFileRegion(file.getChannel(), 0, file.length()));
- } else {
- future = ctx.writeAndFlush(new ChunkedNioFile(file.getChannel()));
- }
- }
- //如果不是keep-alive,则关闭Channel
- if (!keepAlive) {
- future.addListener(ChannelFutureListener.CLOSE);
- }
- }
-
- }
-
- private static void send100Continue(ChannelHandlerContext ctx) {
- FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
- ctx.writeAndFlush(response);
- }
-
- @Override
- public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
- cause.printStackTrace();
- ctx.close();
- }
- }
上面实现的HttpRequestHandler坐了以下的事情:
- 首先检查请求路径是否是WebSocket,如果是WebSocket则使用retain()方法和fireChannelRead(msg)方法将数据传递下去。
- 如果客户端发送的100,则返回100。
- 如果不是WebSocket请求,则返回一个HttpResponse,返回的内容就是一个HTML文件的内容。
当然,上边这个ChannelHandler只处理了普通HTTP的请求,我们还需要处理WebSocket的请求,然后将收到的数据转发,下一小节就来学习这个知识点。
2.2、处理WebSocket请求
Netty提供了6种不同的WebSocket类型,如下表所示。
|