Linux/Windows C/C++ TCP沾包处理方法源码 可定制化头信息 (阻塞和非阻塞 稍改即通用)

本代码是使用在Linux Epoll机制下的,通过socket fd标识建立链表和对应的缓冲区。

tpack.h

#ifndef      TPACK_H_
#define      TPACK_H_

#include "define.h"

//  通信包结构
#define     TPACK_HEAD_SIZE    512				//	头信息先设置最大
#define     TPACK_HEAD_DATA_SIZE	64			//	标准大小
#define     TPACK_DATA_SIZE    8192
#define     TPACK_SIZE         8192 + 512
#define     TPACK_HEAD_LINE_TAG "\r\n"
#define     TPACK_HEAD_TAG      "\r\n106258\r\n"  //  头结束符
#define     TPACK_HEAD_TAG_SIZE strlen(TPACK_HEAD_TAG)
#define     TPACK_DATA_TAG      "\r\n4641\r\n"  //  数据结束符
#define     TPACK_DATA_TAG_SIZE strlen(TPACK_DATA_TAG)

//	接收包的缓冲区建议大小
#define     TPACK_CACHE_SIZE	TPACK_SIZE * 2	//	包的缓冲区建议长度为 一个包的两倍大小

//  包命令
typedef     unsigned int    TPackCmd;
#define     TPACK_CMD_EMPTY         0x1 //  空命令,做keep
#define     TPACK_CMD_SEND          0x2     //  T端数据传到Client
#define     TPACK_CMD_T_CLOSE       0x4     //  target要断开了
#define     TPACK_CMD_T_T_CLOSE     0x8     //  target下的目标以关闭,S判断后关闭,指向的socket客户端
#define     TPACK_CMD_T_LOGIN       0x10    //  target请求登陆,验证成功后返回user_token 和 task列表
#define     TPACK_CMD_5      0x20
#define     TPACK_CMD_6      0x40
#define     TPACK_CMD_7      0x80

//  包Form
#define     TPACK_FORM_SERVER   "Server"
#define     TPACK_FORM_CLIENT   "Client"
#define     TPACK_FORM_TARGET   "Target"

//	包格式
typedef struct ST_TPACK_INFO
{
    TPackCmd    cmd;
	char	    form[TPACK_HEAD_DATA_SIZE];
	uint		sock_cli;	//	服务器传回的客户端sock标识
	uint		h_data_len;

	char*	phead;
	uint		phead_len;
    
	char*	pdata;
	uint		pdata_len;

	char*	pstr;		//	序列化后的数据
	uint	pstr_len;
} *TPack;

TPack tpack_new( TPackCmd cmd, char* form, uint sock, char* data, size_t size);

void tpack_update_head(TPack tpack);

char* tpack_tostr(TPack tpack);

void tpack_free(TPack tpack);

TPack tpack_parse(char* str, uint str_len, char **end);

#endif

 

tpack.c

#include "stdafx.h"
#include "com.h"
#include "tpack.h"

/**
*   通信装包和拆包
*/

