您的位置:首页 > 理论基础 > 计算机网络

淘宝开源网络框架tbnet之ipacketstreamer,ipackethandler以及iserveradaper

2014-01-27 16:46 295 查看
在上篇博文中,我们讨论了iocomponent这部分,个人认为在tbnet中在对网络数据包输入输出的处理上,封装的很不错的,对于那些想要做一些库开发方面的人有一定的借鉴作用,今天我们就来看看tbnet库中的其他几个类,主要是针对数据包的处理方面的,下面我们就来先看看tbnet库的ipacketstreamer这个类,这个类的作用根据网上的说法就是用于底层缓冲区与上次的数据包的交换处理类,个人还是比较认可这种说法的,下面先来看看其代码实现吧,代码如下:

class IPacketStreamer {

public:
/*
* ¹¹Ôì
*/
IPacketStreamer() {
_factory = NULL;
_existPacketHeader = true;
}

/*
* ¹¹Ôì
*/
IPacketStreamer(IPacketFactory *factory) {
_factory = factory;
_existPacketHeader = true;
}

/*
* Îö¹¹º
*/
virtual ~IPacketStreamer() {}

/*
* µÃµ½°üÍ·
*
* @param input  Ô´buffe
* @param header ½á¹ûhea
* @return ÊÇ·ñ³É
*/
virtual bool getPacketInfo(DataBuffer *input, PacketHeader *header, bool *broken) = 0;

/*
* ¶Ô°üµÄ½âÂë
*
* @param input
* @param header
* @return ½âÂëºóµÄÊý¾Ý°ü
*/
virtual Packet *decode(DataBuffer *input, PacketHeader *header) = 0;

/*
* ¶ÔPacketµÄ×é×°
*
* @param packet Êý¾Ý°ü
* @param output ×é×°ºóµÄÊý¾ÝÁ÷
* @return ÊÇ·ñ³É¹¦
*/
virtual bool encode(Packet *packet, DataBuffer *output) = 0;

/*
* ÊÇ·ñÓÐÊý¾Ý°üÍ·
*/
bool existPacketHeader() {
return _existPacketHeader;
}

protected:
IPacketFactory *_factory;   // ²úÉúpacket
bool _existPacketHeader;    // ÊÇ·ñÓÐpacket header, ÈçhttpÓÐ×Ô¼ºÐ­Òé¾Í²»ÐèÒªÊä³öÍ·ÐÅÏ¢
};

在这个类中,首先来看看其成员变量,里面有个IPacketFactory对象,这个对象从名字上就能看出它的作用了,其实准确来讲它就是一个造数据包的工厂(使用了工厂模式),而具体的流程是在另外的两个函数中处理的,decode和encode,而这两个函数是虚函数,因而具体的实现需要看其子类,幸好在tbnet中已经实现了一个默认的数据包转换类(defaultPacketstreamer),接下来首先来看看在defaultpacketstreamer类中怎么造数据包(decode函数的实现过程),然后再看看encode,代码
如下:

Packet *DefaultPacketStreamer::decode(DataBuffer *input, PacketHeader *header) {
assert(_factory != NULL);
Packet *packet = _factory->createPacket(header->_pcode);
if (packet != NULL) {
if (!packet->decode(input, header)) {
packet->free();
packet = NULL;
}
} else {
input->drainData(header->_dataLen);
}
return packet;
}

从代码中可以看出,defaultPacketstreamer类中的decode最终会调用packet类中的decode函数,在之前其会完成根据包头的类型信息来构造一个数据包,至于解码过程则在具体的packet中,在上述这个函数中对于那些无法解析的包,会直接丢弃调用的函数是drainData,从这里面可以看出抽象工厂类其实只是完成了“铺路”的作用,而具体地操作在每个实现的子类中,这种方式也可以学学,接下来我们再来看看encode的这个函数,其作用就是用于将上层的数据包转换为二进制缓冲区里的内容,代码如下:

