前段时间,用NodeJS写了一个翻墙的,然后无聊的时候有写了一个JAVA的事件驱动的客户端,我的Github上都有下载。用的是JAVA net包,当时,为了实现事件驱动,各种线程池,各种折腾,总算实现了。
后来,看别的项目的代码,突现一个叫Netty的东西很早就有了对这些逻辑的封装,我晕菜,知识真实要随时更新啊,不进则退。最近,抽空学习了一下,甚至特意亚马逊上买了电子书看看,虽然作者连Maven 的log都写到书里骗钱了,但是还是值得一读的。抽空他的废话,整理两个例子。因为Netty如果不熟悉,一开始会比较费解,了解之后则用Java实现网路通讯的功能变的简单多了。
首先一个简单的HTTP服务器 简单2个类,一个启动及初始化,一个Handle处理业务
package com.expcorp.demo.netty.httpdemo; import com.expcorp.demo.netty.TimeServerHandler; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.SelfSignedCertificate; public final class HttpHelloWorldServer { static final int PORT = Integer.parseInt(System.getProperty("port", "8080")); public static void main(String[] args) throws Exception { // Configure the server. EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.option(ChannelOption.SO_BACKLOG, 1024); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)).childHandler(new ChannelInitializer<SocketChannel>() { // (4) @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new HttpHelloWorldServerHandler()); } }); Channel ch = b.bind(PORT).sync().channel(); System.err.println("Open your web browser and navigate to http://127.0.0.1:" + PORT + '/'); ch.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
一个Handler处理业务,简单设定个本地目录,有文件则返回文件,没文件返回NOFILE固定页面
package com.expcorp.demo.netty.httpdemo; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.DefaultHttpRequest; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.stream.ChunkedFile; import static io.netty.handler.codec.http.HttpHeaders.Names.*; import static io.netty.handler.codec.http.HttpHeaders.Values; import static io.netty.handler.codec.http.HttpResponseStatus.*; import static io.netty.handler.codec.http.HttpVersion.*; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.logging.Logger; public class HttpHelloWorldServerHandler extends ChannelInboundHandlerAdapter { private static final String ROOT = "C:\\Users\\Demo\\Desktop\\demo-netty\\"; @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { // System.out.print("[INFO] Check msg"); // if (msg instanceof DefaultHttpRequest) { // System.out.println(" DefaultHttpRequest"); // } if (msg instanceof HttpRequest) { HttpRequest req = (HttpRequest) msg; String strResultURL = req.getUri(); System.out.println("URL:" + strResultURL); if (strResultURL.equals("/")) strResultURL = "/index.html"; if (HttpHeaders.is100ContinueExpected(req)) { ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE)); } boolean keepAlive = HttpHeaders.isKeepAlive(req); File file = new File(ROOT + strResultURL); if (file.exists()) { try { final ByteBuf tmpContent = new ChunkedFile(file).readChunk(ctx); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(tmpContent)); // response.headers().set(CONTENT_TYPE, "text/plain"); response.headers().set(CONTENT_TYPE, "text/html"); response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); if (!keepAlive) { ctx.write(response).addListener(ChannelFutureListener.CLOSE); } else { response.headers().set(CONNECTION, Values.KEEP_ALIVE); ctx.write(response); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } else { String data = "<!DOCTYPE html> \n" + "<html>\n" + " <body>No File\n" + " </body>\n" + "</html>"; final ByteBuf tmpContent = Unpooled.copiedBuffer(data.getBytes()); FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(tmpContent)); // response.headers().set(CONTENT_TYPE, "text/plain"); response.headers().set(CONTENT_TYPE, "text/html"); response.headers().set(CONTENT_LENGTH, response.content().readableBytes()); if (!keepAlive) { ctx.write(response).addListener(ChannelFutureListener.CLOSE); } else { response.headers().set(CONNECTION, Values.KEEP_ALIVE); ctx.write(response); } } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
整个Netty核心,我个人理解,就下面两句
ch.pipeline().addLast(new HttpServerCodec()); ch.pipeline().addLast(new HttpHelloWorldServerHandler());
1. XXXCodec主要负责解码,把端口取得的流数据可视化成你想要的,或者方便处理的类,通过这个Codec我们在Handler里就不必面对字节流,而是直接对处理好的数据类了,上文的例子,我们在Handler里面直接可以获得 HttpRequest了。 类似的Codec,Decodec Netty在github上的工程里面提供了很多范例,FTP等等,主流的运用都有。我们也可以自己实现自己想要的Codec,也就是通讯协议
2. 而Handler则是具体的业务流程了,是把数据扔掉,还是转发还是怎么要查数据库格式输出等等,都在这里面实现,你可以直接在这里对流操作,同样应该也可以封装到类,然后通过Decodec返回成流来实现(需要绑定输出outbound)。
暂时到这里,休息一下休息一下