关于MFC CSocket 数据包的分割

25 views
Skip to first unread message

che zhulin

unread,
May 17, 2006, 4:09:40 AM5/17/06
to dev4s...@googlegroups.com
hi,All:
 
由于本人在工作中需要对MFC序列化生成的数据包进行分割,希望各位提提好的建议
 
数据包的格式如下:
 
int    
CString    
BOOL    
CStringList  
CArray(int,int)  
CArray(double,double)
 
本人的初步想法是构建如下分包上下文,记录处理的中间状态:
 
s1   s2   s3   s4  
get_int              
get_string            
    get_string_size        
    get_string_boody        
get_bool              
get_string_list            
    get_list_size        
    get_list_body        
        get_item_n    
            get_string_size
            get_string_body
get_int_array            
    get_array_size        
    get_array_body        
get_double_array            
    get_array_size        
    get_array_body        
 
实现起来比较复杂繁琐,不知各位有没有更好的建议
 
谢谢!
 

BrillyWu

unread,
May 22, 2006, 5:38:19 AM5/22/06
to dev4s...@googlegroups.com
很欣赏你对步骤的划分方式,格式。学了一招。哈哈。
应该没有其他方法吧。除非String,Array用定长的数组实现,直接套上个Struct,就不用解析了。

run...@gmail.com

unread,
May 22, 2006, 10:24:18 AM5/22/06
to 高性能网络编程邮件列表

BrillyWu wrote:
> 很欣赏你对步骤的划分方式,格式。学了一招。哈哈。
> 应该没有其他方法吧。除非String,Array用定长的数组实现,直接套上个Struct,就不用解析了。

完全可以,我就这样做过。利用重载和宏,可让它更简单,可

bool read( bool v ,void* buf,size_t len);
bool read( int v,void* buf,size_t len);
bool read( std::string& v,void* buf,size_t len);
template< typename T>
bool read( std::list< T >& v ,void* buf,size_t len);
template< typename T>
bool read( std::vector< T >& v ,void* buf,size_t len);
template< typename K,typename V>
bool read( std::map< K,V >& v ,void* buf,size_t len );

然后,定义一些宏

#define READ_1( C,T ) bool read( C c , void* buf, size_t len){ read(
c.T, buf,len ); }
#define READ_2( C,T1,T2 ) bool read( C c , void* buf, size_t len){
read( c.T1,buf,len);read( c.T2,buf,len)
...

如果你要读一个结构时,可以如

class Tester
{
public:
int v1;
bool v2;
string v3;
};

READ_3( Tester, v1,v2,v3 );

这样还可嵌套定义。

lin

unread,
May 22, 2006, 12:10:18 PM5/22/06
to 高性能网络编程邮件列表
谢谢两位:

由于我的项目是基于iocp的异步io,我想,只要读多于一个字节的数据,都不要奢望能够一次读全,所以要记录下中间状态,以指导下次有数据到来时的处理

Collins

unread,
May 23, 2006, 5:49:29 AM5/23/06
to 高性能网络编程邮件列表
to lin:

我是这么做的,协议分两层,通讯层协议和应用层协议。你的“要记录下中间状态,以指导下次有数据到来时的处理”在我看来就是要处理半包和粘包的问题,这个我是通过通讯层协议完成的。比如定义通讯层的包格式为:
| proto_flag | data_length | data |
你要有一个用于拼包和拆包的buf,大小通常是最大包size的两倍,当收到数据后先和该buf中的数据append,然后从buf头开始,根据proto_flag,
data_length,
data分析出完整的通讯层数据包,依次进行,可能拆出多个或者0个,最后可能有不完整的剩余数据,那就把这段数据copy到buf的首地址,等待下次收到数据并合并后再处理。上面说的是通讯层协议的工作,对于拆分出来的完整的数据包,应该copy
or ref
给应用层的协议层,应用层的协议格式和你列出来的东东差不多,eg:
| command | parameters |
对于POD来说,应用层协议可以根据command把parameters
memncpy 给你指定的struct or
class变量,对于非POD,它还要处理深拷贝的问题。

所以,我觉得你的问题(似乎要记录很多的中间状态)是由于你没有把协议分层造成的,你的中间状态只存在于通讯层,它是很简单的,一个append_buf,一个pending_data_length就可以解决了,应用层是不存在该问题的。

不知道我是不是理解了你的问题,希望有用吧。

che...@3s.com.cn

unread,
May 23, 2006, 6:40:34 AM5/23/06
to dev4s...@googlegroups.com
to collins:

谢谢您!

您的作法是建立在有通讯层协议支持的基础上的,由于我要读取数据的另外一端的通讯层我是不能动的,牵涉到十几个版本和遍布世界的客户,原先的通讯作的很糟糕,数据包并没有加上分割标志或包的长度等信息,包的长度是不定的,由尤其是CStringList字段,我无法在搞清List长度和每一个String长度的情况下,得知整个StringList数据的长度,(苦啊)

