数据传输的协议

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
简介:

1数据协议

1TCP,websocket,http这些是属于底层的传输协议。保障服务器和客户端可以收发数据。


2假设接收到了数据之后,有什么用呢,比如服务器需要知道客户端发来的数据是干嘛的,

所以就需要用到数据协议。 也就是客户端和服务器商量好一种数据协议.

根据这种自定义的协议收发数据, 服务器就能听懂 客户端的协议了。


3比如说登陆协议,用户名密码啊这些.  这就是上层的协议。


4游戏数据协议每一个数据包都不能过大, 比如64k,如果超出可以在发送的

时候,把这些数据进行分包. 这样做可以防止恶意攻击,


 


6分包协议:

第一种模式:包头+包体模式   

第二种模式:\r\n为结束符号的模式;



命令组成的协议: 比如一个登陆协议

发送:

命令主类型号:用户登陆的命令,用户注册的命令

命令子类型号   用户名    密码


返回:

命令类型号,命令子类型号,返回码, 为多少就返回用户的数据 






2二进制数据协议

1 二进制协议原理:

    直接将内存里的对象保存为二进制数据,然后通过封包(size+(二进制数据))

的方式发送出去,  解包的时候,读取size,然后读取二进制数据,再根据二进制的

结构体描述文件来解开这个包,获取每个数据成员的数据.


2设计原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//协议的结构体
struct  person{
     int  main_type.
     int  sub_type,
     char * name,
     int  uid,
     int  age,
     int  sex,
     ...
}
 
//首先将这个结构体里面的每个数据成员对应的值写入到内存,然后一个封包好的地址
//序列化二进制 打包
unsigned  char * pack_person(结构体)
 
然后进行封包 [包头 size ]+[包体]
 
//反序列号 解包
struct  person* unpack_person(unsigned  char * data, int  len);

3二进制协议的设计优点:

     体积小,传输的性能好,高效



4二进制协议的缺点:

    有一些语言的支持不好,比如javascript,脚本语言去解析内存,

本身需要c/c++的支持,然后到处脚本的接口,所以这种模式非常不适合h5游戏.

不适合服务器和客户端使用不同的语言.





3json数据协议

1 json数据协议,为了改变二进制的不足,改变二进制的封包与解包需要

以来于每个协议的对象,使用跨语言的数据交换格式json与xml相比,体积会比

xml小,可读性比二进制好,跨语言的封包和解包,每个语言只需要实现json的解码编码即可


2json数据格式的封包格式,不采用size+body的方式,因为脚本语言不适合直接操作字节,

所以采用\r\n的模式, 收到/r/n后认为就是一个数据包,所以在编码好的json字符串里

不能有\r\n, 但是字符串里面有\r\n的话呢

这样的话就是要把这些数据转换成base64编码






4二进制数据传输服务器的设计

首先设计协议的结构,客户端和服务器公用同一个封包和拆包的代码。

这个包是这样设计的,前面两个字节放长度,然后紧接着4个字节放协议类型,后面再放数据包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
//使用#define 或者 enum来定义一个协议
enum {
     USER_LOGIN = 0,  //用户登录
};
 
//设计登陆的         数据包1
struct  user_login{
     char * name;  //账号
     char * passwd; //密码
     int  channel; 一个标志
};
 
 
//返回给客户端登陆结果 数据包2  
struct  user_login_response {
     int  status; //登陆状态  1就是登陆成功
     char * name;  //名字
     int  level;   //等级
};
 
不同的包要不同的处理API  
数据包1  就是对登陆的请求进行  封包
int  command_login_pack( int  cmd_type, struct  user_login_req*
                             req, unsigned  char * dst){
      //unsigned 防止最高位扩展
      //无符号比有符号能保存2倍于有符号类型的正整数
     //cmd_type就是 协议类型  然后就是这个结构体,out是一个输出的指针  
     //执行输出的内存的指针
     
     
     unsigned cahr* walk = dst;
     int  len= 0;
     
     
   //前面4个字节cmd_type 因为你可能有很多这样的命令
     *(( int *)walk) = cmd_type; //将这个变量以地址形式显示,然后取他的值
     walk += 4;    //内存向前4个字节
     
     //用户名和密码 
     sprintf (walk,  "%s" , req->uname)
      //跳过这个字符串的长度 + 1 是因为有0的结尾符
     walk +=  strlen (walk) + 1; 
     
     sprintf (walk,  "%s" , req->upasswd);
     walk +=  strlen (walk) + 1;
     
     *(( int *)walk) = req->channel;
     
         walk += 4;
     len = walk - dst; //长度
     return  len;
 
}
 
 
//登陆请求的解包设计
void  command_login_unpack(unsigned  char * data,
                 int  len,  struct  user_login_req* out){
                 
        //data就是服务器收到的二进制数据
           char * walk = ( char *)data;
           out->uname = _strdup(walk);
       walk +=  strlen (walk) + 1;   //+1就是结尾符
       
       out->upasswd = _strdup(walk);  //字符串拷贝函数 需要free释放内存
       walk +=  strlen (walk) + 1;
     
       out->channel = *( int *)walk;
}
 
 
 
//返回结果封包和解包
 