//  创建包,目前是直接申请最大空间,后期应该需要先创建头、创建数据 再合成包
TPack tpack_new( TPackCmd cmd, char* form, uint sock, char* data, size_t size){
	TPack tpack = (TPack) malloc( sizeof(struct ST_TPACK_INFO) );
	memset( tpack, 0, sizeof(struct ST_TPACK_INFO) );
    tpack->cmd = cmd;
    strncpy( tpack->form, form, strlen(form) );
    tpack->sock_cli = sock;
    
	//	为头和数据区申请空间标准大小空间
	tpack->phead = (char *) malloc( TPACK_HEAD_SIZE );
	memset( tpack->phead, 0, TPACK_HEAD_SIZE );
	tpack->pdata = (char *) malloc( TPACK_DATA_SIZE );
	memset( tpack->pdata, 0, TPACK_DATA_SIZE );

	memcpy( tpack->pdata, data, size);
	tpack->pdata_len = strlen( tpack->pdata );

	tpack_update_head(tpack);
    
    return tpack;
}
//	更新头信息,不含头结束符
void tpack_update_head(TPack tpack)
{
    sprintf( tpack->phead, "Command: %d%sFrom: %s%sSocket-Clint: %d%sData-Size: %d", 
                tpack->cmd, TPACK_HEAD_LINE_TAG,  
                tpack->form, TPACK_HEAD_LINE_TAG, 
                tpack->sock_cli, TPACK_HEAD_LINE_TAG,
				tpack->pdata_len);
	tpack->phead_len = strlen( tpack->phead );
	return;
}
//	释放 TPack
char* tpack_tostr(TPack tpack)
{
	tpack_update_head(tpack);

	tpack->pstr = (char *) malloc( TPACK_SIZE );
	memset( tpack->pstr, 0, TPACK_SIZE );

	//	marger, 使用strlen计算长度
	memcpy( tpack->pstr, tpack->phead, tpack->phead_len );
	memcpy( tpack->pstr + strlen(tpack->pstr), TPACK_HEAD_TAG, TPACK_HEAD_TAG_SIZE );
	memcpy( tpack->pstr + strlen(tpack->pstr), tpack->pdata, tpack->pdata_len );
	memcpy( tpack->pstr + strlen(tpack->pstr), TPACK_DATA_TAG, TPACK_DATA_TAG_SIZE );

	tpack->pstr_len = tpack->phead_len + TPACK_HEAD_TAG_SIZE + tpack->pdata_len + TPACK_DATA_TAG_SIZE;

    return tpack->pstr;
}

void tpack_free(TPack tpack){
	if( tpack == NULL ) return;
    
	if( tpack->phead != NULL ) free( tpack->phead );
	if( tpack->pdata != NULL ) free( tpack->pdata );
	if( tpack->pstr != NULL ) free( tpack->pstr );
    
	memset( tpack, 0, sizeof(struct ST_TPACK_INFO));
    free( tpack );
}
//	在head字符串中查找对应数据
char *tpack_head_get_val(char* str, int str_len, char* key, char *val, int val_size)
{
    memset( val, 0, val_size );
	char* str_end = str + str_len;
    char* key_start = memstr( str, str_len, key);
	if( key_start == NULL ){
		return NULL;
	}

	char* end = memstr( key_start, ( str_end - key_start ), TPACK_HEAD_LINE_TAG);
	if( end == NULL ){  //  必须找到行尾
		return NULL;
	}

    char* key_start_i = key_start + strlen(key) + 1;    //  开始字符串位置
    if( (end - key_start_i) < val_size )
        val_size = end - key_start_i;

	strncpy( val, key_start_i, val_size );
    val = trim( val );
    //printf("key_start and end exist, val_size:%d\n", val_size);
	return val;
}
void tpack_parse_head(TPack tpack){
	char headVal[TPACK_HEAD_DATA_SIZE];
	memset( headVal, 0, TPACK_HEAD_DATA_SIZE );

	tpack_head_get_val( tpack->phead, tpack->phead_len, "Form", headVal, TPACK_HEAD_DATA_SIZE);
	memcpy( tpack->form, headVal, strlen(headVal) );
	
	memset( headVal, 0, TPACK_HEAD_DATA_SIZE );
	tpack_head_get_val( tpack->phead, tpack->phead_len, "Socket-Clint", headVal, TPACK_HEAD_DATA_SIZE);
	tpack->sock_cli = atoi( headVal );
	
	memset( headVal, 0, TPACK_HEAD_DATA_SIZE );
	tpack_head_get_val( tpack->phead, tpack->phead_len, "Data-Size", headVal, TPACK_HEAD_DATA_SIZE);
	tpack->h_data_len = atoi( headVal );

	return;
}
//	如果找到返回
TPack tpack_parse(char* str, uint str_len, char **end)
{
	//	通过字符串找到结束符
	char* str_end = str + str_len;	//	数据的结尾
	char* head_start = str;
	char* data_start = str;
    char* head_tag = memstr( str, str_len, TPACK_HEAD_TAG );	//	结束符标记
	char* data_tag = memstr( str, str_len, TPACK_DATA_TAG );	//	结束符标记

	if( head_tag == NULL ){
		printf("TPACK unpack failed,head_tag is NULL\n");
		return NULL;
	}	
	else if( data_tag == NULL ){
		printf("TPACK unpack failed,data_tag is NULL\n");
		return NULL;
	}
	else if( data_tag < head_tag ) {
		//	原因1:留有未处理的data_tag
        printf("TPACK unpack failed,data_tag < head_tag error, 留有未处理的data_tag\n");
        //	设置 head_start 去掉前残余data的位置
		head_start = data_tag + TPACK_DATA_TAG_SIZE;
		//	重新在 str 搜索 data_tag 位置, head-start 需要str
		data_tag = memstr( head_start, ( str_end - head_start), TPACK_DATA_TAG );
		if( data_tag == NULL )
			printf("TPACK unpack failed,data_tag is NULL (2) \n");
			return NULL;
	}
	data_start = head_tag + TPACK_HEAD_TAG_SIZE;

	//	初始化一个空TPack信息
	TPack Tpack = tpack_new( TPACK_CMD_EMPTY, "", 0, "", 0 );

	//	找到的 头 和 内容区, 两指针想减即是之间字节大小
	Tpack->phead_len = head_tag - str;
	Tpack->pdata_len = data_tag - data_start;
	memcpy( Tpack->phead, head_start, Tpack->phead_len );
	memcpy( Tpack->pdata, data_start, Tpack->pdata_len );

	//	解析头
	tpack_parse_head( Tpack );

	*end = data_tag + TPACK_DATA_TAG_SIZE;	//	返回结束的标记位置
	
	return Tpack;
}

 

