c语言协议数据传输,数据传输的协议

1数据协议

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

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

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

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

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

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

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

6分包协议:

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

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

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

发送:

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

命令子类型号   用户名    密码

返回:

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

2二进制数据协议

1 二进制协议原理:

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

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

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

2设计原理://协议的结构体

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个字节放协议类型,后面再放数据包。//使用#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;

}

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

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

//从这里开始发送登录请求 //客户端发送请求

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);

}

9fca39a8ac639ddabc60c06bfb990dca.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值