各位大虾,本人是初学者,请各位帮帮忙。项目要用到TCP通讯,多个客户端给服务器端发送数据,利用内部通讯协议,使用nio能保证没有丢包发生吗?还有,我将接收到的数据存放到ByteBuffer中,不同的客户端数据会不会连续存放呢?部分代码如下:
//如果sk对应的通道有数据需要读取
if (sk.isReadable())
{
// 获取该SelectionKey对应的Channel,该Channel中有可读的数据
SocketChannel sc = (SocketChannel) sk.channel();

// 定义准备执行读取数据的ByteBuffer
ByteBuffer buff = ByteBuffer.allocate(1024);
String content = "";
// 开始读取数据
try
{
while (sc.read(buff) > 0)
{
buff.flip();
byte[] b = new byte[buff.limit()];

buff.get(b);
content += UserFunctions.byteArray2HexString(b,b.length);
}
if(!map.containsKey(content.substring(14, 24)))
map.put(content.substring(14, 24),getSocketChannel());

// 打印从该sk对应的Channel里读取到的数据
System.out.println(content);
// 将sk对应的Channel设置成准备下一次读取
sk.interestOps(SelectionKey.OP_READ);
}
// 如果捕捉到该sk对应的Channel出现了异常,即表明该Channel
// 对应的Client出现了问题,所以从Selector中取消sk的注册
catch (IOException ex)
{
// 从Selector中删除指定的SelectionKey
sk.cancel();
if (sk.channel() != null)
{
sk.channel().close();
}
}
}//if(sk.isReadable())