send发包

// TPACK::装包
        TPack tpack = tpack_new( TPACK_CMD_SEND, TPACK_FORM_SERVER, sock, recv_buf, recvSizeOk );
        tpack_tostr( tpack );
        
            //发送tpack->pstr
        tpack->pstr, tpack->pstr_len
        
        tpack_free( tpack );

 

recv接收处理,循环接收到缓冲区内,再尝试解包,不能解析就继续接收,直到解包成功或者超过缓冲区大小,

超过缓冲区大小时,把能放的最大内存追加到缓冲区中,如果还不能解析就时垃圾数据,直接关闭。

// TPACK::解包
    char* tpack_data_end;   //  解析成功后,结尾数据位置,结束符在内
    TPack tpack = tpack_parse( link->recvCache, link->recvLen, &tpack_data_end );
    if( tpack == NULL ){    //  拆包失败,继续接收
        printf("target_in_thr: tpack parse failed, wait next recv\n");
        
        set_event_et( link->epoll_fd, EPOLLIN, link->sock );
        return NULL;
    }


// TPACK::缓冲区向前移动, 如果尾部相等,直接清空
    int move_len = (link->recvCache + link->recvLen) - tpack_data_end;
    if( move_len == 0 ){
        memset( link->recvCache, 0, TPACK_CACHE_SIZE );
        link->recvLen = 0;
        printf("target_in_thr: recvCache memset\n");
    }
    else {
        int move_size = tpack_data_end - link->recvCache;   //  结束符减去指针就是长度
        memmove( link->recvCache, tpack_data_end, move_size );
        link->recvLen = move_size;
        printf("target_in_thr: recvCache move forward, recvCache: %p, data_end: %p, move size: %d\n", link->recvCache, tpack_data_end, move_size);
    }

 

这是我解决的一种方式,希望得到优化意见。

其中包括tpack问题还很多,特别解析缓冲区数据的时候,比如根据头来获取数据区结束位置。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值