处理android 经典蓝牙发送文件时接收包的问题

需求:android 经典蓝牙发送文件,发送端支持暂停操作(变态!!!!),还想要断点续传(更变态!!!)

      大致实现:

    client端发送定长包,文件首包包头(固定长度)包含此文件标示(名称,文件流总长度等),并且要处理好socket缓存区溢出的问题,防止出现丢包。

  server端从socket读取数据时按照定长包读取,长度不够等下组数据来,长度长了截掉,每个数据流进行包头判断,前一文件未收完的情况下,收到包头数据流,就丢弃,开始接受新的文件流。

 

  case点:包头判断的过程:有两种方式:

      1.定长包,每个包都含有定长包头(流量浪费)

      2.只有首包头,包头中包含文件md5值(文件流中包含其md5,只存在理论上的可能)

 

   遇到的问题:在连续发送文件、或发送较大文件时,会大概率出现接收端解析包头出错的情况。

   分析:在各种打日志,测试分析,代码检视基本确认代码逻辑无明显问题后,怀疑点还是落到了缓存区溢出上。下面就针对这个怀疑点进行排查。

      1.发送端添加测试代码,每发出一个1kb的包就sleep 100ms,经过多次测试发现问题不再复现。。。

      2.更换测试手机,发现不同手机出现的概率不太相同(只能估测..)。

        3.发现console会时不时打出一条错误日志:

      

