tblebuf、tsendbuf不属于tble。tblebuf提供了基于lipdp格式的数据接收功能,tsendbuf则适应了蓝牙发送带有反馈机制这个特点。
- tblebuf用于在接收端缓存数据。数据按着lipdp格式,一个消息、一个消息的发来。它自带提取消息功能。
- tsendbuf用于在发送端缓存数据。和tblebuf不一样,它不检查数据内容,认为就是字节流,把片断数据组成可能较大数据,然后发送。
- tsendbuf强制认为发送数据后,如果成功了,会收到一个反馈,而且发送时刻和收到反馈时刻会有个间隔。
一、tblebuf
tblebuf用于在接收端缓存数据。数据按着lipdp格式,一个消息、一个消息的发来,借助tlipdp_receiver,它自带提取消息功能。
在蓝牙编程中,tblebuf既可用在peripheral,也可用在center。一旦确定了,只能工作在一种模式。
使用tblebuf的接收功能,一般用着以下逻辑。
- 构造tblebuf对象,假设变量名是buf_。如果只有一个chara在接收数据,那只要一个buf_就够了。
- buf_.set_did_read(std::bind(&tble2::did_read_ble, this, _1, _2, _3))。设置收到消息后的回调函数。
- 蓝牙连接后,从chara收到数据[data, len],调用buf_.enqueue(data, len)。
- (buf_.enqueue)。它其实就是tlipdp_receiver::enqueue,发现收到一条完整消息,调用设置的回调did_read_。
- (did_read_)。先使用tlipdp_parser解析出负载中的各单元,然后依着这些单元数据完成操作。
二、tsendbuf
tsendbuf用于在发送端缓存数据。和tblebuf不一样,它不检查数据内容,认为就是字节流,把片断数据组成可能较大数据,然后发送。
tsendbuf强制认为发送数据后,如果成功了,会收到一个反馈。以下是它的发送逻辑。
- 要发送的数据能过did_write_发送出去。并置wait_notification_ = true。
- 收到反馈后,置wait_notification_ = false。并检查是否缓存中是否有数据要发送,有就转到第一步。否则结束发送。
在蓝牙编程时,只有写特征才能需要tsendbuf。
既然tsendbuf不检查数据内容,那么有发送要求了,直接向写特征发就行了,为什么要用它?——将A数据发向对端后,为安全,需要等收到A数据的写反馈后,再开始下一次发送。可是,发送时刻和收到反馈时刻有个时间间隔,如果在这间隔有发送请求,有了tsendbuf后,可以先缓存数据,等收到A数据写反馈了,再继续发送。
2.1 enqueue、did_notification_sent
enqueue和did_notification_sent都是向缓存喂数据,区别是,enquque是app主动调用,did_notification_sent则是tble收到反馈时调用。
enqueue
app有想发送的数据了,调用enqueue。
void tsendbuf::enqueue(const uint8_t* data, int len) { resize_send_data(send_data_vsize_ + len); memcpy(send_data_ + send_data_vsize_, data, len); send_data_vsize_ += len; if (!wait_notification_) { send_data_internal(); } }
wait_notification_==false,意味着上次发送已经有了写反馈,此次可调用send_data_internal直接发送。
did_notification_sent
tble收到写返馈了,调用did_notification_sent,app不应该调用这函数。
bool tsendbuf::did_notification_sent(int error) { wait_notification_ = false; if (send_data_vsize_ > 0) { if (error == 0) { send_data_internal(); } else { // empty send data buf. send_data_vsize_ = 0; } } return wait_notification_; }
首先置wait_notification_ = false,然后检查缓存中是否有数据要发送,有就调用send_data_internal进行发送。
2.2 tsendbuf::send_data_internal
send_data_internal一会定发送数据,怎么发送数据,是通过调用app提供的did_write_。针对tble,did_write_主要是SDL_BleWriteCharacteristic。
void tsendbuf::send_data_internal() { VALIDATE(!wait_notification_, null_str); VALIDATE(send_data_vsize_ > 0, null_str); const int this_len = SDL_min(send_data_vsize_, mtu_size_); if (did_write_) { tdisable_did_write_lock lock(*this); did_write_(send_data_, this_len); } if (send_data_vsize_ > this_len) { memcpy(send_data_, send_data_ + this_len, send_data_vsize_ - this_len); } // 修改send_data_vsize_是在did_write_之后, // 这会导致如果在did_write_内用send_data_vsize_判断是否还有数据要发送,像did_notification_sent,那会出错。 send_data_vsize_ -= this_len; wait_notification_ = true; }
mtu_size_是主观设置的一次最大可发送字节数。蓝牙链路层会自动拆包,或许吧,没必要。
send_data_vsize_语义是缓存中还需要被发送的字节数。为什么要增加tdisable_did_write_lock?——防止SDL在实现SDL_BleWriteCharacteristic时,内中就调用current_callbacks->write_characteristic(...),后者则会调用tsendbuf::did_notification_sent。由于修改send_data_vsize_是在did_write_之后,这导致did_notification_sent用send_data_vsize_判断是否还有数据要发送时,结果肯还是要发送,出现误判,结果是一次又一次调用did_write_,造成死锁。
tsendbuf认为写和收到反馈总是有间隔,不该出现did_write_内调用did_notification_sent。