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問題還很多,特別解析緩衝區數據的時候,比如根據頭來獲取數據區結束位置。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章