[ 12-22 12:53:49.849 21464:21536 D/         ]
PORT_WriteDataCO: tx queue is full,tx.queue_size:10890,tx.queue.count:11,available:14941       

      打出错误日志的代码:https://android.googlesource.com/platform/external/bluetooth/bluedroid/+/android-5.0.0_r3/stack/rfcomm/port_api.c

      见下方标红:      

  1 /*******************************************************************************
  2 **
  3 ** Function         PORT_WriteDataCO
  4 **
  5 ** Description      Normally not GKI aware application will call this function
  6 **                  to send data to the port by callout functions
  7 **
  8 ** Parameters:      handle     - Handle returned in the RFCOMM_CreateConnection
  9 **                  fd         - socket fd
 10 **                  p_len      - Byte count returned
 11 **
 12 *******************************************************************************/
 13 int PORT_WriteDataCO (UINT16 handle, int* p_len)
 14 {
 15     tPORT      *p_port;
 16     BT_HDR     *p_buf;
 17     UINT32     event = 0;
 18     int        rc = 0;
 19     UINT16     length;
 20     RFCOMM_TRACE_API ("PORT_WriteDataCO() handle:%d", handle);
 21     int written;
 22     *p_len = 0;
 23     /* Check if handle is valid to avoid crashing */
 24     if ((handle == 0) || (handle > MAX_RFC_PORTS))
 25     {
 26         return (PORT_BAD_HANDLE);
 27     }
 28     p_port = &rfc_cb.port.port[handle - 1];
 29     if (!p_port->in_use || (p_port->state == PORT_STATE_CLOSED))
 30     {
 31         RFCOMM_TRACE_WARNING ("PORT_WriteDataByFd() no port state:%d", p_port->state);
 32         return (PORT_NOT_OPENED);
 33     }
 34     if (!p_port->peer_mtu)
 35     {
 36         RFCOMM_TRACE_ERROR ("PORT_WriteDataByFd() peer_mtu:%d", p_port->peer_mtu);
 37         return (PORT_UNKNOWN_ERROR);
 38     }
 39     int available = 0;
 40     //if(ioctl(fd, FIONREAD, &available) < 0)
 41     if(p_port->p_data_co_callback(handle, (UINT8*)&available, sizeof(available),
 42                                 DATA_CO_CALLBACK_TYPE_OUTGOING_SIZE) == FALSE)
 43     {
 44         RFCOMM_TRACE_ERROR("p_data_co_callback DATA_CO_CALLBACK_TYPE_INCOMING_SIZE failed, available:%d", available);
 45         return (PORT_UNKNOWN_ERROR);
 46     }
 47     if(available == 0)
 48         return PORT_SUCCESS;
 49     /* Length for each buffer is the smaller of GKI buffer, peer MTU, or max_len */
 50     length = RFCOMM_DATA_POOL_BUF_SIZE -
 51             (UINT16)(sizeof(BT_HDR) + L2CAP_MIN_OFFSET + RFCOMM_DATA_OVERHEAD);
 52     /* If there are buffers scheduled for transmission check if requested */
 53     /* data fits into the end of the queue */
 54     PORT_SCHEDULE_LOCK;
 55     if (((p_buf = (BT_HDR *)p_port->tx.queue.p_last) != NULL)
 56      && (((int)p_buf->len + available) <= (int)p_port->peer_mtu)
 57      && (((int)p_buf->len + available) <= (int)length))
 58     {
 59         //if(recv(fd, (UINT8 *)(p_buf + 1) + p_buf->offset + p_buf->len, available, 0) != available)
 60         if(p_port->p_data_co_callback(handle, (UINT8 *)(p_buf + 1) + p_buf->offset + p_buf->len,
 61                                     available, DATA_CO_CALLBACK_TYPE_OUTGOING) == FALSE)
 62         {
 63             error("p_data_co_callback DATA_CO_CALLBACK_TYPE_OUTGOING failed, available:%d", available);
 64             PORT_SCHEDULE_UNLOCK;
 65             return (PORT_UNKNOWN_ERROR);
 66         }
 67         //memcpy ((UINT8 *)(p_buf + 1) + p_buf->offset + p_buf->len, p_data, max_len);
 68         p_port->tx.queue_size += (UINT16)available;
 69         *p_len = available;
 70         p_buf->len += (UINT16)available;
 71         PORT_SCHEDULE_UNLOCK;
 72         return (PORT_SUCCESS);
 73     }
 74     PORT_SCHEDULE_UNLOCK;
 75     //int max_read = length < p_port->peer_mtu ? length : p_port->peer_mtu;
 76     //max_read = available < max_read ? available : max_read;
 77     while (available)
 78     {
 79         /* if we're over buffer high water mark, we're done */
 80         if ((p_port->tx.queue_size  > PORT_TX_HIGH_WM)
 81          || (p_port->tx.queue.count > PORT_TX_BUF_HIGH_WM))
 82         {
 83             port_flow_control_user(p_port);
 84             event |= PORT_EV_FC;
 85             debug("tx queue is full,tx.queue_size:%d,tx.queue.count:%d,available:%d",
 86                     p_port->tx.queue_size, p_port->tx.queue.count, available);
 87             break;
 88          }
 89         /* continue with rfcomm data write */
 90         p_buf = (BT_HDR *)GKI_getpoolbuf (RFCOMM_DATA_POOL_ID);
 91         if (!p_buf)
 92             break;
 93         p_buf->offset         = L2CAP_MIN_OFFSET + RFCOMM_MIN_OFFSET;
 94         p_buf->layer_specific = handle;
 95         if (p_port->peer_mtu < length)
 96             length = p_port->peer_mtu;
 97         if (available < (int)length)
 98             length = (UINT16)available;
 99         p_buf->len = length;
100         p_buf->event          = BT_EVT_TO_BTU_SP_DATA;
101         //memcpy ((UINT8 *)(p_buf + 1) + p_buf->offset, p_data, length);
102         //if(recv(fd, (UINT8 *)(p_buf + 1) + p_buf->offset, (int)length, 0) != (int)length)
103         if(p_port->p_data_co_callback(handle, (UINT8 *)(p_buf + 1) + p_buf->offset, length,
104                                       DATA_CO_CALLBACK_TYPE_OUTGOING) == FALSE)
105         {
106             error("p_data_co_callback DATA_CO_CALLBACK_TYPE_OUTGOING failed, length:%d", length);
107             return (PORT_UNKNOWN_ERROR);
108         }
109         RFCOMM_TRACE_EVENT ("PORT_WriteData %d bytes", length);
110         rc = port_write (p_port, p_buf);
111         /* If queue went below the threashold need to send flow control */
112         event |= port_flow_control_user (p_port);
113         if (rc == PORT_SUCCESS)
114             event |= PORT_EV_TXCHAR;
115         if ((rc != PORT_SUCCESS) && (rc != PORT_CMD_PENDING))
116             break;
117         *p_len  += length;
118         available -= (int)length;
119     }
120     if (!available && (rc != PORT_CMD_PENDING) && (rc != PORT_TX_QUEUE_DISABLED))
121         event |= PORT_EV_TXEMPTY;
122     /* Mask out all events that are not of interest to user */
123     event &= p_port->ev_mask;
124     /* Send event to the application */
125     if (p_port->p_callback && event)
126         (p_port->p_callback)(event, p_port->inx);
127     return (PORT_SUCCESS);
128 }

 

       查到了调用流程:http://blog.csdn.net/wendell_gong/article/details/45060337

    

 

  网上搜了下:

        https://stackoverflow.com/questions/41281207/bluetoothsocket-outputstream-write-issue

        https://stackoverflow.com/questions/8098713/transferring-large-amounts-of-data-over-bluetooth-on-android-gingerbread

  

  分析:基本大致确定了是缓存区溢出导致应用层数据包丢弃引起的混乱问题。

  解决办法:目前更改了发送的定长的数据包大小(加大1倍),降低发送频率。。。(low)测试结果OK...

 

  遗留问题:没能确定地层到底有没有向上层抛出缓存区溢出的异常。正常修改逻辑应该是应用层捕获到异常后停止向底层塞数据包直到正常。。  

  看Android 文档:https://developer.android.com/guide/topics/connectivity/bluetooth.html  应该有流控制。。。哎~

  

=====2017.07.05又看了下

  试着直接找着看了下RFCOMM协议相关的文章:http://blog.csdn.net/Wendell_Gong/article/details/45060337 这里貌似只是简单提了下

  

  http://www.amd.e-technik.uni-rostock.de/ma/gol/lectures/wirlec/bluetooth_info/rfcomm.html#Flow Control Methods Used

  

  顺带看了下简单了解了下CBFC https://www.nap.edu/read/5769/chapter/4#7

  基本确定应该是有流控制的。。。

  

  参考资料:  

  Android 官方关于蓝牙的文档:https://source.android.com/devices/bluetooth

  蓝牙协议的命令和事件 

  找到个分析蓝牙协议的博主:http://www.wowotech.net/bluetooth/ble_connection.html/comment-page-2#comments 

posted on 2017-05-27 15:57  kelisi_king  阅读(896)  评论(0编辑  收藏  举报