Socket半包、粘包与分包的问题

949 阅读3分钟

1.简述                                                  

  首先看两个概念

    短连接

  • 连接->传输数据->关闭连接
  • HTTP是无状态的,浏览器和服务器每进行一次HTTP操作,就建立一次连接,但任务结束就中断连接。
  • 也可以这样说:短连接是指Socket连接后发送后接收完数据后马上断开连接。

    长连接

  • 连接->传输数据->保持连接 -> 传输数据-> 。。。 ->关闭连接。
  • 长连接指建立Socket连接后不管是否使用都保持连接,但安全性较差。

  半包:指接受方没有接受到一个完整的包,只接受了部分,这种情况主要是由于TCP为提高传输效率,将一个包分配的足够大,导致接受方并不能一次接受完。(在长连接和短连接中都会出现)。

  粘包:指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。

  分包:指在出现粘包的时候接收方要进行分包处理。

  :粘包情况有两种,一种是粘在一起的包都是完整的数据包,另一种情况是粘在一起的包有不完整的包。

  之所以出现粘包和半包现象,是因为TCP当中,只有流的概念,没有包的概念。

2.示例                                                  

  最近使用Socket(从机)读取设备数据(主机),根据协议简单做了一个示例代码(实现了分包)如下:

package org.tempuri;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Test {
    private static Socket socket = null;
    private static BufferedInputStream read;
    private static BufferedOutputStream write;
    /**
     * IEC104协议
     * 第二位表示数据长度
     */
    private static int[] activation = new int[]{0x68,0x04,0x07,0x00,0x00,0x00};//激活
    private static int[] totalCall = new int[]{0x68,0x0E,0x00,0x00,0x00,0x00,0x64,0x01,0x06,0x00,0x01,0x00,0x00,0x00,0x00,0x14};//总召唤
    private static int[] sendS = new int[]{0x68,0x04,0x01,0x00,0x00,0x00};//S帧
    
    public static void main(String[] args) throws Exception {
        while(true){
            try {
                socket = new Socket("127.0.0.1", 2404);
                read = new BufferedInputStream(socket.getInputStream());
                write = new BufferedOutputStream(socket.getOutputStream());
                sendData(activation);
                sendData(totalCall);
                read();
            } catch (Exception e) {
                System.out.println("连接失败......");
            }
        }
    }
    
    //读取
    public static void read(){
        List<Byte> bts = new ArrayList<Byte>();
        byte[] btsb = new byte[512];
        int bLen;
        int dataLen=0;
        long time = System.currentTimeMillis();
        while(true){
            try {
                bLen = read.read(btsb);
                if(bLen != -1){
                    time = System.currentTimeMillis();
                    for(int i=0; i<bLen; i++){
                        byte b = btsb[i];
                        if(bts.size() == 0){
                            if(b != 0x68)//协议头不是0x68跳过
                                continue;
                            else
                                bts.add(b);
                        }else{
                            bts.add(b);
                            if(bts.size() == 2){
                                dataLen = (0XFF & b) + 2;//获取数据长度
                            }else if(bts.size() >= dataLen){
                                byte[] temp = new byte[bts.size()];
                                for(int j=0; j<bts.size(); j++)
                                    temp[j]=bts.get(j);
                                System.out.println("接收:"+getStrByByteArr(temp));
                                if((temp[2] & 1) == 1)//S帧
                                    sendData(sendS);
                                bts.clear();
                            }
                        }
                    }
                }
                
                if(System.currentTimeMillis() - time > 8000)//8秒没有收到数据发送S帧
                    sendData(sendS);
            } catch (Exception e) {
                break;
            }
        }
    }
    
    //退出连接
    public static synchronized void exitSocket(){
        try {
            if(socket != null)
                socket.close();
            if(read != null)
                read.close();
            if(write != null)
                write.close();
        } catch (IOException e1) {
            System.out.println("关闭出错");
        }finally{
            System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+",连接通道已关闭 ");
        }
    }
    
    //发送命令
    public static synchronized void sendData(int[] msg) throws Exception{
        System.out.println("发送:-----"+getStrByByteArr(iCB(msg)));
        write.write(iCB(msg));
        write.flush();
    }
    
    public static byte[] iCB(int[] arr){
        if(arr==null)
            return null;
        byte[] narr = new byte[arr.length];
        for(int i=0; i<arr.length; i++){
            narr[i] = (byte)arr[i];
        }
        return narr;
    }
    
    public static String getStrByByteArr(byte[] bts){
        StringBuffer sb = new StringBuffer();
        for(int i=0; i<bts.length; i++){
            String str = Integer.toHexString(bts[i]&0xFF);
            if(str.length()==1)
                str="0"+str;
            sb.append(" ").append(str);
        }
        return sb.toString().toUpperCase();
    }
}