MINA2收包中对粘包的处理


http://scholers.iteye.com/blog/784336

MINA2中(MINA2 RC版本,MINA2.0正式版已经发布)服务端接受数据默认有一定长度的缓冲区(可以在启动的时候设置)。那么对于大报文,怎么处理呢?比如说超过1024,甚至更多?MINA2为了节省网络流量,提高处理效率,会将大报文自动拆分(可能是存放MINA2中的缓冲区里面):比如2048字节的报文,就会拆分成两次;那么在接受的时候,就有一个如何判断是完整报文的问题,或者说是一个拆包组包的问题。
MINA2中初始化服务的时候是可以设置输入和输出的缓冲区的:

Java代码
  1. acceptor.getSessionConfig().setReadBufferSize(1024);


MINA2提供的案例是,在IoSession中设置一个类似于session,存在在当前IoSession中的全局变量,在此IoSession中有效。

Java代码
  1. privatefinalAttributeKeyTEST=newAttributeKey(getClass(),"TEST");


大家都知道,通过 SOCKET TCP/IP传输过来的报文是不知道边界的,所以一般会约定在前端固定长度的字节加上报文长度,让SERVER来根据这个长度来确定整个报文的边界,在我前面的博文有提到。其实MINA2中有:
prefixedDataAvailable(4) int
方法,来判断固定长度的报文长度,但是参数只能是1,2,4;该方法很好用。判断前四字节的整型值是否大于等于整个缓冲区的数据。可以方便的判断一次 messageReceived 过来的数据是否完整。(前提是自己设计的网络通讯协议前四字节等于发送数据的长度) ,如果你不是设定1,2,4字节来作为长度的话,那么就没辙了。
在你的解码操作中,MINA2的缓冲区发多少次报文,你的decode方法就会调用多少次。
上面设置了session之后,可以采用一个方法:

Java代码
  1. /**
  2. *
  3. *@paramsession
  4. *会话信息
  5. *@return返回session中的累积
  6. */
  7. privateContextgetContext(IoSessionsession){
  8. Contextctx=(Context)session.getAttribute(CONTEXT);
  9. if(ctx==null){
  10. ctx=newContext();
  11. session.setAttribute(CONTEXT,ctx);
  12. }
  13. returnctx;
  14. }


然后在你的decode方法中,首先从session取出数据对象,进行拼接:

Java代码
  1. Contextctx=getContext(session);
  2. //先把当前buffer中的数据追加到Context的buffer当中
  3. ctx.append(ioBuffer);
  4. //把position指向0位置,把limit指向原来的position位置
  5. IoBufferbuf=ctx.getBuffer();
  6. buf.flip();



接着读取每次报文的总长度:

Java代码
  1. //读取消息头部分
  2. byte[]bLeng=newbyte[packHeadLength];
  3. buf.get(bLeng);
  4. intlength=-1;
  5. try{
  6. length=Integer.parseInt(newString(bLeng));
  7. }catch(NumberFormatExceptionex){
  8. ex.printStackTrace();
  9. }
  10. if(length>0){
  11. ctx.setMsgLength(length);
  12. }



在读取到每次报文的长度之后,就接着循环判断BUF里面的字节数据是否已经全部接受完毕了,如果没有接受完毕,那么就不处理;下面是完整处理的代码:

