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