要分析粘包问题,首先要从数据读开始分析。那么,netty从哪开始读的呢?
-----------------------------------------------------------------------------
messageReceived
java.lang.Exception
at org.jboss.netty.channel.SimpleChannelHandler.stack(SimpleChannelHandler.java:331)
at org.jboss.netty.example.echo.EchoServerHandler.messageReceived(EchoServerHandler.java:47)
at org.jboss.netty.channel.SimpleChannelHandler.handleUpstream(SimpleChannelHandler.java:95)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:564)
at org.jboss.netty.channel.DefaultChannelPipeline.sendUpstream(DefaultChannelPipeline.java:559)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:268)
at org.jboss.netty.channel.Channels.fireMessageReceived(Channels.java:255)
at org.jboss.netty.channel.socket.nio.NioWorker.read(NioWorker.java:97)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.process(AbstractNioWorker.java:108)
at org.jboss.netty.channel.socket.nio.AbstractNioSelector.run(AbstractNioSelector.java:337)
at org.jboss.netty.channel.socket.nio.AbstractNioWorker.run(AbstractNioWorker.java:89)
at org.jboss.netty.channel.socket.nio.NioWorker.run(NioWorker.java:187)
at org.jboss.netty.util.ThreadRenamingRunnable.run(ThreadRenamingRunnable.java:108)
at org.jboss.netty.util.internal.DeadLockProofWorker$1.run(DeadLockProofWorker.java:42)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
at java.lang.Thread.run(Thread.java:662)
以上是Netty读取消息以及触发我们的MessageReceived处理逻辑的整个线程栈。
下面就让我们从NioWorker.read开始分析
=============================================================
什么时候执行read操作?
代码如下:
for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
SelectionKey k = i.next();
i.remove();
try {
int readyOps = k.readyOps();
if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
if (!read(k)) {
// Connection already closed - no need to handle write.
continue;
}
}
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
writeFromSelectorLoop(k);
}
} catch (CancelledKeyException e) {
close(k);
}
if (cleanUpCancelledKeys()) {
break; // break the loop to avoid ConcurrentModificationException
}
}
其实也就是说,当对某个key进行OP_READ检测后,如果确实发生了数据可读事件,就执行读操作。
读操作,又做了哪些事情?
============================================================
代码如下:
@Override
protected boolean read(SelectionKey k) {
/*
if(1==1){
try {
throw new Throwable();
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}*/
final SocketChannel ch = (SocketChannel) k.channel();//获取对应的channel
final NioSocketChannel channel = (NioSocketChannel) k.attachment();//获取对应的NioSocketChannel
final ReceiveBufferSizePredictor predictor =
channel.getConfig().getReceiveBufferSizePredictor();
final int predictedRecvBufSize = predictor.nextReceiveBufferSize();
final ChannelBufferFactory bufferFactory = channel.getConfig().getBufferFactory();
int ret = 0;
int readBytes = 0;
boolean failure = true;
ByteBuffer bb = recvBufferPool.get(predictedRecvBufSize).order(bufferFactory.getDefaultOrder());
//获取临时缓冲区
try {
while ((ret = ch.read(bb)) > 0) {//尽量往里面读数据
readBytes += ret;
if (!bb.hasRemaining()) {
break;
}
}
failure = false;
} catch (ClosedChannelException e) {
// Can happen, and does not need a user attention.
} catch (Throwable t) {
fireExceptionCaught(channel, t);
}
if (readBytes > 0) {//如果读到了数据
bb.flip();//转化成读模式
final ChannelBuffer buffer = bufferFactory.getBuffer(readBytes);
buffer.setBytes(0, bb);//这里是关键的复制操作。
buffer.writerIndex(readBytes);//org.jboss.netty.buffer.BigEndianHeapChannelBuffer
// Update the predictor.
predictor.previousReceiveBufferSize(readBytes);
// Fire the event.
fireMessageReceived(channel, buffer);//收到消息了,就触发消息处理逻辑
}
if (ret < 0 || failure) {
k.cancel(); // Some JDK implementations run into an infinite loop without this.
close(channel, succeededFuture(channel));
return false;
}
return true;
}
org.jboss.netty.buffer.HeapChannelBufferFactory
buffer对应的类是
org.jboss.netty.buffer.BigEndianHeapChannelBuffer
好,不管怎么样,现在获取了消息,怎么触发消息处理机制呢?
---------------------------------------------------代码如下:
public static void fireMessageReceived(Channel channel, Object message, SocketAddress remoteAddress) {
channel.getPipeline().sendUpstream(
new UpstreamMessageEvent(channel, message, remoteAddress));
}
所以,这里,其实交给对应的channel的处理管道pipiline来处理,这里的管道可能有好几个对象,这个由addLast函数添加。
注意2点:1 sendUpstream,这是处理的方向 2 UpstreamMessageEvent这是一个上行事件,只有有能力处理的ctx里的handler才可以处理。
------------------假如我们在自定义逻辑里加入了一个继承了SimpleChannelHandler的类或者SimpleChannelUpstreamHandler的类的对象(通过addLast函数)。
那么,消息就会最终传递到我们这个函数里。
----------------------------------------而这个函数,就是大部分时候,我们自己要增加的逻辑。
其实最基本的问题就是如何获取本次的内容。
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) {
try{
BigEndianHeapChannelBuffer behcb= (BigEndianHeapChannelBuffer)e.getMessage();
System.out.println("\n\n\nreceived: "+new String(behcb.array(),"UTF-8"));
}catch(Exception e11){
}
}
其实根本原因在于,Netty刚开始会把socket里的数据读到一个ByteBuffer直接分配的缓冲区里,比如说1024个字节的容量,
最后读了256个字节,然后这256个数据会复制到BigEndianHeapChannelBuffer 的一个256长度的字节数组里。
所以我们此时通过这个256字节数组,获得了本次所读的256个字节的值,问题就解决了。