最近阅读Android的源码,发现了一个挺有意思的代码。
使用TCP发送消息的时候,需要定义一条消息的边界,防止接收方"粘包"。
cpp
static std::string PackageString(const std::string& str) {
// 1. 定义基础开销
// 开销包括:第一个 '\n',尾部的 '\n' 和 '\f',共 3 个字节
size_t overhead_length = 3;
// 2. 计算如果不算长度前缀的位数,大概需要多少位
// log10(x) 返回 x 的常用对数。例如 log10(9) ≈ 0.95, log10(10) = 1。
// static_cast<size_t>(log10(N)) + 1 可以计算数字 N 有几位。
// 这里计算的是:(原始字符串长度 + 3个开销字节) 需要几位数字来表示。
size_t str_size_digits = 1 + static_cast<size_t>(log10(str.size() + overhead_length));
// 3. 计算加上长度前缀本身的位数后,总共需要多少位
// 这里的逻辑是:总长度 = 原始串 + 开销 + "长度的位数"
size_t total_size_digits =
1 + static_cast<size_t>(log10(str.size() + overhead_length + str_size_digits));
// 4. 边界修正 (核心算法)
// 如果加上 "长度的位数" 后,导致总长度跨越了进位 (例如从 9 变成了 10),
// 那么表示总长度的数字位数就会增加 1 (从1位变成2位)。
// 这种情况只会发生一次(因为增加一位带来的增长很小,不可能再次触发进位)。
if (total_size_digits != str_size_digits) {
overhead_length++;
}
// 5. 计算最终的总大小
// 总大小 = 原始数据 + 开销(3或4) + 长度数字本身的位数
size_t total_size = str.size() + overhead_length + str_size_digits;
// 6. 格式化输出
// %zu 是 size_t 的格式化占位符
return android::base::StringPrintf("%zu\n%s\n\f", total_size, str.c_str());
}
封装后的协议格式如下:
<总长度>\n<原始数据>\n\f
- <总长度>: 一个 ASCII 数字字符串,表示整个包(包括长度前缀本身、换行符、原始数据和尾部字符)的字节数。
- \n: 分隔符。
- <原始数据>: 输入的字符串内容。
- \n\f: 尾部标记。\f 是换页符 (Form Feed, ASCII 0x0C),通常用作消息结束的哨兵字符。
测试代码
cpp
#include <iostream>
#include <string>
#include <cmath>
#include <vector>
#include <cstdio>
#include <memory>
// 模拟 android::base::StringPrintf
template<typename ... Args>
std::string StringPrintf(const std::string& format, Args ... args) {
int size_s = std::snprintf(nullptr, 0, format.c_str(), args ...) + 1;
if (size_s <= 0) { throw std::runtime_error("Error during formatting."); }
auto size = static_cast<size_t>(size_s);
std::unique_ptr<char[]> buf(new char[size]);
std::snprintf(buf.get(), size, format.c_str(), args ...);
return std::string(buf.get(), buf.get() + size - 1);
}
// 提取出来的 PackageString 函数
static std::string PackageString(const std::string& str) {
size_t overhead_length = 3; // \n \n \f.
// 必须处理空字符串导致 log10(0) 的情况,虽然源码没处理,但逻辑上 str.size() 最小为0,+overhead=3,不会log10(0)
// log10(3) = 0.47 -> 0 + 1 = 1.
size_t str_size_digits = 1 + static_cast<size_t>(log10(str.size() + overhead_length));
size_t total_size_digits =
1 + static_cast<size_t>(log10(str.size() + overhead_length + str_size_digits));
if (total_size_digits != str_size_digits) {
overhead_length++;
}
size_t total_size = str.size() + overhead_length + str_size_digits;
return StringPrintf("%zu\n%s\n\f", total_size, str.c_str());
}
void TestInput(const std::string& input, const std::string& desc) {
std::string pkg = PackageString(input);
std::cout << "--- " << desc << " ---" << std::endl;
std::cout << "Input Length: " << input.size() << std::endl;
// 把不可见字符转义
std::cout << "Output String: \"";
for (char c : pkg) {
if (c == '\n') std::cout << "\\n";
else if (c == '\f') std::cout << "\\f";
else std::cout << c;
}
std::cout << "\"" << std::endl;
// 验证长度一致性
// 解析头部长度
size_t first_newline = pkg.find('\n');
std::string len_str = pkg.substr(0, first_newline);
size_t declared_len = std::stoi(len_str);
std::cout << "Actual Size: " << pkg.size() << std::endl;
std::cout << "Header Says: " << declared_len << std::endl;
if (pkg.size() == declared_len) {
std::cout << "VERIFIED" << std::endl;
} else {
std::cout << "MISMATCH" << std::endl;
}
std::cout << std::endl;
}
int main() {
// 1. 空字符串测试
TestInput("", "Empty String");
// 2. 普通短字符串
TestInput("hello", "Short String");
// 3. 临界值测试:长度为6 (总长度从个位数跨越到十位数)
// 6 + 3(overhead) + 1(digit) = 10 -> 需要修正
TestInput("123456", "Edge Case: 6 chars");
// 4. 临界值测试:长度为96 (总长度从2位跨越到3位)
// 96 + 3(overhead) + 2(digits) = 101 -> 3 digits.
std::string s96(96, 'x');
TestInput(s96, "Edge Case: 96 chars");
return 0;
}
结果输出
bash
--- Empty String ---
Input Length: 0
Output String: "4\n\n\f"
Actual Size: 4
Header Says: 4
VERIFIED
--- Short String ---
Input Length: 5
Output String: "9\nhello\n\f"
Actual Size: 9
Header Says: 9
VERIFIED
--- Edge Case: 6 chars ---
Input Length: 6
Output String: "11\n123456\n\f"
Actual Size: 11
Header Says: 11
VERIFIED
--- Edge Case: 96 chars ---
Input Length: 96
Output String: "102\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\
f"
Actual Size: 102
Header Says: 102
VERIFIED
进程已结束,退出代码为 0