解决方案 »

  1.   

    TCP保证不丢包> 不同的客户端数据会不会连续存放呢
    不太明白你说的连续存放是啥意思?  不同客户端存放的同一块内存的意思?
    不同客户端,socket也不同,各自allocate了,就不会“连续存放”
      

  2.   

    1.tcp本身有一定容错性。但是你自己的协议必须也有必要的容错机制。比如,使用应答机制。
    2. ByteBuffer 在使用时你只要作为局部对象使用是不可能出现同步问题的。也就是你说出现不同客户端的数据被写到同一个ByteBuffer对象中。(除非你自己刻意这么做)
    3.比较明显的是你的代码在一个seletor.select()的大循环中主要是看整体有没有问题。至少在你的这段代码中整体结构正确的。(每次都ByteBuffer buff = ByteBuffer.allocate(1024); )
    4.其实catch (IOException ex)后应该尽量多判断情况,不一定close。还有就是有时底层tcp已经断掉,这时read()动作可能会返回<0但是没有exception(我遇到过 还有write也是)
    5.sk.interestOps(SelectionKey.OP_READ);这动作好像没必要吧
      

  3.   

    ...接上面我最近也在研究这块内容...可以看看我的blog。研学了MINA的代码所以自己仿照作了一个类似的框架。哈哈。
      

  4.   

    从你上在这两个回复看来你是白研究了。
    ByteBuffer只需每个线程分配一个,就可以反复使用,因为它只是作为读写缓冲而不是用户数据存储。没有必要为每个SelectKey分配一个ByteBuffer,如果是读事件在多个线程中处理的话,应该为每个线程分配一个ByteBuffer而不能跨线程共享。但同一线程中处理完第一个sk,只需复位ByteBuffer就可以为下一个sk使用,为什么要花很大我开销为每个sk分配一个ByteBuffer?sk.interestOps(SelectionKey.OP_READ);这动作好像没必要吧
    你怎么知道一次Select就能把对方发送的数据读完?如果当好前没有读完难要阻塞在这里等待?
      

  5.   

    楼上是存心找吵架的 哈哈作技术的难免这种人。。我只是针对LZ问题回答,具体如何处理当然要去研究ByteBuffer的内部机制。你非要说LZ是错的那也没办法。。(从性能角度出发每次都分配一个ByteBuffer当然不够优化)但是对于每个线程只用一个buffer的方式我也可以说你“都白研究了”更好的方式是整个体系中只有一个大的bytebuffer封装体,然后每次使用都从这个封装体中去取然后手工释放也就有些人说的“memcache”。
    你怎么知道一次Select就能把对方发送的数据读完?如果当好前没有读完难要阻塞在这里等待?
    怎么说法呢?
    1.我只看了这部分代码没有出现“sk.interestOps( sk.interestOps() & ~SelectionKey.OP_READ)”这样的行为
    2.去仔细研究一下selectorKey.interestOps()以及selectorKey.readOps()的机制底层socket没有数据的时候会如何看看基本行为就知道不知道你的所谓阻塞从何说起。interestOps() OP_READ才会出现不断readOps()...最后,讨论问题就讨论问题“都白研究了”这种话先对自己说省得人家说你“先学做人后学技术”
      

  6.   

    反驳你的胡说八道就叫找碴?让你在这里不懂装懂?费话少说,以技术见高低。你上面的回复更说明你对NIO和ByteBuffer根本不理解(我没说一无所知,因为你只是了解这个名词)。更好的方式是整个体系中只有一个大的bytebuffer封装体,然后每次使用都从这个封装体中去取然后手工释放也就有些人说的“memcache”。你说这句话的时候已经说明连入门级还没到。多个线程中不同的KEY读取的数据在同一个bytebuffer中交叉存放,然后如果分配给每个具体的处理程序?“手工释放”这种搞笑的话也说得出来?如何手工?用手去操作内存?除了因为理屈词穷而想出这种混乱思维,你说的有些人在哪里见到这种使用方式?对sk.interestOps(SelectionKey.OP_READ);的两点你说明什么?说明你去看了底层的操作?底层如何操作了?只说明你根本没有能力看懂sk.interestOps(SelectionKey.OP_READ);的作用。对于非阻塞IO的读操作(写相反),一次select只是通知载协议栈的读操作有数据可读,至于这次读到的数据是多少,是否是一次完整的交互数据,selector并不关心。如果客户端在发送了1024字节的数据,这次只读到512字节,那么余下的512只能等下次select,除非是阻塞方式可以等到一直读完。那么到底是重新注册READ事件还是sk.interestOps(SelectionKey.OP_READ);,如果重新注册当然可以读到下面的数据,但是你就无法和上次的那部份数据合并,因为多个KEY的不完整数据同时存在,你不知道要合并到哪个现有上去,所以用sk.interestOps(SelectionKey.OP_READ);的意思其实就是用同一个KEY重新注册,下次读到的余下的数据合并到上次这个KEY的部分数据上,代表同一客户端的一次完整的发送。
    对于同一次交互中比较大的数据,必须使用sk.interestOps(SelectionKey.OP_READ);来多次读取。无论你出于什么原因只要你知道他的作用就不可能说它不必要。就象阻塞方法中我们while((len = in.read(buf)) > 0){....};如果你说while不必要的话,那只能说明你根本不懂IO操作。理论上即使对方发两个字节的数据,一次select也可能只读到第一次字节,除非在第一次select时,已经读到的数据中包含中约定好的结束标记或已经知道的长度。而楼主的代码只是将读到的数据打印出来,既没有判断是否已经读到结束标记,也没判断已经读到的数据长度是否达到多少而不需再读,那么sk.interestOps(SelectionKey.OP_READ);来读下次数据怎么会不必要?只要你懂这段代码的意思就不可能说出这样的话。说出这样的话只能代表你不懂。
      

  7.   

    回楼上的:没想到你这都能也能长篇大论。。说你说话有问题么现在非说人家不懂装懂那你先问问自己懂不懂1。bytebuffer一定根SelectionKey有关系么?我所谓bytebuffer的封装体并不是我虚构出来的。。你自己去读一下NIOServer这个国人写的开源案例源代码就知道了。 ByteBuffer无非是一个美化过的byte数组,我去封装一下。然后是在多线呈中使用不行么,非要在单线呈中用?“手工释放”这种口头语你也挑骨头么我也没办法了c++没学过么? c++对对象分配内存与java有啥不同?如果当面你能这么个态度更别人说话,你能活到现在我就服了你,只能在网上嚣张嚣张吧。 2。你说的意思写过NIO的都明白,但是你遗漏一个很关键因素k.interestOps(SelectionKey.OP_READ)后如果不去取消 SelectionKey.OP_READ,每次底层有数据需要读(注意这个更你每次读多少无关)都会使发select()的释放block,然后的到的key.isReadable()为真也就是readyOP的OP_READ为真。根本不用重复k.interestOps(SelectionKey.OP_READ)去这注册关心的OP_READ。
    你长篇大论那么多无非是在说读取不一定能完全读完底层OS级缓冲中的数据。但是你要知道,不用重复注册就能不断得到readyop(OP_READ)为真这个道理。你不信可以去写个最最简单的例子尝试一下,底层没有数据的时候会怎么样,然后一旦有数据后需不需要重复interestOps()动作你不做测试也没关系看看MINA核心部分代码再说话如果你非要这样攻击别人么,我奉陪你的水平也就这种程度而已,不懂装懂的程度。
      

  8.   

    晕死,你是真的不懂k.interestOps(SelectionKey.OP_READ)的作用。
    当客户端和服务端进行通讯时,因为是非阻塞的,所以在正在进行READ操作时,用户也可能提交WRITE操作。
    如果一个KEY同时注册了READ,WRITE操作。那么正在READ时,如果协议栈的写缓冲准备好。要不要触发WRITEABLE?那么当前的写操作会不会被CONTENT SWITCH?而如果我知道我当前的任务是埋头苦读,我就不允许当前KEY触发操作,所以k.interestOps(SelectionKey.OP_READ)把这个KEY的操作完全置为READ,我一门心思读完。
    没有这个操作当然可以读到数据,但能说明k.interestOps(SelectionKey.OP_READ)没有必要吗?
    因为程序员知道我现在不必要写,所以即使写缓冲状态是WRITEABLE请你也不要通知我,如果没有这个操作,虽然能读到数据,但其中可能多次被注册的其它操作的事件把线程处理抢占过去。假如进行k.interestOps(SelectionKey.OP_READ)用了10ms读完了所有数据,没有这个操作可能要50ms(当然我只是举例而已)。
    你自己说这个操作有没有必要?
      

  9.   

    回楼上的 你的说法是完全错误的还在说人家懂不懂?没学几年java就升到面试人家实习生很了不起是吧?
    就是你这样的人才导致现在学Java的人越来越浮躁明明就简单问题的“可与否”却要对口头用于纠缠不清就像人家说对你说“你好”,你回答人家“你怎么知道我好?”一个样子你自己去仔细思考一下为啥所有的用nio做的框架都用“反应堆”模式而且其模式中基本都是单线程完成selector的select()及其后续具体IO只是将IO得到的数据分发到线程池中进行分发处理。。
    那是因为本身具体IO就是应该有序的无论你先读或先写。对于上层来说需要也应该有序而对于底层,虽然nio对貌似read行为和write是对应类似的但是事实上注意“事实上”注册OP_READ与OP_WRITE导致的效果是不一样的。
    1。注册OP_READ后如果不去显示注销(即interestOps(interestOps() & ~SelectionKey.OP_READ)操作)那么出现效果为:select()会关心可以读的数据(或连接失败情况),只有这种情况会触发释放select()操作。当没有数据的时候根本不会关注。注意:“这就是说只要你需要一直读取数据,就没必要去注销OP_READ操作。”既然没有注销操作,而且该注册一直有效那在重复注册有何意义?不是多余是啥?
    2。而注册OP_WRITE后反而会导致不断的导致select()释放,最终导致select无法正常休眠而使CPU占用率达到100%。因为其真正关注的只是interestOps(SelectionKey.OP_WRITE)操作。所以,就算是你所说同时注册OP_READ以及OP_WRITE也应该控制OP_WRITE的注销与注册而不是OP_READ。如果说要整体wirte控制流量也轮不到进行k.interestOps(SelectionKey.OP_READ)而是应该在其他地方用k.interestOps(k.interestOps() & ~SelectionKey.OP_WRITE)来注销写关注;如果只是读就更没有这个必要了。OP_READ这册后没有显示注销那就一直有效,你一次没读完的底层数据可以直接下次select()继续读。之前已经说了很清楚了。。更何况对于上层操作一般都是有序操作操作。对于NIO的读写方式的论述就知道没有做过真正可靠的NIO,只研究研究buffer clear和filp的行为就以为很了不起了么?
    连LZ想表达的意义都没有看明白,就在这里随便攻击别人。去研究你的“xyz”编译期常量把。。
      

  10.   

    if (sk.isReadable()) {。。}
    楼主的代码中的key你根本不知道它是在同一线程中处理还是在多线程中委派的。正因为如此,在我读的时候我根本不知道其它线程是否会注册WRITE操作来打扰我的READ操作。所以我每读完一次才
    sk.interestOps(SelectionKey.OP_READ),即把这个KEY上的所有任何操作都注销让它只关心READ操作。你自己都知道“而注册OP_WRITE后反而会导致不断的导致select()释放,最终导致select无法正常休眠而使CPU占用率达到100%。”那么我怎么知道在我读的时候这个KEY有没有被其它线程注册WRITE操作呢?为了保证我只关心READ操作所以我才用sk.interestOps(SelectionKey.OP_READ)把这个KEY上的所有操作都更新为READ.你自己说的话不正是我这个观点的解释吗?自己煽自己的耳光去吧!
      

  11.   


    sk.interestOps(SelectionKey.OP_READ),即把这个KEY上的所有任何操作都注销让它只关心READ操作。 我之前也说的很清楚了,sk.interestOps(SelectionKey.OP_READ)这个就事多余的而已在write到相关的地方
    k.interestOps(k.interestOps() & ~SelectionKey.OP_WRITE)以及k.interestOps(SelectionKey.OP_WRITE)
    来控制write行为而与是否重复注册sk.interestOps(SelectionKey.OP_READ)有关系。。并不是说
    sk.interestOps(SelectionKey.OP_READ)因为只注册了sk.interestOps(SelectionKey.OP_READ)导致取消了WRITE就是对的。。因为他的代码中根本没有要说明WRITE相关的操作。所以这句话就是多余的扇耳光的说法就留给你自己吧。。
      

  12.   

    你要关心WRITE就用WRITE相关的东西去控制在读取过后去sk.interestOps(SelectionKey.OP_READ)就是多余的...你自己去读读别人写的代码把... 这种nio基本问题还要狡辩 还用自己不成熟的思路来攻击别人 只能让人鄙视...还要说啥"技术见高低"...只能让人无奈...
      

  13.   

    两位,谢谢你们的精彩讨论,程序是要用到write事件的,我慢慢在写。因为是初学,感觉nio很难理解。还有一个小问题,客户端发送给服务器的数据存在buff中,是有结束标志的,我怎么样才能完整的把数据读出来呢?
      

  14.   

    太精彩了 搜藏,不过我还是偏向axman~~
      

  15.   

    axman  说的话简直就是放屁     首先他的思想就错了
      

  16.   

    按楼主的代码,单线程处理读写就好了,所以wfeng的思路其实没问题的。多线程来处理注册读写,得要同步维护吧。
      

  17.   

    wfeng007 和 axman 两位的精彩辩论很精彩,不过似乎火气有点大,没这个必要,大家都是做技术的,就技术论技术。
    我个人认同 wfeng007 的观点。sk.interestOps(SelectionKey.OP_READ)这条语句没有这个必要,只要你不是显式的注销 SelectionKey.OP_READ,系统就会一直监测是否有数据可读。 tcp 是全双工的,可以同时接收和发送,不是axman 所说的一定要把通道所有的数据都读完了才能用通道去发送数据。设想如果接收的数据量很大的话那岂不是把数据发送的任务给耽搁了,所以这个是完全错误的。当有数据需要发送的话就去注册 SelectionKey.OP_WRITE 事件,不用管是否有数据要读,把接收和发送缓冲区分别管理好,同一个socketchannel可以在不同的线程里同时读和写操作.只要注意数据发送完了就要注销 SelectionKey.OP_WRITE 事件,以避免 cpu 100%,这个我看两位都应该是明白的。