基于socket简单通信协议实现(c/c++)

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的当前指针开始,查找帧头<Head>。如果找到,则当前指针向后移2个字节位置,继续查找<Type>;如果没找到,则删除前1个字节,当前指针向后移1个字节位置,继续查找<Head> 

3、  从Q的当前指针开始,查找<Type>。如果Q中至少还剩一个字节,则表示找到,当前指针向后移1个字节位置,否则退出解析。 

4、  从Q的当前指针开始,查找<DataLength>。如果Q中至少还剩一个字节,则表示找到,当前指针向后移1个字节位置,否则退出解析。 

5、  从Q的当前指针开始,向后移DataLength个字节位置,查找<End>。如果找到,则从Q中取出一条完整的消息P1,并从Q中删除此消息空间,调用外部的回调函数;否则删除帧头的第一个字节a5,当前指针指向帧头第二个字节a5位置,从步骤2开始,重新一轮解析。 



3.2    数据结构 

查找策略枚举,用于查找时判断查找帧结构的哪个部位: 
C代码   收藏代码
  1. typedef enum{  
  2.     SEARCH_HEAD,  
  3.     SEARCH_TYPE,  
  4.     SEARCH_LEN,  
  5.     //SEARCH_CS,  
  6.     SEARCH_END,  
  7.     SEARCH_NONE  
  8. }cache_strategy;  

消息结构体,用于存储从缓存中解析出的数据: 
C代码   收藏代码
  1. typedef struct{  
  2.     unsigned char data[SOCKET_MSG_SIZE];            //data  
  3.     int len;  
  4.     unsigned char type;  
  5. }socket_msg;  

回调函数,用于从缓存中解析出消息时调用: 
C代码   收藏代码
  1. typedef void (*tp_socket_msg_handle)(int fd, socket_msg *msg,void *args);  

循环队列,用于缓存接收到的数据: 
C代码   收藏代码
  1. typedef struct{  
  2.     unsigned char buf[SOCKET_MSG_CACHE_SIZE]; //buffer for storing data read from client  
  3.     int front;  
  4.     int rear;  
  5.     int current;      
  6.     int len;  
  7.     int tag;            //mark that whether the cache is full,1-full,0-not full  
  8.     cache_strategy strategy;  
  9.     tp_socket_msg_handle handle;//callback function to invoke when a message is parsed out        
  10.     void* args;                                 //external parameter  
  11.     socket_msg recv_msg;                            //parsed message  
  12.       
  13. }socket_cache;  

3.3 关键实现 
1、把接收到的数据存储到缓冲中,并准备解析: 
C代码   收藏代码
  1. //copy the unparsed data to cache, and parsed them  
  2. int socket_msg_pre_parse(  
  3. int fd,   
  4. socket_cache *cache,  
  5. unsigned char *buf,   
  6. int len,   
  7. void *args)  
  8. {  
  9.     int n = 0;  
  10.     unsigned char *p = buf;  
  11.     //when reading buffer's length is greater than cache's left length,  
  12.     //we should copy many times.  
  13.     cache->args = args;  
  14.     while(1){  
  15.         n = socket_msg_cpy_in(cache, p, len);  
  16.         if(n == 0){  
  17.             return FALSE;//cache is full      
  18.         }  
  19.         //parse and handle socket message from cache  
  20.         socket_msg_parse(fd, cache);  
  21.           
  22.         if(n == len){  
  23.             return TRUE; //copy completed  
  24.         }  
  25.         //move the pointer  
  26.         p   = p + n;  
  27.         len = len - n;  
  28.     }  
  29.       
  30.     return TRUE;  
  31.           
  32. }  


2、递归解析消息: 
C代码   收藏代码
  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. }  
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
实验内容 根据自定义的协议规范,使用 Socket 编程接口编写基本的网络应用软件。 掌握 C 语言形式的 Socket 编程接口用法,能够正确发送和接收网络数据包。 开发一个客户端,实现人机交互界面和与服务器的通信。 开发一个服务端,实现并发处理多个客户端的请求。 程序界面不做要求,使用命令行或最简单的窗体即可。 功能要求如下: 运输层协议采用 TCP 客户端采用交互菜单形式,用户可以选择以下功能: a) 连接:请求连接到指定地址和端口的服务端。 b) 断开连接:断开与服务端的连接。 c)获取时间: 请求服务端给出当前时间。 d)获取名字:请求服务端给出其机器的名称。 e)活动连接列表:请求服务端给出当前连接的所有客户端信息(编号、IP 地址、端口等) f)发消息:请求服务端把消息转发给对应编号的客户端,该客户端收到后显示在屏幕上 g) 退出:断开连接并退出客户端程序 3.服务端接收到客户端请求后,根据客户端传过来的指令完成特定任务: a)向客户端传送服务端所在机器的当前时间。 b)向客户端传送服务端所在机器的名称。 c)向客户端传送当前连接的所有客户端信息。 d)将某客户端发送过来的内容转发给指定编号的其他客户端。 e)采用异步多线程编程模式,正确处理多个客户端同时连接,同时发送消息的情况。 根据上述功能要求,设计一个客户端和服务端之间的应用通信协议。 本实验涉及到网络数据包发送部分不能使用任何的 Socket 封装类,只能使用最底层的 C 语言形式的 Socket API。 本实验可组成小组,服务端和客户端可由不同人来完成。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值