zoukankan      html  css  js  c++  java
  • Socket半包、粘包与分包的问题

    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();
        }
    }
    View Code

      设备模拟工具https://pan.baidu.com/s/1rAwicJ2o-xgAczYedcmL6g

      提取码:AAAA

      :上面只是简单案例,并没有实现IEC104协议的通讯。

  • 相关阅读:
    从寻找资源的习惯上谈如何获得好的代码及控件(使用Koders查找)
    数据仓库的一些基本知识2
    淘宝API开发系列商家的绑定
    淘宝API开发系列开篇概述
    读取实体类的属性的备注作为表头的应用
    设置Windows服务允许进行桌面交互,实现屏幕监控
    C#进行Visio二次开发之组合形状操作
    吉日嘎啦通用权限管理系统解读及重构升华高度封装的编辑窗体
    WinForm界面开发之模块化分合
    CLR中的内存管理
  • 原文地址:https://www.cnblogs.com/bl123/p/14845196.html
Copyright © 2011-2022 走看看