逐步实现TCP服务端Step03-7:封装Codec类

Codec提供了一套Encode/Decode方法供MessageBody使用。MessageHead类并未使用Codec提供的服务,而是内置了Encode/Decode方法。消息头与消息体不同,消息头是公共部分,不会轻易改变,消息体则与具体的业务相关,结构不固定,应把他俩区别处理。

对于消息体的多样性,可以通过重载’<<‘和’>>’运算符来实现Encode/Decode,编解码时直接在运算符后面追加字段就行了。

属性

  • buf_size_:用于编码或解码的buffer的大小
  • buf_:buffer首地址,编码时,这个buffer用于存放编码后的结果。解码时,用于提供数据,供解码方法取用。
  • cur_:它指向了缓冲区当前被操作的位置。
  • actual_encode_len_:用于向调用者返回实际完成编码的字节数。
  • is_last_encode_success_:记录上次编码是否成功。若上次编码失败,此后将不允许进行编码操作。
  • is_last_decode_success_:记录上次解码是否成功。若上次解码失败,此后将不允许进行解码操作。

方法,图中’#’表示protected

  • SetLastEncodeFailed:仅将is_last_encode_success_置为false 。
  • SetLastDecodeFailed:与SetLastEncodeFailed类似。
  • PrepareEncode:准备Encode所需的东西,调用者需要给出缓冲区及其长度。在编码之前,必须先调用这个方法。
  • PrepareDecode:与PrepareEncode类似。
  • FinishEncode:做些收尾工作,只有读操作,在这里会确定最终Encode成功的字节数。
  • FinishDecode:与FinishEncode类似。
  • GetProcessedBufSize:查询已处理的缓冲大小。对于编码过程,就是已经编出了多少个字节放在了buffer中。对于解码过程,就是已经从buffer中读出了多少字节。这两个过程统称为“Process”。
  • IsCanEncode/IsCanDecode:不论编码还是解码工作,已经Process的buffer长度加上准备要处理的目标的长度,都不能超过buffer的总长度。若超过了buffer的总长度,则编出的字节没地儿放,要解码的字节读不全。
  • 编解码方法:剩下一组编解码方法跟MessageHead中的Encode/Decode方法类似,只不过重载了运算符而已。在解码方法中,有个地方要说一下,以下面代码为例:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    Codec& Codec::operator>>(short& dst)
    {
        if (IsCanDecode(sizeof(dst))) {
            unsigned short tmp_dst;
            memcpy(&tmp_dst, cur_, sizeof(tmp_dst));
            dst = ntohs(tmp_dst);
            cur_ += sizeof(dst);
        }
        return *this;
    }
    
    Codec& Codec::operator>>(unsigned short& dst)
    {
        *this >> (*(short*)(&dst)); 
        return *this;
    }
    

这两个函数的区别是,第一个是对有符号的short型变量做操作,第二个是对无符号。第二个函数没必要将第一个函数中的内容再实现一遍,只要调用函数一即可。唯一要做的工作就是把dst参数由unsigned short转成short型。

注意,两个函数的参数是引用类型。假设传入的实参名叫“abc”,那么,相当于做了如下的操作:

1
unsigned short& dst = abc;

即,将dst声明为abc的引用,或者说别名,总之dst与abc对应的内存地址是相同的。上面的操作,要求abc必须是非const型的左值。下面的用法,在编译时会报错:

1
2
3
const int a = 10;   
int& ra1 = a;  
int& ra2 = 20;

也就是说,我们在将dst由unsigned short转为short时,必须要保证转换的结果是一个非const型左值。const不用考虑。若使用强制转换,编译时将报与上面同样的错误。因为,强制转换,相当于要求对目标数据强制使用指定类型进行解读。转换的结果是一个表面值,非左值。

使用如下的方式是正确的,它的最后一步运算是’*’运算,对一个地址进行了解引用,得到的是左值,而非表面值。

1
*this >> (*(short*)(&dst));

详细代码:

<==  index  ==>