int  login_response_pack( int  cmd_type, struct  user_login_response*
             respons, unsigned  char * out)
{
     //unsigned 防止最高位扩展
     //无符号比有符号能保存2倍于有符号类型的正整数
 
     unsigned  char * walk = out;
     *( int *)walk = cmd_type;
     walk += 4;
 
 
     *( int *)walk = respons->status;
     walk += 4; 
 
 
     sprintf (walk, "%s" , respons->name);
     walk += ( strlen (walk) + 1);
 
 
     *( int *)walk = respons->level;
     walk += 4;
 
     return  (walk - out);
}
 
 
void  login_response_unpack(unsigned  char * data,  int  len, struct 
         user_login_response* out)
{
     unsigned  char * walk = data;
     out->status = *( int *)walk;
     walk += 4;
 
     out->name = _strdup(walk);
     walk += ( strlen (walk) + 1);
 
     out->level = *( int *)walk;
     walk += 4;
 
}





然后 客户端和服务器 需要公用这个 协议文件

首先客户端会创建一个请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
//从这里开始发送登录请求 //////////////////////////////客户端发送请求
struct  user_login req;   // 用户登陆的结构体
req.uname =  "小明" ;     //账号
req.upasswd =  "1123456" ;    //密码
req.channel = 10;        //一个标记
 
char  send_buf[4096]; 
//封包  跳过前面两个字节 用来存长度
int  len = command_login_pack(USER_LOGIN, &req ,send_buf+2);
//返回的len就是长度直接把这个长度个 这个 缓冲区
*((unsigned  int  *)send_buf) = (len + 2);
send(s, send_buf, len + 2, 0);   //发送给客户端
 
 
 
 
// 从这里收取服务器的处理结果了  ////////////////////////////////服务器收到处理请求
         
         int  size = (*(unsigned  short *)io_data->pkg);  //获取前面两个字节的长度
         //内存这里要+2个字节 才是协议
          data += 2;   
         
     //前面4个字节总是包的命令  也就是协议
     switch  (*( int *)data){
         case  USER_LOGIN:{     //判断是不是登陆协议
         //解包  
             
 
                 //之后先调用回调函数
                 SERVER.cmd_func[USER_LOGIN](s,data+4,len-4);  +4个字节 就是协议的长度
             
                 
         } break ;
             
     }
 
/////////////////////////////////登陆处理回调函数
     //  解包
     struct  user_login_req req;
     command_login_unpack(data, len, &req);
 
 
         //然后就能拿到完整的数据了
         printf ( "%s:%s==%d登录请求\n" , req.uname,req.upasswd,req.channel);
         
         
         
         
         
         
         
         /////////////////////////////到这里  应该就是要查询数据库了
         
         //返回ok        随便返回一数据
         struct  user_login_response res;
     res.level = 100;
     res.name =  "张三" ;
     res.status = 1;   //登录OK
         
         
             //封包
     unsigned  char  send_buf[256];
     len = login_response_pack(USER_LOGIN, &res,send_buf);
         
         
         
         
         
         在发送前我们要在这个字符串前面加两个字节的表示长度
 
     //len就是你要的长度
     char * send_buf =  malloc (len + 2);  //先申请一个内存
     memcpy (send_buf + 2, data,len);    //把数据跳过前面两个字节进行拷贝
     //把长度赋值给前面两个字节 首先len+2 把len的值以地址显示,然后取值
     *(unsigned  short *)send_buf = (len + 2);
     发送给客户端
         
         
         
         
       
       
       ////////////////////////////////////////////////////客户端 收到响应
           len = recv(s, send_buf,4096,0);
 
     struct  user_login_response respons;
     //解包
     if  ((*( int *)(send_buf + 2)) == USER_LOGIN){
         login_response_unpack(send_buf + 2 + 4, len - 2 - 4, &respons);
         printf ( "请求结果:%d==%s\n" , respons.status, respons.status ?  "成功"  "失败" );
         printf ( "用户等级:%d=用户姓名:%s\n" , respons.level, respons.name);
 
     }

_UZ{QCUSP`EMJ]Q$7)1S6EM.png





 本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/2060710,如需转载请自行联系原作者


相关实践学习
RocketMQ一站式入门使用
从源码编译、部署broker、部署namesrv,使用java客户端首发消息等一站式入门RocketMQ。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
相关文章
|
2月前
|
前端开发 网络协议 JavaScript
|
6月前
|
网络协议 算法 Go
【协议分析】rpcx网络协议分析之kcp数据传输
【协议分析】rpcx网络协议分析之kcp数据传输
89 0
|
9月前
|
编解码 C++
国标GB28181协议客户端开发(四)实时视频数据传输
国标GB28181协议客户端开发(四)实时视频数据传输
300 0
|
Web App开发 存储 编解码
【WebRTC原理探索】更进一步,核心组件RTP/RTCP数据传输协议
【WebRTC原理探索】更进一步,核心组件RTP/RTCP数据传输协议
271 0
|
负载均衡 算法
leach协议性能对比仿真,包括死亡节点数,数据传输,网络能量消耗,簇头产生数以及负载均衡度
leach协议性能对比仿真,包括死亡节点数,数据传输,网络能量消耗,簇头产生数以及负载均衡度
260 0
leach协议性能对比仿真,包括死亡节点数,数据传输,网络能量消耗,簇头产生数以及负载均衡度
|
网络协议 算法 安全
网络协议之:基于UDP的高速数据传输协议UDT
网络协议之:基于UDP的高速数据传输协议UDT
网络协议之:基于UDP的高速数据传输协议UDT
|
网络协议 测试技术 网络安全

热门文章

最新文章