netty是一个基于nio的socket框架,其调用方式也是异步调用。客户端建立和服务端的链接后,客户端向服务的发送消息,客户端并不等待服务端的的返回,而是在另外一个监听线程中监听服务的的返回。所以客户端的信息发送和信息返回是在两个线程中进行的。
但是在很多时候客户端需要等待服务端的消息,那么如何操作呢,就是使用闭锁等待。具体示例:
先定义一个消息类 NettyMessage
public class NettyMessage implements Serializable {
private static final long serialVersionUID = -8478844905397248186L;
private String id;
private String serviceCode;
private byte[] value;
}
服务端代码:
public class NettyServer {
private static final String IP = "127.0.0.1";
private static final int PORT = 9999;
static final int BIZGROUPSIZE = Runtime.getRuntime().availableProcessors() * 2;
static final int BIZTHREADSIZE = 4;
private static final EventLoopGroup bossGroup = new NioEventLoopGroup(BIZGROUPSIZE);
private static final EventLoopGroup workerGroup = new NioEventLoopGroup(BIZTHREADSIZE);
protected static void run(final NettyServerListener listener) throws Exception {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class);
b.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast(new NettyServerHandler(listener));
}
});
b.bind(IP, PORT).sync();
System.out.println("TCP服务器已启动");
}
protected static void shutdown() {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
System.out.println("开始启动TCP服务器...");
NettyServer.run(new NettyServerListener() {
@Override
public void channelRead(ChannelHandlerContext ctx, NettyMessage message) {
try {
String serviceCode = message.getServiceCode();
//业务分发
switch (serviceCode) {
case "add_user":
break;
case "update_user":
break;
case "print_user":
String userName = new String(message.getValue(), "UTF-8");
System.out.println(userName);
userName = "Hello " + userName + " !";
message.setValue(userName.getBytes("UTF-8"));
//返回处理结果
ctx.channel().writeAndFlush(message).sync();
break;
default:
message.setValue("{error}".getBytes("UTF-8"));
ctx.channel().writeAndFlush(message).sync();
break;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
});
// TcpServer.shutdown();
}
}
其中NettyServerHandler为继承SimpleChannelInboundHandler的监听类:
public class NettyServerHandler extends SimpleChannelInboundHandler<Object> {
private NettyServerListener listener;
public NettyServerHandler(NettyServerListener listener) {
this.listener = listener;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg != null) {
if (msg instanceof NettyMessage) {
listener.channelRead(ctx, (NettyMessage) msg);
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
System.out.println("Unexpected exception from downstream.");
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) {
System.out.println("registered: " + ctx.channel().remoteAddress());
}
}
NettyServerListener为只有一个channelRead的接口:
public interface NettyServerListener {
void channelRead(ChannelHandlerContext ctx, NettyMessage message);
}
客户端代码:
同样定义一个监听接口:
public interface NettyClientListener {
void channelRead(NettyMessage msg);
}
继承SimpleChannelInboundHandler:
public class NettyClientHandler extends SimpleChannelInboundHandler<Object> {
private NettyClientListener listener;
public NettyClientHandler(NettyClientListener listener) {
this.listener = listener;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg != null) {
if (msg instanceof NettyMessage) {
listener.channelRead((NettyMessage) msg);
}
}
}
}
客户端连接发送接收信息部分:
public class NettyClient {
public static String HOST = "127.0.0.1";
public static int PORT = 9999;
public static Channel channel = null;
public static void connect(final NettyClientListener listener) {
try {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast("handler", new NettyClientHandler(listener));
}
});
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
channel = bootstrap.connect(HOST, PORT).sync().channel();
} catch (Exception e) {
e.printStackTrace();
}
}
private static AtomicInteger id = new AtomicInteger(1);
private static ConcurrentHashMap<String, NettyResult> result = new ConcurrentHashMap<String, NettyResult>();
public static void main(String[] args) throws Exception {
connect(new NettyClientListener() {
@Override
public void channelRead(NettyMessage msg) {
NettyResult nettyResult = result.get(msg.getId());
if (nettyResult != null) {
nettyResult.setValue(msg.getValue());
nettyResult.getLatch().countDown();
}
}
});
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
String idStr = String.valueOf(new Integer(id.getAndAdd(1)));
try {
NettyMessage message = new NettyMessage();
message.setId(idStr);
message.setServiceCode("print_user");
message.setValue(("mcyxxx:" + idStr).getBytes("UTF-8"));
NettyResult nresult = new NettyResult();
CountDownLatch latch = new CountDownLatch(1);
nresult.setLatch(latch);
result.put(idStr, nresult);
channel.writeAndFlush(message).sync();
System.out.println("id:" + idStr + " tid:" + Thread.currentThread().getId());
latch.await(10000, TimeUnit.MILLISECONDS);
latch.countDown();
byte[] res = result.get(idStr).getValue();
if (res != null) {
System.out.println(new String(res, "UTF-8") + " tid:" + Thread.currentThread().getId());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
result.remove(idStr);
}
}
});
threads.add(thread);
}
for (Thread thread : threads) {
thread.start();
}
}
}
其中重点是定义了一个CountDownLatch闭锁,在发送了消息之后进行闭锁等待,在监听线程中监听到服务端的消息返回时latchcountDown闭锁等待结束。
但是监听线程是实时在监听消息的,如何知道服务端返回的消息是哪个线程的客户端的呢?这里在NettyMessage中定义了个id属性,每次发送消息时对其赋个唯一序列值,最后通过这个id值确定哪个线程的客户端。这些都是存在一个全局的ConcurrentHashMap中key值为id,最后客户端不管调用成功与否都需要remove掉map里面的key。
这样就简单实现了netty4的同步调用服务端,性能方面未做测试,而且略显繁琐,不知道有没有其它更好的方式。