netty是目前最好的nio框架,一般用来做数据通信.为什么用netty?因为nio编程太麻烦,netty做了很好的封装,是操作更简单,下面我写一个简单的netty框架的应用
首先要使用netty必须引入netty的包,pom文件加入如下代码段
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
先看服务端
package com.kevindai.netty;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
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;
public class Server {
private int port;
public Server(int port){
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();//用于接收进来客户端的连接
EventLoopGroup workerGroup = new NioEventLoopGroup();//用于处理已经被接收的连接
try {
ServerBootstrap b = new ServerBootstrap();//启动NIO服务的辅助启动类
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ServerHandler());//以上及以下很多都是模板化的代码,主要要更改的是ServerHandler()的实现
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new Server(port).run();
}
}
然后看看服务端的处理类,从客户端接受的数据如何处理都在这里定义,在实际工作中要做修改的一般也是这个地方
package com.kevindai.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
public class ServerHandler extends ChannelHandlerAdapter{
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception{
//获得接收客户端的数据并进行处理
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String str = new String(data,"UTF-8");
System.out.println("server get:" + str);
//回写给客户端数据
ctx.write(Unpooled.copiedBuffer("msg handled".getBytes()))
.addListener(ChannelFutureListener.CLOSE);//发送完数据之后关闭跟客户端的连接
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
cause.printStackTrace();
ctx.close();
}
}
下面看看客户端如何启动
package com.kevindai.netty;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
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.NioSocketChannel;
public class Client {
public static void main(String[] args) throws Exception {
String host = "localhost";
int port = 8080;
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
b.option(ChannelOption.SO_KEEPALIVE, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ClientHandler());//客户端接受服务端发送的数据进行的处理逻辑
}
});
ChannelFuture f = b.connect(host, port).sync();
f.channel().write(Unpooled.copiedBuffer("test".getBytes()));//发送给服务端数据
f.channel().flush();//发送之后清空缓存
//f.channel().writeAndFlush(Unpooled.copiedBuffer("test".getBytes()));//这行代码和上面两行意思是一样的
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
}
}
}
看看客户端处理数据的逻辑,这里的逻辑都很简单,实际情况实际处理
package com.kevindai.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
public class ClientHandler extends ChannelHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
//接收从服务端发送的数据
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String request = new String(data, "utf-8");
System.out.println("Client get: " + request);
} finally {
ReferenceCountUtil.release(msg);
}
}
// @Override
// public void channelActive(final ChannelHandlerContext ctx) { // (1)
// final ByteBuf time = ctx.alloc().buffer(4); // (2)
// time.writeInt((int) (System.currentTimeMillis() / 1000L + 2208988800L));
// final ChannelFuture f = ctx.writeAndFlush(time); // (3)
// f.addListener(new ChannelFutureListener() {
// @Override
// public void operationComplete(ChannelFuture future) {
// assert f == future;
// ctx.close();
// }
// }); // (4)
// }
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
1、在实际项目中客户端和服务器端都不在一台服务器中,那么如何如何启动呢?第一种办法是让程序集成在web容器中,比如tomcat,在web容器启动时即启动.第二种办法打成Jar包来运行(这两种办法我这里都不做介绍了)
2、这里存在一个tcp 黏包、拆包的问题,一般有三种解决方案
- 消息定长,例如每个报文的大小固定为一定长度,如果不够就补空格;如果过长会被截取(不推荐)
- 在包尾部增加特殊字符进行分割(推荐,一般通过DelimiterBasedFrameDecoder自定义分隔符实现)
- 将消息分为消息头和消息体,在消息头中包含表示消息总长度的字段,然后再进行业务逻辑处理.(类似自定义协议,比较麻烦,我没试过)
用特殊字符分割的应用如下,跟上面代码最大的区别是服务端加了以下两行
客户端发送信息格式如下