1 场景
当用socket进行进程通信,传输数据的时候,会出现以下一些情况:
(1)完整的一条消息被系统拆分成几条发送,例如要发送一条消息:Hello World ,却被系统分成两条消息发送,分别为:Hello 和 World。
(2)几条独立的消息被系统合成一条消息发送,例如要发送两条消息分别为:a memory from my past和it’s been a year,却被系统和成一条消息发送:a memory from my pastit’s been a year。
这个时候,需要为socket通信设计一种通信协议,以保证数据的准确性。
2 协议格式
通信协议设计如下:
Head:帧头,2个字节,此处为0xa5a5
Type:通信类型,1个字节,范围0x00~0xff
Data Length:数据长度,1个字节,即Data的字节总数,
Data:实际传输的数据,长度不定
CS:校验值,1个字节,type、data length、data三个域所有字节的异或值,实际中并没用到校验
End:帧尾,2个字节,此处为0xbeef
3 程序设计
3.1 解析思路
假设socket客户端C和服务端S通信,C向S发送消息M1。
1、 S收到消息M1。S把消息M1拷贝到缓存Q中,Q为循环队列。假如M1的长度大于Q的剩余空间,则只拷贝剩余空间大小的字节到Q。
2、 从Q的当前指针开始,查找帧头
。如果找到,则当前指针向后移2个字节位置,继续查找;如果没找到,则删除前1个字节,当前指针向后移1个字节位置,继续查找3、 从Q的当前指针开始,查找。如果Q中至少还剩一个字节,则表示找到,当前指针向后移1个字节位置,否则退出解析。
4、 从Q的当前指针开始,查找。如果Q中至少还剩一个字节,则表示找到,当前指针向后移1个字节位置,否则退出解析。
5、 从Q的当前指针开始,向后移DataLength个字节位置,查找。如果找到,则从Q中取出一条完整的消息P1,并从Q中删除此消息空间,调用外部的回调函数;否则删除帧头的第一个字节a5,当前指针指向帧头第二个字节a5位置,从步骤2开始,重新一轮解析。
3.2 数据结构
查找策略枚举,用于查找时判断查找帧结构的哪个部位:
1 typedef enum{ 2 SEARCH_HEAD, 3 SEARCH_TYPE, 4 SEARCH_LEN, 5 //SEARCH_CS, 6 SEARCH_END, 7 SEARCH_NONE 8 }cache_strategy;
消息结构体,用于存储从缓存中解析出的数据:
typedef struct{ unsigned char data[SOCKET_MSG_SIZE]; //data int len; unsigned char type; }socket_msg;
回调函数,用于从缓存中解析出消息时调用:
typedef void (*tp_socket_msg_handle)(int fd, socket_msg *msg,void *args);
循环队列,用于缓存接收到的数据:
typedef struct{ unsigned char buf[SOCKET_MSG_CACHE_SIZE]; //buffer for storing data read from client int front; int rear; int current; int len; int tag; //mark that whether the cache is full,1-full,0-not full cache_strategy strategy; tp_socket_msg_handle handle;//callback function to invoke when a message is parsed out void* args; //external parameter socket_msg recv_msg; //parsed message }socket_cache;
3.3 关键实现
1、把接收到的数据存储到缓冲中,并准备解析:
//copy the unparsed data to cache, and parsed them int socket_msg_pre_parse( int fd, socket_cache *cache, unsigned char *buf, int len, void *args) { int n = 0; unsigned char *p = buf; //when reading buffer's length is greater than cache's left length, //we should copy many times. cache->args = args; while(1){ n = socket_msg_cpy_in(cache, p, len); if(n == 0){ return FALSE;//cache is full } //parse and handle socket message from cache socket_msg_parse(fd, cache); if(n == len){ return TRUE; //copy completed } //move the pointer p = p + n; len = len - n; } return TRUE; }
2、递归解析消息:
1 //parsed the packaged data, and invoke callback function 2 void socket_msg_parse(int fd, socket_cache *cache) 3 { 4 int current_len; 5 int p, q; 6 int i; 7 int find; 8 9 if(cache->front == cache->rear && cache->tag == 0){ 10 //D("socket cache is empty!\n"); 11 return; 12 } 13 14 //calculate the current length of cache 15 if(cache->current >= cache->front){ 16 current_len = cache->len - (cache->current - cache->front); 17 } 18 else{ 19 current_len = cache->rear - cache->current; 20 } 21 22 switch(cache->strategy){ 23 case SEARCH_HEAD://to find a Head format in cache 24 if(current_len < SOCKET_MSG_HEAD_SIZE){ 25 return; 26 } 27 find = FALSE; 28 for(i = 0; i < current_len - 1; i++){ 29 p = cache->current; 30 q = (cache->current + 1) % SOCKET_MSG_CACHE_SIZE; 31 if( (cache->buf[p] == (SOCKET_MSG_HEAD >> 8))&& 32 (cache->buf[q] == (SOCKET_MSG_HEAD & 0xff))){ 33 34 find = TRUE; 35 break; //exit for loop 36 } 37 else{ 38 //current pointer move to next 39 cache->current = q; 40 //delete one item 41 cache->front = cache->current; 42 cache->len --; 43 cache->tag = 0; 44 } 45 } 46 47 if(find == TRUE){ 48 //move 2 items towards next 49 cache->current = (cache->current + 2) % SOCKET_MSG_CACHE_SIZE; 50 //we found the head format, go on to find Type byte 51 cache->strategy = SEARCH_TYPE; 52 } 53 else{ 54 //if there is no head format ,delete previouse items 55 LOGE("socket message without head: %x!\n",SOCKET_MSG_HEAD); 56 //go on to find Head format 57 cache->strategy = SEARCH_HEAD; 58 } 59 break; 60 61 case SEARCH_TYPE://to find the type byte in cache 62 if(current_len < SOCKET_MSG_TYPE_SIZE){ 63 return ; 64 } 65 //get the value of type 66 //cache->type = cache->buf[cache->current]; 67 cache->recv_msg.type = cache->buf[cache->current]; 68 cache->current = (cache->current + 1) % SOCKET_MSG_CACHE_SIZE; 69 //we found Type byte, go on to find Datalen format 70 cache->strategy = SEARCH_LEN; 71 break; 72 73 case SEARCH_LEN://to find the datalen byte in cache 74 if(current_len < SOCKET_MSG_LEN_SIZE){ 75 return ; 76 } 77 if(cache->buf[cache->current] > SOCKET_MSG_DATA_SIZE){ 78 LOGE("the data len of message out of size: %d!\n",SOCKET_MSG_DATA_SIZE); 79 //delete the frist item 'a5' 80 //move back 2 items 81 cache->current = cache->current >= 2 ? (cache->current - 2) : (SOCKET_MSG_CACHE_SIZE - 2 + cache->current); 82 cache->front = cache->current; 83 //length sub 2 84 cache->len -= 2; 85 cache->tag = 0; 86 //go on to find Head format 87 cache->strategy = SEARCH_HEAD; 88 } 89 else{ 90 //get the value of datalen 91 //cache->data_len = cache->buf[cache->current]; 92 cache->recv_msg.len = cache->buf[cache->current]; 93 cache->current = (cache->current + 1) % SOCKET_MSG_CACHE_SIZE; 94 //we found datalen byte, go on to find End format 95 cache->strategy = SEARCH_END; 96 } 97 break; 98 99 100 101 case SEARCH_END: 102 if(current_len < (cache->recv_msg.len + SOCKET_MSG_END_SIZE)){ 103 return; 104 } 105 //because we have known the data bytes' len, so we move the very 106 //distance of datalen to see if there is End format. 107 p = (cache->current + cache->recv_msg.len) % SOCKET_MSG_CACHE_SIZE; 108 q = (cache->current + cache->recv_msg.len + 1) % SOCKET_MSG_CACHE_SIZE; 109 if( (cache->buf[p] == (SOCKET_MSG_END >> 8))&& 110 (cache->buf[q] == (SOCKET_MSG_END & 0xff)) ){ 111 socket_msg_cpy_out(cache, cache->recv_msg.data, cache->current, cache->recv_msg.len); 112 if(cache->handle != NULL){ 113 //cache->handle(fd, cache->buf + cache->data_index, cache->data_len); 114 cache->handle(fd, &cache->recv_msg, cache->args); 115 } 116 //delete all previous items 117 cache->current = (q + 1) % SOCKET_MSG_CACHE_SIZE; 118 cache->front = cache->current; 119 cache->len -= (cache->recv_msg.len + SOCKET_MSG_FORMAT_SIZE); 120 cache->tag =0; 121 122 } 123 else{ 124 LOGE("socket message without end: %x!\n",SOCKET_MSG_END); 125 //delete the frist item 'a5' 126 //move back 3 items 127 cache->current = cache->current >= 3 ? (cache->current - 3) : (SOCKET_MSG_CACHE_SIZE - 3 + cache->current); 128 cache->front = cache->current; 129 //length sub 3 130 cache->len -= 3; 131 cache->tag = 0; 132 133 } 134 //go on to find Head format 135 cache->strategy = SEARCH_HEAD; 136 break; 137 138 default: 139 break; 140 141 142 } 143 144 //parse new socket message 145 socket_msg_parse(fd,cache); 146 }
原文:http://blog.csdn.net/xnwyd/article/details/7668742