在TCP通讯中,常需要添加一些特殊字节用作识别,但数据里面肯定有可能包含这些识别字节,一开始想到的是把数据里面的这些特殊字节替换为另一个字节,使特殊字节不出现(通常的转义算法转义后,被转义字符还是会出现在转义结果中的),一番折腾后走入了死胡同,始终无法全部的正确还原,再经过一番折腾才想到,其实可以把某几个字节序列用作识别,然后对这个组合里的每一个字节进行转义,这样转义后数据里就不会再出现识别用的字节序列。
注意,编程时写的斜杆转义是给编译器识别的,如果发送一个"\r\n"的字符串,实际发送的数据里回车换行并没有被转义。
还有一种处理方案,就是发送数据的base64字符串,然后以不在base64使用字符里的其他字符作为识别,但是这样会导致数据长度增加,平均增加33%左右。
转义与还原过程
// 转义
text.replace(escape, escape + escape);
text.replace(delimiter1, escape + delimiter1);
text.replace(delimiter2, escape + delimiter2);
// 还原
text.replace(escape + delimiter2 , delimiter2);
text.replace(escape + delimiter1 , delimiter1);
text.replace(escape + escape, escape);
其中escape为转义符号,delimiter1 、delimiter2为被转义字符,注意转义符号在替换中的顺序!
C语言的转义实现与测试(gcc -std=c99 -o escape escape.c)
#include <stdio.h>
//数据包分隔序列:0x03 0x02(也可以是0x02 0x03)
//这里先计算转义后长度,再转义
//可以预见:转义后最大长度也就两倍,所有也可以分配两倍大小的空间后直接转换,不外乎使用cpu与使用内存的平衡。
unsigned char esc = 0x1b; //转义符号
unsigned char d1 = 0x02; //转义字符1
unsigned char d2 = 0x03; //转义字符2
int getEscapeToLen(unsigned char data[], int len)
{
int count = 0;
for(int i = 0; i < len; i++){
if(data[i] == esc || data[i] == d1 || data[i] == d2) count++;
}
return len + count;
}
int getEscapeOutLen(unsigned char data[], int len)
{
int count = 0;
for(int i = 0; i < len; i++){
if(data[i] == esc && i + 1 < len)
{
if(data[i + 1] == esc || data[i + 1] == d1 || data[i + 1] == d2)
{
count++;
}
i++;
}
}
return len - count;
}
int escapeTo(unsigned char data[], int len, unsigned char result[])
{
int count = 0;
for(int i = 0; i < len; i++){
if(data[i] == esc || data[i] == d1 || data[i] == d2)
{
result[i + count] = esc;
count++;
}
result[i + count] = data[i];
}
return count;
}
int escapeOut(unsigned char data[], int len, unsigned char result[])
{
int count = 0;
for(int i = 0; i < len; i++){
if(data[i] == esc && i + 1 < len)
{
if(data[i + 1] == esc || data[i + 1] == d1 || data[i + 1] == d2)
{
count++;
}
else
{
result[i - count] = data[i];
}
i++;
}
result[i - count] = data[i];
}
return count;
}
void test(unsigned char data[], int dataLen)
{
for(int i = 0; i < dataLen; i++)
{
printf("0x%02X ", data[i]);
}
printf(" ");
int toLen = getEscapeToLen(data, dataLen);
unsigned char toResult[toLen];
int toCunnt = escapeTo(data, dataLen, toResult);
for(int i = 0; i < 12; i++)
{
if(i < toLen)
{
printf("0x%02X ", toResult[i]);
}
else
{
printf(" ");
}
}
printf("%d ", toCunnt);
printf(" ");
int outLen = getEscapeOutLen(toResult, toLen);
unsigned char outResult[outLen];
int outCount = escapeOut(toResult, toLen, outResult);
for(int i = 0; i < outLen; i++)
{
printf("0x%02X ", outResult[i]);
}
printf("%d ", outCount);
printf(" ");
if(dataLen==outLen)
{
int hasErr = 0;
for(int i = 0; i < dataLen; i++)
{
if(data[i] != outResult[i])
{
hasErr = 1;
}
}
if(hasErr == 0)
{
printf("ok");
}
}
printf("\n");
}
void main()
{
unsigned char c[] = {0x02,0x03,0x1b,0xaa}; //前三个字节为转义字节,最后一个为数据代表,然后由它们构成6个字节长度的排列
unsigned char data[6];
for(int i = 0; i < 4; i++)
{
data[0] = c[i];
for(int j = 0; j < 4; j++)
{
data[1] = c[j];
for(int k = 0; k < 4; k++)
{
data[2] = c[k];
for(int l = 0; l < 4; l++)
{
data[3] = c[l];
for(int m = 0; m < 4; m++)
{
data[4] = c[m];
for(int n = 0; n < 4; n++)
{
data[5] = c[n];
test(data, 6);
}
}
}
}
}
}
}