前段时间,用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)。

暂时到这里,休息一下休息一下

最后修改日期: 2018年1月17日

作者