由于无法得知整个包的长度,不得已将整个包的逻辑分析下移到通讯层来做


再次谢谢您!

Collins

unread,
May 23, 2006, 8:53:44 AM5/23/06
to 高性能网络编程邮件列表
to lin:

原来是这样……,那你只好把发来的数据当成一个字节流来处理了。我观察了你的数据结构,如果这6个域是固定的,那么可以用一个N叉树来描述它,什么时候这6个域的数据收齐了,一个完整的数据包就得到了。你的问题是,由于网络的问题,这棵数的任何一个节点的数据都可能是不完整的,所以我建议:用N叉树来管理数据的接收状态,树的每个节点同时保存位于该节点的数据,以及一个表示该节点数据是否完成的bool标识。当收到新数据时,首先遍历这棵树,根据这个bool值定位到第一个等于false的节点,然后把收到的数据和这个节点的数据对接,取足这个节点需要的数据后,再把剩下的数据用于下一节点的处理,依次类推,直到整棵树结束,最后再从头遍历一次,把数据导出到你的目标结构。因为每个结点的数据类型可能不同,可以考虑用boost::any。

good luck

lin

unread,
May 23, 2006, 10:29:51 AM5/23/06
to 高性能网络编程邮件列表
to Collins:

这是个不错的思路!

多谢指教!

che...@3s.com.cn

unread,
May 23, 2006, 9:33:56 PM5/23/06
to dev4s...@googlegroups.com

大家好:

对于通讯用来发送和接收的缓冲区,一般是用缓存池技术管理比较高效,而且提高了系统的稳定性,假如数据包最大为4k,考虑有1k用户的连接的情况,那么程序初始化时,至少需要4K*3*1024为12M,其中4M用于接收数据缓存,8M用于处理包的整理缓存,OK,没有问题
但是,如果不可避免要用到大于1M的数据包(比如传输数据库中整个表),那么用于缓存的数据就是1M*3*1024为3个G了,显然是不现实的,而且绝大多数数据包的大小仅有几十至几百个字节,为每个连接投递3M的缓存,简直是太浪费了,不知大家是如何考虑的,欢迎讨论!

谢谢

Collins

unread,
May 23, 2006, 10:13:56 PM5/23/06
to 高性能网络编程邮件列表
不必每人一个,可以由一个BufferManager统一管理,比如:
class BufferManager : public Singleton<BufferManager>
{
public:
char* GetLittleFreeBuffer();
char* GetMiddleFreeBuffer();
char* GetBigFreeBuffer();
void PutFreeBuffer( int buf_type, const char* buf );
private:
struct LittleBuffer { bool idle; char buf[4*1024]; };
struct MiddleBuffer { bool idle; char buf[16*1024]; };
struct BigBuffer { bool idle; char buf[1024*1024]; };

vector<LittleBuffer> m_littleBuffers;
vector<MiddleBuffer> m_middleBuffers;
vector<BigBuffer> m_bigBuffers;
}
为了提高效率,你可以用3个stack来管理空闲buffer,这样就省去了查询的开销。

Collins

unread,
May 23, 2006, 10:19:41 PM5/23/06
to 高性能网络编程邮件列表
补充一句,由于是多线程环境,所以Get/Put函数实现时要加锁,所以这是一个以时间换空间的作法。

che...@3s.com.cn

unread,
May 23, 2006, 11:50:41 PM5/23/06
to dev4s...@googlegroups.com
to Collins:

您好!

我也有类似的想法,这样当发现一个大包时,必须有一个交换缓冲区的过程了。
如果有粘包,处理就会更复杂,可能限定每次接收的大小来避免粘包的发生,逻辑会简单一点,但是性能损失就会大一点。

谢谢您!

-----Original Message-----
From: dev4s...@googlegroups.com [mailto:dev4s...@googlegroups.com]On
Behalf Of Collins

Sent: 2006年5月24日 10:20
To: 高性能网络编程邮件列表
Subject: Re: 关于通讯的buffer管理


补充一句,由于是多线程环境,所以Get/Put函数实现时要加锁,所以这是一个以时间换空间的作法。

che...@3s.com.cn

unread,
May 24, 2006, 12:02:06 AM5/24/06
to dev4s...@googlegroups.com
to Collins:

您好!

我是用Cycle queue 来管理空闲缓冲区的。

-----Original Message-----
From: dev4s...@googlegroups.com [mailto:dev4s...@googlegroups.com]On
Behalf Of Collins
Sent: 2006年5月24日 10:14
To: 高性能网络编程邮件列表
Subject: Re: 关于通讯的buffer管理

Reply all
Reply to author
Forward
0 new messages