做過網絡或者多媒體應用開發的朋友應該會經常的接觸一些協議(或文件格式,以下統稱協議),特別是那些二進制協議,這些協議不但晦澀難懂,而且編寫處理協議的代碼也很麻煩,我們經常需要對著文檔,一個bit一個bit的去分析我們的處理邏輯,而如果我們對協議的理解有偏差,產生了一些bug,那調試和修改這些bug也是極為費力的(特別是調試別人的或舊的代碼,這種問題更加突出)。
此外,通常處理一種協議的代碼也是專用的,當我們需要增加一種新協議的支持時,我們不得不重寫一套新的代碼,然後反復的測試,調試,解bug。這樣的開發效率是很低的,而且成本也很高。
如果用傳統的編程方法,我們很難避免這些問題,然而如果采用元類編程思想,這些問題就會變得相對簡單得多。讓我們以ts流(視頻傳輸中最常見的一種格式)為例,來看看我們如何使用元類編程思想來處理它,並能帶來哪些優勢。
這裡我不打算(也無法)詳細的介紹ts流協議的細節,有興趣的朋友可以自己上網查閱相關資料,對於某些沒有相關背景知識的朋友,你只需要知道ts流的結構是一種按field組織成的流式結構,每一種field都有特定的大小或結構(由許多子field組成),其中有些fields的內容用於指示之後的fields的類型(即某些fields是否存在或其數量取決於它之前的某些fields)。
類似於我們解決第一個例子問題時的情況,首先我們需要設計一個元類來描述每一種field。為避免我們糾結於ts流協議的細節,下面我直接給出一個定義:
class CBitStream; // 這是一個已實現的處理bit流的類,我們不用關心這個類的具體實現,只需要知道這個類提供了訪問一段buffer中的某個(或某些)bit的方法
typedef CBitStream::CIterator stream_iterator; // CBitStream采用了iterator的設計,同樣,我們不需要知道這個iterator的實現細節,只需要知道它是訪問某個(或某些)bit的接口
struct IProtocolDescriptor; // 如之前所說,有些field是有結構的,這種field我們可以把它看作是一種子協議
struct IFieldDescriptor {
virtual unsigned ID() const = 0; // 為提高調用函數的處理效率,我們需要一個ID來唯一標識這種field
virtual const char * Name() const = 0; // 這個容易理解
virtual bool IsCondition() const = 0; // 指示這種field是否是另一種field的條件,參見ConditionFieldDescriptor
virtual const IFieldDescriptor * ConditionFieldDescriptor() const = 0; // 用來標識這種field是否由另一種field來控制,參見IsCondition
virtual const IProtocolDescriptor * CastToProtocolDescriptor() const = 0; // 參見IProtocolDescriptor的注釋,如果這個轉換能成功,說明這是一種子協議field virtual size_t FieldSize(stream_iterator fieldData) const = 0; // 這種field的大小(單位是bit),如果是變長編碼的field,我們可以根據它的內容計算出來
virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const = 0; // 這種field在bit流中的個數,如果不存在,應當返回0,否則我們可以根據它的條件field的內容和其本身內容計算出來
};
注意上面的定義中IsCondition和ConditionFieldDescriptor用於描述兩種field的依賴關系,而FieldCount則是(運行時)取得field對象個數的元函數。另外,它提到了IProtocolDescriptor接口(以及與這個接口的關系),其定義如下:
struct IProtocolDescriptor {
virtual size_t GetFieldDescriptorCount() const = 0;
virtual const IFieldDescriptor * GetFirstFieldDescriptor() const = 0;
virtual const IFieldDescriptor * GetNextFieldDescriptor() const = 0;
}; // 注意這個接口描述的是一個協議field由多少個子field組成,以及如何訪問這些子field的描述,它與field的對象在bit流中的分布不是一回事。
結合這兩個定義,我們不難發現,我們可以用它描述出一個協議的(樹狀)邏輯結構,甚至,不僅僅是協議,而是任意的樹狀邏輯結構,比如:
struct image {
struct header_t {
short width;
short height;
} image_header; unsigned char image_data[];
};
上面是一個簡化版的位圖格式的數據結構,用我們的IFieldDescriptor和IProtocolDescriptor描述出來會像這樣:
enum image_des_id {
image_des = 1,
image_des_width,
image_des_height,
image_des_header,
image_des_data
};
class CImageHeaderWidthDes: public IFieldDescriptor {
virtual unsigned ID() const { return image_des_width; }
virtual const char * Name() const { return "image_des_width"; }
virtual bool IsCondition() const { return false; }
virtual const IFieldDescriptor * ConditionFieldDescriptor() const { return NULL; }
virtual const IProtocolDescriptor * CastToProtocolDescriptor() const { return NULL; }
virtual size_t FieldSize(stream_iterator fieldData) const { return 16; }
virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const { return 1; }
};
class CImageHeaderHeightDes: public IFieldDescriptor {
...
}; // 請仿造CImageHeaderWidthDes將這個定義補充完整
class CImageHeaderDes : public IFieldDescriptor, public IProtocolDescriptor {
virtual unsigned ID() const { return image_des_header; }
virtual const char * Name() const { return "image_des_header"; }
virtual bool IsCondition() const { return true; }
virtual const IFieldDescriptor * ConditionFieldDescriptor() const { return NULL; }
virtual const IProtocolDescriptor * CastToProtocolDescriptor() const { return this; }
virtual size_t FieldSize(stream_iterator fieldData) const { return 0; } // 我們可以不關心子協議類型的field的大小,至於具體原因,我們將來會再解釋
virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const { return 1; }
virtual size_t GetFieldDescriptorCount() const { return 2; }
virtual const IFieldDescriptor * GetFirstFieldDescriptor() const {
const_cast
return mSubFieldDes[0];
}
virtual const IFieldDescriptor * GetNextFieldDescriptor() const {
++ const_cast
return mSubFieldDes[mSubFieldDesIdx];
}
CImageHeaderWidthDes mWidthDes;
CImageHeaderHeightDes mHeigthDes;
const IFieldDescriptor * mSubFieldDes[2];
int mSubFieldDesIdx;
public:
CImageHeaderDes() {
mSubFieldDes[0] = &mWidthDes;
mSubFieldDes[1] = &mHeigthDes;
mSubFieldDesIdx = 0;
}
};
class CImageDataDes: public IFieldDescriptor {
virtual unsigned ID() const { return image_des_data; }
virtual const char * Name() const { return "image_des_data"; }
virtual bool IsCondition() const { return false; }
virtual const IFieldDescriptor * ConditionFieldDescriptor() const { return mImageHeaderDes; }
virtual const IProtocolDescriptor * CastToProtocolDescriptor() const { return NULL; }
virtual size_t FieldSize(stream_iterator fieldData) const { return 8; }
virtual size_t FieldCount(stream_iterator conditionFieldData, stream_iterator fieldData) const {
short size[2] = {0};
conditionFieldData.CopyBits( 32, &size, sizeof(size) ); // 這個操作從conditionFieldData中拷貝32bits到size中。
return (int) size[0] * (int) size[1] * 4;
}
const IFieldDescriptor * mImageHeaderDes;
public:
CImageDataDes(const IFieldDescriptor * imageHeaderDes) {
mImageHeaderDes = imageHeaderDes;
}
};
class CImageDes: public IFieldDescriptor, public IProtocolDescriptor {
... // 省略接口IFieldDescriptor和IProtocolDescriptor的實現,請仿造CImageHeaderDes自行補充完整
CImageHeaderDes mImageHeaderDes;
CImageDataDes mImageDataDes;
const IFieldDescriptor * mSubFieldDes[2];
int mSubFieldDesIdx;
public:
CImageDes(): mImageDataDes(&mImageHeaderDes) {
mSubFieldDes[0] = &mImageHeaderDes;
mSubFieldDes[1] = &mImageDataDes;
mSubFieldDesIdx = 0;
}
};
從上面的例子中,我們可以清晰的看到元類IFieldDescriptor和IProtocolDescriptor的描述能力,同時這種表達方式也非常的便於我們(根據原始的結構)檢查每一個描述是否是正確的,同時我們也應當看到,這種描述能力是非常靈活的,可以任意的根據我們的需要進行擴展(比如我們最初提出這個設計是為了描述TS協議,但是居然也可以描述位圖!而且我們可以肯定,只要是能用樹形表示的絕大多數邏輯結構,它都能描述!),之後需要考慮的就是如何利用IFieldDescriptor和IProtocolDescriptor描述出的數據結構,去分析bit流,並且輸出一個field的序列。