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);
}
|
本文转自超级极客51CTO博客,原文链接:http://blog.51cto.com/12158490/2060710,如需转载请自行联系原作者