只知道原理,代码实现还不知道怎么实现?请高手指点,谢谢!高分馈赠!

解决方案 »

  1.   

    此回复为自动发出,仅用于显示而已,并无任何其他特殊作用
    楼主【jiangguilong2000】截止到2008-07-22 23:16:57的历史汇总数据(不包括此帖):
    发帖的总数量:10                       发帖的总分数:390                      每贴平均分数:39                       
    回帖的总数量:9                        得分贴总数量:1                        回帖的得分率:11%                      
    结贴的总数量:8                        结贴的总分数:270                      
    无满意结贴数:2                        无满意结贴分:40                       
    未结的帖子数:2                        未结的总分数:120                      
    结贴的百分比:80.00 %               结分的百分比:69.23 %                  
    无满意结贴率:25.00 %               无满意结分率:14.81 %                  
    楼主加油
      

  2.   

    一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系在java中对于这样的优化,lz可以用非阻塞的流操作,非阻塞socket和流操作都是nio包中个人建议,呵呵,没有去认真的考虑过这个问题
      

  3.   

    可能解决 粘包的问题。
    Socket socket = new Socket();
    socket.setTcpNoDelay(true);
    分包可能不太好解决
    似乎和两端的介质等都有关系。
      

  4.   

    之所以出现粘包和半包现象,是因为TCP当中,只有流的概念,没有包的概念.楼主可以使用UDP协议.这样可以就可以区分每个包了.但是要确保包的丢失处理.为了提到效率,可以考虑写一个滑动窗口进行收发包.若采用TCP协议进行传输,就要将每个包区分开来.可以有三种方式.因为TCP是面向流的.流只有打开和关闭,你要用一个流传输多个包,那就要向办法区分出每个包.一:: 可以每次发送同样大小的包,过大的包不予发送,过小的包,后面部分用固定的字符'\0'进行填充.二:: 将流按字符处理,抽出一个字符做转义字符(通常Java用'\'来做转义字符,比如"\n"表示换行).假如就设'\'为转义字符,发送方如果流当中出现'\',就在后面在追加一个'\',如果包结束,则用'\'做包的结束符.这样,在接收方,若读取一个单独的'\'或者流结束,就标示前面的内容构成一个包,如果连续读取两个'\',就将两个'\'用一个'\'进行替换.这样,就可以保证原来包中的信息不变,同时也能区分出每个包了.三:: 在发送方发送一个包的时候,先将这个包的长度发送给对方(一般是4个字节表示包长),然后再将包的内容发送过去.接收方先接收4个字节,看看包的长度,然后按照长度来接收包,这样就不会出错了.以上三种方法,是网络传输中经常用到的方法.后两种很常见.最后一种,在TCP长连接传输中应用最多.
    综合以上的说法,就是要在TCP协议以上再封装一层协议,用来做分包的信息交换.
      

  5.   

    当然,如果TCP不是非要长连接,或者,信息包不是批量传输的情况下.可以一次TCP连接只传输一个包.这种情况下一般,一次TCP完成一次交互,即发送方发送信息包,接收方接收信息包同时发送一个接收方的响应包给发送方,表明接收方收到信息包,还是收到了错误包,或者接收方系统异常没有处理这个包之类的信息.
    其实HTTP的交互过程,就是非常类似这样的.当然这种情况,必须要求发送方和接收方互为服务器客户端,否则信息就是单向的了.
      

  6.   

    本帖最后由 kokobox 于 2008-07-23 23:24:45 编辑
      

  7.   

    上面的回复代码中这一行in.read(data);
    其实我是想让这一行高亮红色显示的,没想到编辑器不能识别,大家注意一下,其实就in.read(data);
      

  8.   

    经过几个小时的研究,终于把这个issue搞定了,我写了个递归函数,于是处理半包粘包问题的代码如下所示:private final static int PACKATHEADER_TYPE_LENGTH=4;
    private final static int PACKATHEADER_DATALEN_LENGTH=4;
    private final static int PACKATHEADER_LENGTH=PACKATHEADER_TYPE_LENGTH+PACKATHEADER_DATALEN_LENGTH;
    private BufferedReader in;
    public void run() {
    while (true){
    try {
    recv();
    } catch (CrescentException e) {
    Logger.error("Receive message failed.", e);
    }
    try {
    Thread.sleep(50);
    } catch (InterruptedException e) {
    Logger.error("Interrupted waiting for job to finish", e);
    }
    }
    }
    private void recv() throws CrescentException{
    char[] temp = new char[PACKATHEADER_LENGTH];
    this.recursionRead(temp,0,PACKATHEADER_LENGTH);
    String dataLen = new String(temp, PACKATHEADER_TYPE_LENGTH, PACKATHEADER_DATALEN_LENGTH);
    int size = Integer.parseInt(dataLen);
    char[] data = new char[size];
    this.recursionRead(data,0,size);
    String str=String.valueOf(temp)+String.valueOf(data);
    msgQueue.offer(str);
    }private void recursionRead(char[] data,int length,int gap)throws CrescentException{
    int lengthTemp=0;
    try {
    lengthTemp = in.read(data, length, gap);
    } catch (IOException e) {
    throw new ClientException("Transceiver-recursionRead-1001","read I/O stream failed",e);
    }
    int gapTemp=gap-lengthTemp;
    if(gapTemp>0){
    recursionRead(data,length+lengthTemp,gapTemp);
    }
    }
    主要是大家注意recursionRead(char[] data,int length,int gap)这个函数,原理是读入期望长度的数据,但如果
    读入的长度小于期望长度,算出长度差,继续读剩余的....以此递归。Performance:还是用上述的case,服务器端模拟粘包和半包发送,间隔为50ms,500ms,5000ms....结果都是:
    10010006avsdfg
    10020008sdafhjSD
    10010006avsdfg
    10020008sdafhjSD
    10010006avsdfg
    .....
    与期望的结果相匹配,并且Performance比以前写的要好!
    我就研究到这,希望各位再踊跃跟帖,有没有更好的处理办法,用NIO方式或者阻塞式socket也可以,最好能附上源关键代码(处理粘包和半包的部分)和代码运行结果。关注中!
      

  9.   

    我一般处理是:一个BUFFER,用于保存当前连接的读缓存
    有数据时,Buffer = Buffer + DataIn,不停的接收
    收完成后,开始解析Buffer,
    根据包的协议,不停的解析Buffer,并形成一个个包进行处理,处理后,Buffer = Buffer - Data,并继续解包
    完成。
      

  10.   

    喜欢使用Buffer,把数据先压入,而后按照协议去解析,这样就绕开了半包等问题
      

  11.   

    这也是个很好的办法,和我实现的区别是,我是根据包头所得到的包体长度读入Buffer,反复进行,如果检测到半包,就继续申请读取。
      

  12.   

    JDK里面.BufferedReader是用来处理字符流的.在网络通信当中,一般不用这个类.
    而用这个类来处理的.一般是我讲到的第二种处理方式.只不过,是用换行符作为包的分隔符(我讲的第二方式比较通用,特例的情况下如果传输的是ACSII字符流,可以指定换行符为包的分隔符).接收端使用BufferedReader的readLine()方法.发送端在发送字符串之后,再追发一个换行符'\n',用于进行包的分隔.
    楼主可以试一下,这个效率会很高,而且,不会有粘包,半包的现象.由于网络传输数据的不确定性(也就是说有可能传输的是图像,文件什么的),所以,一般直接使用InputStream这个类来操作.它的read方法都是阻塞读的.参数为byte数组的情况下,如果流没有结束,该方法直到byte数组填满才会返回.楼主可以试一下,直接用InputStream这个类来处理.就不会出现使用BufferedReader读出半包的情况了.当然也不用再去递归补读了.直接使用InputStream一般效率还是很高的,如果还要提高效率,那就要自己编写一个缓冲区了.使用多线程(线程数量3个就可以)并发处理.性能会显著提高的.
      

  13.   

    看下来这个就不像是TCP应该处理的问题,貌似楼主是在用TCP模拟UDP。直接用UDP处理包数据,然后用TCP处理流数据多好。UDP确实有丢数据的可能,不过可以模拟一下TCP里的握手协议。一个包一个ID,server收到后给client发个ID回去,告诉client收到此包了,否则client继续发送。而且,UDP协议比TCP协议好的一点就是速度快,每个包都是独自路由的,TCP一旦建立连接后路由路径就定下来了。当然UDP不可能保证数据包到达server的顺序,不过这个在楼主的应用中貌似不重要。
      

  14.   

    socket在通信处理中数据包一分为二或二合为一的状况很多,而且不好模拟测试. 处理方法一般有二:1.定长法. 每次发送数据都定长. 多余的数据作为下一个包的起始部分保留. 收不够长度就存储起来, 知道够数据为止
    2.起始符法. 每个数据都有特定的起始符号内的特定数据, 如果起始符范围外有数据,可以确认是程序逻辑有问题或有人搞破坏了.上面的方法开工前, 都要制定你的应用协议. 原理清楚了, 协议搞完整了, 程序就好写了.
      

  15.   

    29楼说的 UDP是独立路由的,这一点,我赞同. 
    但是,"TCP一旦建立连接后路由路径就定下来了",这句话,我不赞同.
    个人理解,TCP在传输流的时候,其实是将整个流拆成很多个独立的IP报文,进行独立路由传输的,只是,到了接收端,接收端会把这些报文按照先后顺序重新组装成TCP的IO流,当然,丢包的话,会有相应的机制来确保发送端重新发送.
    TCP的建立连接阶段,本人认为,其实就是传输双达成一个共识,比如双方的发送窗口,接收窗口的大小,每个报文的大小,以及超时的时间范围等等.
    因为,实际上,TCP也好,UDP也好,在网络传输的过程中都遵循的是IP协议,并且,每个节点的路由也只是遵照IP协议来进行转发的.
    所以,相对来讲TCP底层的IP报文,它的路由具有不确定性.
    以上是我的愚见...
      

  16.   

    我同意,但是tcp要进行差错检验或着重发机制,还有就是,TCP一旦建立连接,只是说传输期间,双方的连接一直存在。
      

  17.   

    1.你的回复表示了你连帖子的意思都没看懂,看看其他人回复的吧,特别是preferme ,kokobox ,TinyJimmy==回复的,他们的回复的内容就是帖子所需要的答案。
    2.你这样的回复根本没有任何意义,就好像我们现在问向量机,正交矩阵的问题,然后你跳出来说这都是大学里学的东西,还好意思来这里问。为什么我们来这里CSDN来问,是因为我们想要更快的解决问题,得到其他人更有项目经验的回答,有些东西,不是大学里的书里能说透的,特别是实现这块。而CSDN里聚集的都是能人,我们走到这里都是为了经验交流,共同进步。大家觉得呢?
      

  18.   

    1.如果是TCP流传输,就定义包文头与包文体结构,然后通过包文头中的报文体长度来进行截取.(报文体为变长,报文头为定长)
    2.如果是UDP,内置支持消息边界,不用考虑粘包,只需要考虑可靠性问题.通常为减少TCP粘包以及提高TCP效率,每个包建议不超过1.3K,以避免MAC层进行分祯
      

  19.   

    这位仁兄说话直白了点,哈哈本人可能见识浅薄,没听说粘包和半包的问题,
    只知道首先定义好socket的网络通讯协议是不会有这种问题的,具体内容楼主可以参照下各通信运营商自己开发的通讯协议,如CMPP,SMGP,SGIP等等。
      

  20.   

    学习,ls说的是在TCP上层又封装过的协议吧
      

  21.   

    更新:采用了缓冲输入/输出流来包装输出流,再采用数据输入/输出输出流进行包装,加快传输的速度。最近把包装类换成了DataInputStream和DataOutputStream,替换了以前的BufferedReader和PrintStream,字符传送改成了字节传送方式,并发现有个方法可以取代我写的递归读recursionRead()的方法,readFully,参看了一下API说明:
    void
     readFully(byte[] buf) 
              读取字节,同时阻塞直至读取所有字节。
     
     void
     readFully(byte[] buf, int off, int len) 
              读取字节,同时阻塞直至读取所有字节。
    包装如下:in = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
    out = new DataOutputStream(socket.getOutputStream());
    满足我们以前的需求!preferme ,kokobox 以及大家觉得合适吗?
      

  22.   

    粘包后,分包的具体实现如下
        
    // 定义消息接受缓冲区
    byte[] buf = new byte[4096];
                    // 真正用来放数据的缓冲区,要有两个包长度的大小
    byte[] tmp = new byte[4096 * 2]; 
                    // tmp缓冲区中实际数据长度
    int len = 0;
                    // 本次解析中使用了的缓冲数据长度
    int usedlen = 0;
                    // 本次接收包的真实长度
    int recvLen = 0;
    try {
    // 等待接受消息
    while((recvLen = skt.getInputStream().read(buf)) > 0) {

    // 解析获取到的消息
    ClientFileInfoPack pack = new ClientFileInfoPack();
    TcpDataHeader headerRecv = (TcpDataHeader)pack.getHeader(buf);
                                    // 如果包中有上次解析剩下的余留数据,就先处理
    if(len > 0) {
    // 如果长度小于通讯时候定义的头长度(我们的是26个byte)
    if(len<26) {
                                                    // 不够头长度,就读现在的包
    System.arraycopy(buf, 0, tmp, len, recvLen);
    len += recvLen;
    // 再判断是不是够一个头的长度,够了就分析这个头里面描述数据体长度的字段
    if(len>=26){
    headerRecv = (TcpDataHeader)pack.getHeader(tmp);
    if((recvLen + len) < headerRecv.length()) {
    continue;
    }
    }
    } else if (len >= 26) {// 如果余留的数据就超过头长度
    // 解析这个头, 然后读数据看看有分半包和粘包的情况
    headerRecv = (TcpDataHeader)pack.getHeader(tmp);
    if ((recvLen + len) < headerRecv.length()) {
    System.arraycopy(buf, 0, tmp, len, recvLen);
    len += recvLen;
    continue;
    } else {
    System.arraycopy(buf, 0, tmp, len, recvLen);
    len += recvLen;
    }
    }
    } else {
                                            // 没有余留数据,那么就直接解析先在接收到的包
    System.arraycopy(buf, 0, tmp, len, recvLen);
    len += recvLen;
    if(len>=26) {
    headerRecv = (TcpDataHeader)pack.getHeader(tmp);
    if(len < headerRecv.length()) {
    continue;
    }
    } else {
    continue;
    }
    }
                                    // 实际本包长度
    usedlen = headerRecv.length();
    //////////////////////////////////////////////////////////////////////////////////////

    // 彻底解析消息
    MSG msg = pack.parse(tmp);
    // 先判断有没通讯异常,我们规定30000以上的是网络通讯异常
    if(msg.getErrorCode() > 29999) {
    ClientFileInfo cfi = new ClientFileInfo();
    cfi.setErrorCode(msg.getErrorCode());
    infos.add(cfi);
    return infos;
    }
    // 把剩余的多余的移到缓冲区头部,因为这必然是下一个包的开始
    for(int i=usedlen; i<len; i++) {
    tmp[i - usedlen] = tmp[i];
    }
    //////////////////////////////////////////////////////////////////////////////////////
                                    // 因为解析了一个包,所以要减去这个包的长度
    len -= usedlen;
    //////////////////////////////////////////////////////////////////////////////////////
      

  23.   


      
      分包算法
      针对三种不同的粘包现象,分包算法分别采取了相应的解决办法。其基本思路是首先将待处理的接收数据流(长度设为m)强行转换成预定的结构数据形式,并从中取出结构数据长度字段,即图5中的n,而后根据n计算得到第一包数据长度。
      1)若n<m,则表明数据流包含多包数据,从其头部截取n个字节存入临时缓冲区,剩余部分数据依此继续循环处理,直至结束。
      2)若n=m,则表明数据流内容恰好是一完整结构数据,直接将其存入临时缓冲区即可。
      3)若n>m,则表明数据流内容尚不够构成一完整结构数据,需留待与下一包数据合并后再行处理。
      对分包算法具体内容及软件实现有兴趣者,可与作者联系。
      

  24.   

    解决方法:
    1.定义自己的协议
    2.
    byte[] byteArray = new byte[len];
    int actualReadNum = inputStream.read(byteArray, off, len); if (actualReadNum == len) {
    return byteArray;
    } int readNum;
    while (actualReadNum < len) {
    readNum = inputStream.read(byteArray, actualReadNum, len
    - actualReadNum);
    if (readNum == -1) {
    throw new IOException("读取失败!");
    }
    actualReadNum = actualReadNum + readNum;
    }
    return byteArray;
    }