需求: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
见下方标红:
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 }
网上搜了下:
https://stackoverflow.com/questions/41281207/bluetoothsocket-outputstream-write-issue
分析:基本大致确定了是缓存区溢出导致应用层数据包丢弃引起的混乱问题。
解决办法:目前更改了发送的定长的数据包大小(加大1倍),降低发送频率。。。(low)测试结果OK...
遗留问题:没能确定地层到底有没有向上层抛出缓存区溢出的异常。正常修改逻辑应该是应用层捕获到异常后停止向底层塞数据包直到正常。。
看Android 文档:https://developer.android.com/guide/topics/connectivity/bluetooth.html 应该有流控制。。。哎~
=====2017.07.05又看了下
试着直接找着看了下RFCOMM协议相关的文章,貌似只是简单提了下
顺带看了下简单了解了下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