1数据协议

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


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

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

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


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


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

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



6分包协议:

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

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



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

发送:

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

命令子类型号 用户名 密码


返回:

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






2二进制数据协议

1 二进制协议原理:

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

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

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


2设计原理:

//协议的结构体structperson{intmain_type.intsub_type,char*name,intuid,intage,intsex,...}//首先将这个结构体里面的每个数据成员对应的值写入到内存,然后一个封包好的地址//序列化二进制打包unsignedchar*pack_person(结构体)然后进行封包[包头size]+[包体]//反序列号解包structperson*unpack_person(unsignedchar*data,intlen);

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

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



4二进制协议的缺点:

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

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

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





3json数据协议

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

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

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


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

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

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

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






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

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

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

//使用#define或者enum来定义一个协议enum{USER_LOGIN=0,//用户登录};//设计登陆的数据包1structuser_login{char*name;//账号char*passwd;//密码intchannel;一个标志};//返回给客户端登陆结果数据包2structuser_login_response{intstatus;//登陆状态1就是登陆成功char*name;//名字intlevel;//等级};不同的包要不同的处理API数据包1就是对登陆的请求进行封包intcommand_login_pack(intcmd_type,structuser_login_req*req,unsignedchar*dst){//unsigned防止最高位扩展//无符号比有符号能保存2倍于有符号类型的正整数//cmd_type就是协议类型然后就是这个结构体,out是一个输出的指针//执行输出的内存的指针unsignedcahr*walk=dst;intlen=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;//长度returnlen;}//登陆请求的解包设计voidcommand_login_unpack(unsignedchar*data,intlen,structuser_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;}//返回结果封包和解包intlogin_response_pack(intcmd_type,structuser_login_response*respons,unsignedchar*out){//unsigned防止最高位扩展//无符号比有符号能保存2倍于有符号类型的正整数unsignedchar*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);}voidlogin_response_unpack(unsignedchar*data,intlen,structuser_login_response*out){unsignedchar*walk=data;out->status=*(int*)walk;walk+=4;out->name=_strdup(walk);walk+=(strlen(walk)+1);out->level=*(int*)walk;walk+=4;}





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

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

//从这里开始发送登录请求//////////////////////////////客户端发送请求structuser_loginreq;//用户登陆的结构体req.uname="小明";//账号req.upasswd="1123456";//密码req.channel=10;//一个标记charsend_buf[4096];//封包跳过前面两个字节用来存长度intlen=command_login_pack(USER_LOGIN,&req,send_buf+2);//返回的len就是长度直接把这个长度个这个缓冲区*((unsignedint*)send_buf)=(len+2);send(s,send_buf,len+2,0);//发送给客户端//从这里收取服务器的处理结果了////////////////////////////////服务器收到处理请求intsize=(*(unsignedshort*)io_data->pkg);//获取前面两个字节的长度//内存这里要+2个字节才是协议data+=2;//前面4个字节总是包的命令也就是协议switch(*(int*)data){caseUSER_LOGIN:{//判断是不是登陆协议//解包//之后先调用回调函数SERVER.cmd_func[USER_LOGIN](s,data+4,len-4);+4个字节就是协议的长度}break;}/////////////////////////////////登陆处理回调函数//解包structuser_login_reqreq;command_login_unpack(data,len,&req);//然后就能拿到完整的数据了printf("%s:%s==%d登录请求\n",req.uname,req.upasswd,req.channel);/////////////////////////////到这里应该就是要查询数据库了//返回ok随便返回一数据structuser_login_responseres;res.level=100;res.name="张三";res.status=1;//登录OK//封包unsignedcharsend_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的值以地址显示,然后取值*(unsignedshort*)send_buf=(len+2);发送给客户端////////////////////////////////////////////////////客户端收到响应len=recv(s,send_buf,4096,0);structuser_login_responserespons;//解包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);}