Java代码
  1. while(buf.remaining()>=packHeadLength){
  2. buf.mark();
  3. //设置总长度
  4. if(ctx.getMsgLength()<=0){
  5. //读取消息头部分
  6. byte[]bLeng=newbyte[packHeadLength];
  7. buf.get(bLeng);
  8. intlength=-1;
  9. try{
  10. length=Integer.parseInt(newString(bLeng));
  11. }catch(NumberFormatExceptionex){
  12. ex.printStackTrace();
  13. }
  14. if(length>0){
  15. ctx.setMsgLength(length);
  16. }
  17. }
  18. //读取消息头部分
  19. intlength=ctx.getMsgLength();
  20. //检查读取的包头是否正常,不正常的话清空buffer
  21. if(length<0){//||length>maxPackLength2){
  22. buf.clear();
  23. out.write("ERROR!");
  24. break;
  25. //读取正常的消息包,并写入输出流中,以便IoHandler进行处理
  26. }elseif(length>packHeadLength&&buf.remaining()>=length){
  27. //完整的数据读取之后,就可以开始做你自己想做的操作了
  28. }else{
  29. //如果消息包不完整
  30. //将指针重新移动消息头的起始位置
  31. buf.reset();
  32. break;
  33. }
  34. }
  35. if(buf.hasRemaining()){//如果有剩余的数据,则放入Session中
  36. //将数据移到buffer的最前面
  37. IoBuffertemp=IoBuffer.allocate(2048).setAutoExpand(
  38. true);
  39. temp.put(buf);
  40. temp.flip();
  41. buf.clear();
  42. buf.put(temp);
  43. }else{//如果数据已经处理完毕,进行清空
  44. buf.clear();
  45. }


为了便于操作,最好设置一个内部类:

Java代码
  1. privateclassContext{
  2. privatefinalCharsetDecoderdecoder;
  3. privateIoBufferbuf;
  4. privateintmsgLength=0;
  5. privateintoverflowPosition=0;
  6. /**
  7. *
  8. *
  9. */
  10. privateContext(){
  11. decoder=charset.newDecoder();
  12. buf=IoBuffer.allocate(80).setAutoExpand(true);
  13. }
  14. /**
  15. *
  16. *
  17. *@returnCharsetDecoder
  18. */
  19. publicCharsetDecodergetDecoder(){
  20. returndecoder;
  21. }
  22. /**
  23. *
  24. *
  25. *@returnIoBuffer
  26. */
  27. publicIoBuffergetBuffer(){
  28. returnbuf;
  29. }
  30. /**
  31. *
  32. *
  33. *@returnoverflowPosition
  34. */
  35. publicintgetOverflowPosition(){
  36. returnoverflowPosition;
  37. }
  38. /**
  39. *
  40. *
  41. *@returnmatchCount
  42. */
  43. publicintgetMsgLength(){
  44. returnmsgLength;
  45. }
  46. /**
  47. *
  48. *
  49. *@parammatchCount
  50. *报文长度
  51. */
  52. publicvoidsetMsgLength(intmsgLength){
  53. this.msgLength=msgLength;
  54. }
  55. /**
  56. *
  57. *
  58. */
  59. publicvoidreset(){
  60. this.buf.clear();
  61. this.overflowPosition=0;
  62. this.msgLength=0;
  63. this.decoder.reset();
  64. }
  65. /**
  66. *
  67. *@paramin
  68. *输入流
  69. */
  70. publicvoidappend(IoBufferin){
  71. getBuffer().put(in);
  72. }
  73. }
优质内容筛选与推荐>>
1、node.js从入门到菜鸟——资源无法载入?你需要学会地址解析
2、给TextBox添加水印
3、洛谷P1505 [国家集训队]旅游(树剖+线段树)
4、CSS声明顺序
5、【leetcode】Combination Sum II (middle) ☆


长按二维码向我转账

受苹果公司新规定影响,微信 iOS 版的赞赏功能被关闭,可通过二维码转账支持公众号。

    阅读
    好看
    已推荐到看一看
    你的朋友可以在“发现”-“看一看”看到你认为好看的文章。
    已取消,“好看”想法已同步删除
    已推荐到看一看 和朋友分享想法
    最多200字,当前共 发送

    已发送

    朋友将在看一看看到

    确定
    分享你的想法...
    取消

    分享想法到看一看

    确定
    最多200字,当前共

    发送中

    网络异常,请稍后重试

    微信扫一扫
    关注该公众号





    联系我们

    欢迎来到TinyMind。

    关于TinyMind的内容或商务合作、网站建议,举报不良信息等均可联系我们。

    TinyMind客服邮箱:support@tinymind.net.cn