这次我们来实现TIME协议。它不同于之前的协议,它会在创建连接之后发送一个32位的整数,然后一旦数据发送成功就马上关闭连接。这篇文章,你将学习如何构造和发送信息,并在发送完成后关闭连接。
因为我们会忽略任何接收到的信息,在连接成功时就发送数据,所以我们这次不会重写channelRead()
方法。而是,重写channelActive()
方法。
package netty.example.time;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class TimeServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception { // (1)
ByteBuf time = ctx.alloc().buffer(4); // (2)
time.writeInt((int)(System.currentTimeMillis() / 1000L + 2208988800L));
ChannelFuture f = ctx.writeAndFlush(time); // (3)
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
assert f == channelFuture;
ctx.close();
}
}); // (4)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
- 这个
channelActive()
方法会在连接被建立并准备好传输数据时触发。接下来就在这个方法中发送一个32位的整数。 - 要发送数据,我们需要先创建一个buffer,来包含这个32位的整数。因此我们需要一个至少可以包含4个字节的
ByteBuf
。首先通过ChannelHandlerContext.alloc()
获取当前的ByteBufAllocator
,然后分配一个buffer。 - 然后,我们可以把构造好的消息发出去…
等一下,怎么没有flip()
呢?在NIO中,我们不是需要在发送信息之前调用java.nio.ByteBuffer.flip()
吗?Netty中没有相关的方法,是因为Netty的ByteBuf实现中包含了两个指针。一个负责读,一个负责写。当向buffer写入数据时,writer指针会增肌,而reader指针不变。reader指针和writer指针分别代表了消息的开始与结束。
相反,NIO的buffer就没有这个明确表示消息开始和结束,所以必须调用flip()
方法。你如果不调用flip()
方法,会导致或者没有数据被发送,或者发送了错误的数据。而很显然Netty就不会了,它会让你的工作变的很轻松。
另外一个要注意的地方是:ChannelHandlerContext.write()
或者writeAndFlush()
会返回一个ChannelFuture
。一个ChannelFuture
代表一个还没有发生的I/O操作。在Netty中调用任何的操作,返回后都有可能没有完成,因为Netty中的所有操作都是异步的。例如下面的代码,关闭操作可能会发生在发送消息之前。
因此,你需要在Channel ch = ...; ch.writeAndFlush(message); ch.close();
write()
方法返回的ChannelFuture
完成之后调用close()
方法,另外,ChannelFuture
完成后会通知它的监听者。同样也要注意,close()
也不会立即完成,也会返回ChannelFuture
。 - 我们怎么获得
write()
完成的通知呢?只要在操作返回的ChannelFuture
中加入ChannelFutureListener
即可。这里,我们创建了一个匿名的ChannelFutureListener
,当写入操作完成后关闭连接。
另外,也可以用简化的listenerf.addListener(ChannelFutureListener.CLOSE);
如果要测试服务程序是否正确,可以在linux终端执行命令rdate -p <host>
。另外服务器需要监听37端口。