netty是一个基于nio的通讯框架,如果想要看懂netty那么就先要对nio有一定了解,但这篇文章并不讨论nio。
传统的socket通信的话一个线程只能应对一个客户端,但是nio技术中就可以在一条线程开启多个channel通道应对多个客户端,从而降低服务端的压力。
直接上代码:
public class Server {
private int port;
public Server(int port) {
this.port = port;
}
public void run() throws Exception {
//创建两个EventLoopGroup对象,boss用于接收所有进来的连接然后下发给worker。
// 而worker是用于专门对这个客户端进行服务,有多少个客户端就有多少worker。
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建一个server端引导项进行各项属性的设置。
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class);
b.childHandler(new ServerInitializer());
b.option(ChannelOption.SO_BACKLOG, 128);
b.childOption(ChannelOption.SO_KEEPALIVE, true);
System.out.println("server 启动");
// 同步绑定端口,开始接收进来的连接
ChannelFuture f = b.bind(port).sync();
// 同步等待服务器 socket 关闭 。
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("server关闭了");
}
}
public static void main(String[] args) throws Exception {
new Server(16666).run();
}
}
netty的server端的大体完成了,但还需要一个ChannelInitializer以及SimpleChannelInboundHandler。
public class ServerInitializer extends
ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/**
* 分割器,遇到换行符则进行分隔并对这个1024字节数据进行处理。这可以用于解决黏包问题,当然更高级的黏包解决方案就需要自己开动脑筋了。
* 但如果你的客户端并不发送带有\n换行符,那么就有可能客户端一直发送消息但你就是没能及时处理
* 导致buffer被写满抛出tooLongFrameLength异常。
*/
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()));
/**
* 解码器,如果你的项目是要接收字节数组这种数据的话就不需要StringEncoder,否则它就会将接收到的数据进行默认环境下编码成字符串;
*/
pipeline.addLast("decoder", new StringDecoder());
/**
* 编码器,将你要发送给客户端的数据编码为字符串。
*/
pipeline.addLast("encoder", new StringEncoder());
/**
* handler
*/
pipeline.addLast("handler", new ServerHandler());
System.out.println("客户端:" + ch.remoteAddress() + "连接上");
}
}
public class ServerHandler extends SimpleChannelInboundHandler<String> {
//创建一个全局的channelGroup以方便调用。
public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.writeAndFlush(incoming.remoteAddress() + " 加入\n");
//将连接上的channel添加进Group中去,以便调用
channels.add(ctx.channel());
}
/**
* 客户端close时调用该方法。
*
* @param ctx
* @throws Exception
*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
channels.writeAndFlush(incoming.remoteAddress() + " 离开\n");
}
/**
* 读取内容。
*
* @param ctx
* @param s
* @throws Exception
*/
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
Channel incoming = ctx.channel();
for (Channel channel : channels) {
if (channel != incoming) {
//向客户端写入数据
channel.writeAndFlush("服务端已收到\n");
System.out.println("客户端说:" + s);
} else {
//向客户端写入数据
channel.writeAndFlush("服务端已收到\n");
System.out.println("客户端说:" + s);
}
}
}
/**
* 检测是否在线方法。
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println("客户端:" + incoming.remoteAddress() + "在线");
}
/**
* 客户端掉线时调用该方法
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel incoming = ctx.channel();
System.out.println("客户端:" + incoming.remoteAddress() + "掉线");
}
/**
* 该channel发生异常时调用的方法,
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
Channel incoming = ctx.channel();
System.out.println("客户端:" + incoming.remoteAddress() + "异常");
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
}
}
------------------分割线---------------
接下来的是客户端代码:
public class Client {
public static void main(String[] args) throws Exception{
new Client("localhost", 16666).run();
}
private final String host;
private final int port;
public Client(String host, int port){
this.host = host;
this.port = port;
}
public void run() throws Exception{
EventLoopGroup group = new NioEventLoopGroup();
try {
//创建一个引导项,设置各种属性项
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ClientInitializer());
//同步连接到服务端
Channel channel = bootstrap.connect(host, port).sync().channel();
//读取控制台的信息。
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
while(true){
//channel向服务端发送讯息。
channel.writeAndFlush(in.readLine() + "\r\n");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
public class ClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("framer", new DelimiterBasedFrameDecoder(1024, Delimiters.lineDelimiter()));
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast("handler", new ClientHandler());
}
}
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
System.out.println(s);
}
}
一个Netty的基本通讯demo就完成了。