Netty是一个java的网络应用程序框架和工具。我们做Java web开发过程中,正常的应用使用SpringMVC/Jessey做rest接口,方便前端调用。而有时候,我们又会遇到IM即时通讯这样的需求,像Rest或者Webservice可能就不太适合做服务器端的实现。这时候可以采用Netty框架,方便基于TCP,UDP的服务开发。
下面我们看第一个例子,如何构建一个Netty的服务,需要些4个类。
1:NettyServer
package com.netty.start;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 服务器类.
*/
public class NettyServer {
public void start(int port) throws Exception {
//创建接收者的事件循环组
EventLoopGroup parentGroup = new NioEventLoopGroup();
//创建访问者的事件循环组
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
//创建服务器引导程序
ServerBootstrap b = new ServerBootstrap();
//设置消息循环
b.group(parentGroup, childGroup);
//设置通道
b.channel(NioServerSocketChannel.class);
//配置通道参数:连接队列的连接数
b.option(ChannelOption.SO_BACKLOG, 1024);
//设置客户端请求的处理操作
b.childHandler(new ChildChannelHandler());
//绑定端口,并获取通道io操作的结果
ChannelFuture f = b.bind(port).sync();
//等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
//关闭接收器事件循环
parentGroup.shutdownGracefully();
//关闭访问者的事件循环
childGroup.shutdownGracefully();
}
}
}
2:ChildChannelHandler
package com.netty.start;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.charset.Charset;
/**
* 客户端通道处理类.
*/
public class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
static private Logger logger = LoggerFactory.getLogger(ChildChannelHandler.class);
@Override
protected void initChannel(SocketChannel e) throws Exception {
logger.info(e.remoteAddress() + ":进入通道");
ChannelPipeline pipeline = e.pipeline();
// 以("\n")为结尾分割的 解码器
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()));
// 字符串解码 和 编码
pipeline.addLast("decoder", new StringDecoder(Charset.forName("GBK")));
pipeline.addLast("encoder", new StringEncoder(Charset.forName("UTF-8")));
//添加消息处理
e.pipeline().addLast(new NettyServerHandler());
}
}
3:NettyServerHandler
package com.netty.start;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.SocketAddress;
/**
* 服务器处理类.
*/
public class NettyServerHandler extends ChannelHandlerAdapter {
static private Logger logger = LoggerFactory.getLogger(NettyServerHandler.class);
/**
* 连接通道.
*
* @param ctx
* @param remoteAddress
* @param localAddress
* @param promise
* @throws Exception
*/
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception {
logger.info(ctx.channel().remoteAddress() + ":连接通道");
super.connect(ctx, remoteAddress, localAddress, promise);
}
/**
* 活跃通道.
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
logger.info(ctx.channel().remoteAddress() + ":通道激活");
super.channelActive(ctx);
ctx.writeAndFlush("欢迎访问服务器!!!\r\n");
}
/**
* 非活跃通道.
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
logger.info(ctx.channel().remoteAddress() + ":通道失效");
super.channelInactive(ctx);
}
/**
* 接收消息.
*
* @param ctx
* @param msg
* @throws Exception
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
logger.info(ctx.channel().remoteAddress() + ":" + msg);
}
/**
* 接收完毕.
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
}
/**
* 关闭通道.
*
* @param ctx
* @param promise
* @throws Exception
*/
@Override
public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception {
logger.info(ctx.channel().remoteAddress() + ":关闭通道");
super.close(ctx, promise);
}
/**
* 异常处理.
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.info("异常信息:" + cause.getMessage());
super.exceptionCaught(ctx, cause);
}
}
4:NettyServerTest
package com.netty.start;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class NettyServerTest {
static private Logger logger = LoggerFactory.getLogger(NettyServerTest.class);
@Test
public void test() {
try {
logger.info("启动netty服务端");
new NettyServer().start(3000);
} catch (Exception e) {
e.printStackTrace();
}
}
}
启动NettyServerTest进行单元测试。
在cmd中输入:
telnet 127.0.0.1 3000
然后输入文本内容,服务器控制台会输出请求内容。
注意:如果ChildChannelHandler初始化的时候不添加编码和解码器,那么NettyServerHandler在接收内容的时候需要将数据转成byte,再转String。所以,配置好比较方便。