Server端代码:
public class DiscardServer {
private static final int PORT_NUM = Integer.parseInt(System.getProperty("port", "6767"));
public void run() throws CertificateException, SSLException {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于accept incomming连接请求
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 程序将accepted连接请求注册到workerGroup中,并由里面的线程跟踪处理相关“traffic”
try {
ServerBootstrap sb = new ServerBootstrap(); // “helper class” 2 set up a server
sb.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // 利用 NioServerSocketChannel class 的初始化Server端的Channel实例来accept连接请求
.handler(new LoggingHandler(LogLevel.INFO)) // 设置handler,
.childHandler(new DiscardServerInitializer()); // 设置另外一个handler,childHanlder与handler的不同会在后续博文中描述,Handler在Netty是一个很重要的地位,相当于Servlet中的filter一样,这里用一个ChannelInitializer来统一管理所有handler
// bind and start to accept incoming connections
ChannelFuture cf = sb.bind(PORT_NUM).sync(); // 绑定端口,并利用sync()方法开始监听端口(等待客户端连接请求),若成功,返回ChannelFuture,ChannelFuture也会在后续详细说明
// Wait until the server socket is closed
// 本例中,下面一行代码永远不会发生,但可以用于正常关闭server
// 监听服务关闭监听
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully(); // 相当于是关闭线程池
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws CertificateException, SSLException {
DiscardServer server = new DiscardServer();
server.run();
}
}
Client端代码:
public class DiscardClient {
private static final String REMOET_HOST = System.getProperty("host", "127.0.0.1");
private static final int REMOTE_PORT = Integer.parseInt(System.getProperty("port", "6767"));
public static final int BUFFER_SIZE = Integer.parseInt(System.getProperty("size", "8192"));
public void run() throws SSLException, CertificateException, InterruptedException {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bs = new Bootstrap();
bs.group(group)
.channel(NioSocketChannel.class)
.handler(new DiscardClientInitializer());
// make the connection attempt
ChannelFuture f = bs.connect(REMOET_HOST, REMOTE_PORT).sync();
// Wait until the connection is closed
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
}
}
Initializer(Server与Client的几乎是通用的):
public class DiscardServerInitializer extends ChannelInitializer<SocketChannel> {
private static final boolean SSL = System.getProperty("ssl") != null;
private final SslContext sslCtx;
public DiscardServerInitializer() throws SSLException, CertificateException {
if (SSL) {
// 如果是需要使用SSL加密传输,则初始化并获取一个self-signed certificate 4 testing
SelfSignedCertificate ssc = new SelfSignedCertificate();
// 返回一个openJDK默认的X509临时证书生成文件和密钥文件
sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
} else {
sslCtx = null;
}
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 从SocketChannel中获得管道
ChannelPipeline pipeline = ch.pipeline();
<pre name="code" class="java"> // 都属于ChannelOutboundHandler,按逆序执行
if (sslCtx != null) {
// 安全通信通道
pipeline.addLast(sslCtx.newHandler(ch.alloc())); // 字符串
}
pipeline.addLast("encoder", new StringEncoder());
// 都属于ChannelInboundHandler,顺序执行pipeline.addLast("frame", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));pipeline.addLast("decoder", new StringDecoder()); // 字符串解码// 自定义handlerpipeline.addLast("handler", new DiscardServerHandler());}}
ChannelInitializer的主要目的是为SocketChannel注册Handler,也就是当一个SocketChannel被Server accept 进来以后,进来的数据要分别按注册先后顺序执行哪些handler,出去的数据要分别按逆序的顺序执行哪些handler!这里的注册handler的先后顺序十分重要,ChannelInboundHandler都是按顺序执行的,ChanneloutboundHandler都是按逆序的顺序执行的如下图所示:
同时需要说明一点的是:(下面##之间的说法有误,修正在下面)
###########################################################################################################################
client端和server端的Inbound和Outbound顺序应该刚好相反如下所示:
Server:
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 从SocketChannel中获得管道
ChannelPipeline pipeline = ch.pipeline();
// 都属于ChannelInboundHandler,顺序执行
pipeline.addLast("frame", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder()); // 字符串解码
// 自定义handler
pipeline.addLast("handler", new DiscardServerHandler());
// 都属于ChannelOutboundHandler,按逆序执行
if (sslCtx != null) {
// 安全通信通道
pipeline.addLast(sslCtx.newHandler(ch.alloc())); // 字符串
}
pipeline.addLast("encoder", new StringEncoder());
}
Client:
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 这里要和服务器对应起来
// 否则无法正常解码和编码
ChannelPipeline pipeline = ch.pipeline();
// 都属于ChannelOutboundHandler,按逆序执行
if (sslCtx != null) {
// 安全通信通道
pipeline.addLast(sslCtx.newHandler(ch.alloc())); // 字符串
}
pipeline.addLast("encoder", new StringEncoder());
// 从SocketChannel中获得管道
pipeline.addLast("frame", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// 字符串解码、编码
pipeline.addLast("decoder", new StringDecoder());
// 自定义handler
pipeline.addLast("handler", new DiscardClientHandler());
}
否则程序执行的过程中会出现如下错误:
java.lang.UnsupportedOperationException: unsupported message type: String (expected: ByteBuf, FileRegion)
at io.netty.channel.nio.AbstractNioByteChannel.filterOutboundMessage(AbstractNioByteChannel.java:281)
at io.netty.channel.AbstractChannel$AbstractUnsafe.write(AbstractChannel.java:697)
at io.netty.channel.DefaultChannelPipeline$HeadContext.write(DefaultChannelPipeline.java:1114)
at io.netty.channel.AbstractChannelHandlerContext.invokeWrite(AbstractChannelHandlerContext.java:705)
at io.netty.channel.AbstractChannelHandlerContext.write(AbstractChannelHandlerContext.java:763)
at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:753)
at io.netty.channel.AbstractChannelHandlerContext.writeAndFlush(AbstractChannelHandlerContext.java:788)
at cn.scut.chiu.netty.examples.DiscardClientHandler.generateTraffic(DiscardClientHandler.java:74)
at cn.scut.chiu.netty.examples.DiscardClientHandler.channelActive(DiscardClientHandler.java:58)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:212)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:198)
at io.netty.channel.ChannelInboundHandlerAdapter.channelActive(ChannelInboundHandlerAdapter.java:64)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:212)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:198)
at io.netty.channel.ChannelInboundHandlerAdapter.channelActive(ChannelInboundHandlerAdapter.java:64)
at io.netty.channel.AbstractChannelHandlerContext.invokeChannelActive(AbstractChannelHandlerContext.java:212)
at io.netty.channel.AbstractChannelHandlerContext.fireChannelActive(AbstractChannelHandlerContext.java:198)
at io.netty.channel.DefaultChannelPipeline.fireChannelActive(DefaultChannelPipeline.java:818)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.fulfillConnectPromise(AbstractNioChannel.java:252)
at io.netty.channel.nio.AbstractNioChannel$AbstractNioUnsafe.finishConnect(AbstractNioChannel.java:282)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:528)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:468)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:382)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:354)
at io.netty.util.concurrent.SingleThreadEventExecutor$2.run(SingleThreadEventExecutor.java:111)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:137)
at java.lang.Thread.run(Unknown Source)
###########################################################################################################################
修正:
报错的原因似乎不是因为顺序需要相反,相反,Server和Cilent注册handler时候对应的注册先后顺序应该一样的!!如下所示:
Server和Client都是这样:
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 从SocketChannel中获得管道
ChannelPipeline pipeline = ch.pipeline();
// 都属于ChannelOutboundHandler,按逆序执行
if (sslCtx != null) {
// 安全通信通道
pipeline.addLast(sslCtx.newHandler(ch.alloc())); // 字符串
}
pipeline.addLast("encoder", new StringEncoder());
// 都属于ChannelInboundHandler,按注册先后顺序执行
pipeline.addLast("frame", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
// 字符串解码、编码
pipeline.addLast("decoder", new StringDecoder());
// 自定义handler
pipeline.addLast("handler", new DiscardServerHandler());
}
可以发现的是这里的顺序和之前的说法中Client端的注册顺序是一样的,归结起来题主假设造成之前错误的原因应该是,ChannelOutboundHandler在这种Client主动连接Server的情况下,要先注册ChannelOutboundHandler,再注册ChannelInboundHandler。