bool DefaultPacketStreamer::encode(Packet *packet, DataBuffer *output) {
PacketHeader *header = packet->getPacketHeader();

// ΪÁ˵±encodeʧ°Ü»Ö¸
int oldLen = output->getDataLen();
// dataLenµÄλÖÃ
int dataLenOffset = -1;
int headerSize = 0;

// ÔÊÐí´æÔÚÍ·ÐÅÏ¢,д
if (_existPacketHeader) {
output->writeInt32(DefaultPacketStreamer::_nPacketFlag);
output->writeInt32(header->_chid);
output->writeInt32(header->_pcode);
dataLenOffset = output->getDataLen();
output->writeInt32(0);
headerSize = 4 * sizeof(int);
}
// дÊý¾Ý
if (packet->encode(output) == false) {
TBSYS_LOG(ERROR, "encode error");
output->stripData(output->getDataLen() - oldLen);
return false;
}
// ¼ÆËã°ü³¤¶È
header->_dataLen = output->getDataLen() - oldLen - headerSize;
// ×îÖհѳ¤¶È»Øµ½bufferÖÐ
if (dataLenOffset >= 0) {
unsigned char *ptr = (unsigned char *)(output->getData() + dataLenOffset);
output->fillInt32(ptr, header->_dataLen);
}

return true;
}

从上述代码中,我们可以看到在tbnet中如何将数据包转换为二进制内容,首先通过调用写字节的形式将数据包包头内容写入output缓存区中,接着又调用packet的encode函数来完成对包体的二进制写入,在这段代码中使用了一个很不错的小技巧,首先为整个packet保留存放长度的字段,然后等到包体内容二进制编码完毕,然后 再来 统计整个packet的长度,并通过调用fillInt32函数来完成填充操作,个人感觉很不错,因为在对packet进行二进制编码时,其长度可能会改变,所以要统计整个packet的长度时,只能首先为长度字段保留空间,然后等到编码结束,再写入真正的长度信息,至于对packet的二进制编码则完全放在了每个packet对应的encode函数中,tbnet库只是提供了一个框架而已,通过分析ipacketstreamer类,我们可以从中学到不少好用的东西,最重要的一点就是如何将抽象的概念与具体的实现分离开来,从而减少模块之间的耦合度,并且代码维护成本也会降到最低,所以每次当需要使用数据包的编解码时,只需要为packet指定相应的ipacketstreamer即可,至于具体的实例,大家可以参考test目录下的一些案例,在此不再多说了,

2. ipacketHandler

上面已经对ipacketstreamer进行了分析,ipacketstreamer只是完成了packet与缓冲区之间的转化,而对于一些接收到的消息时,我们需要对此进行处理,换句话说等到ipacketstreamer处理完接收到的消息时(decode),我们需要调用相关的函数来处理,这时ipacketHandler就起到作用了,ipacketHandler的主要用途就是用于实现对数据包的相关处理过程,代码如下:

class IPacketHandler {
public:
enum HPRetCode {
KEEP_CHANNEL  = 0,
CLOSE_CHANNEL = 1,
FREE_CHANNEL  = 2
};

virtual ~IPacketHandler() {}
virtual HPRetCode handlePacket(Packet *packet, void *args) = 0;
};

在这个类中,其使用了虚函数的形式来确保在实际处理packet的多样性,这个抽象类即为所有数据包的处理类的父类,我们在真正处理packet的时候,会自行定义其实现,下面我们就来看看test目下的echoclient文件中的clientEchoPacketHandler类,代码如下:

class ClientEchoPacketHandler : public IPacketHandler
{
public:
ClientEchoPacketHandler() {_recvlen = 0; _timeoutCount = 0;
atomic_set(&_count, 0);}
HPRetCode handlePacket(Packet *packet, void *args)
{
ClientEchoPacket *echoPacket = (ClientEchoPacket*)args;
atomic_inc(&_count);
if (!packet->isRegularPacket()) { // ÊÇ·ñÕý³£µÄ
TBSYS_LOG(ERROR, "INDEX: %d => ControlPacket: %d", echoPacket->getIndex(), ((ControlPacket*)packet)->g
_timeoutCount ++;
if (_count.counter == gsendcount) {
transport.stop();
}
delete echoPacket;
return IPacketHandler::FREE_CHANNEL;
}
_recvlen += ((ClientEchoPacket*)packet)->getRecvLen();
//int index = (int)args;
if (_count.counter == gsendcount) {
TBSYS_LOG(INFO, "INDEX: %d OK=>_count: %d gsendlen: %lld==%lld, _timeoutCount: %d", echoPacket->getInd
transport.stop();
} else {
TBSYS_LOG(INFO, "INDEX: %d _count: %d gsendlen: %lld==%lld, _timeoutCount: %d", echoPacket->getIndex()
}
delete echoPacket;
delete packet;
return IPacketHandler::FREE_CHANNEL;
}
private:
atomic_t _count;
int64_t _recvlen;
int _timeoutCount;
};

该类是ipacketHandler的继承子类,并且实现了handlePacket方法,针对接收到的数据包进行一些处理,在整个框架中,所有与包处理有关的操作都是通过传递函数句柄的方式来实现的,这块内容,后面博文中,我们会专门讨论,在此只需要只知道就可以了,接下来吗,我们再来看看iserverAdapter,这个类其实就是一个数据包处理的适配器,想必接触过设计模式的读者,应该会知道适配器是用来做什么的,先来看看代码吧,代码如下:

class IServerAdapter {
friend class Connection;
friend class TCPConnection;
public:
// µ¥¸öpacket»Ø
virtual IPacketHandler::HPRetCode handlePacket(Connection *connection, Packet *packet) = 0;
// ÅúÁ¿packet»
virtual bool handleBatchPacket(Connection *connection, PacketQueue &packetQueue) {
UNUSED(packetQueue);
UNUSED(connection);
return false;
}
// ¹¹Ôì
IServerAdapter() {
_batchPushPacket = false;
}
// Îö¹¹º
virtual ~IServerAdapter() {}
// setBatch()
void setBatchPushPacket(bool value) {
_batchPushPacket = value;
}
private:
bool _batchPushPacket;          // ÅúÁ¿post pack
};

从代码中可以很清楚地看出来,该函数的作用就是用于实现对不同的数据包进行不同的处理,一种是每个数据包都是单独处理,第二种就是采用批处理的方式,而唯一界定这两种方式的标志就是_batchPushPacket,在test目录下的实例中使用到了,代码如下:

class EchoServerAdapter : public IServerAdapter
{
public:
IPacketHandler::HPRetCode handlePacket(Connection *connection, Packet *packet)
{
EchoPacket *reply = new EchoPacket();
reply->setString(((EchoPacket*)packet)->getString());
reply->setChannelId(packet->getChannelId());
if (connection->postPacket(reply) == false) {
reply->free();
}
packet->free();
return IPacketHandler::FREE_CHANNEL;
}
};

从这部分代码中可以看出,在echo功能里面,主要是一应一答的方式,所以EchoServerAdapter只是实现了handlePacket函数,一般来说根据不同的功能选择实现不同的函数,当采用tbnet库作为自己开发应用的网络基础库时,如果有需要批量处理数据包的模块,则需要通过继承iserverAdapter类来实现批处理函数,在此就不多说了,当真正需要的时候,可以参考下test目录下的实例即可。

总结

我写这篇博文的目的其实只有一个:就是学会封装和抽象,在tbnet中针对不同的packet的处理方式采用了抽象工厂的方式,这对不同的包分别实现不同的处理流程,并且为了屏蔽掉不同的流程之间的差异,在数据包转换时又使用了适配器模式,这些模式其实在我们的开发过程中使用的是极为广泛的,并且这些模式思想也是很简单的,如果有时间的话,也希望自己能够好好地学学一些比较通用的设计模式,好了,本篇博文到此结束,谢谢大家,接下来的我们将会讨论tbnet库中的buffer这部分,这部分很重要也很基础,谢谢了

如果需要,请注明转载,多谢
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: