本文仅为笔者的思考和学习记录,不做教程使用,不保证内容正确和完美,如有错误还请指正。
一、
在开始对TCP进行封装之前,我们需要明确自己的需求,一下是我的需求:
1.用户可以登录
2.用户可以注册
3.可以创建文件夹
4.可以下载文件
5.可以上传文件
6.可以设置当前目录
7.可以得到错误信息
有了以上需求,现在就要可以设计通信协议中的数据类型标志位了(标志位的大小和类型可以根据自己的需求变动),具体设计如下:
//协议头占用四个字节(占用的空间可以更少或者更多)
//=============宏=============
typedef unsigned int Type_t; //消息类型
typedef unsigned short Cmd_t; //在封装的消息发送和接收函数中对数据的操作(可以同时对一条数据进行多个操作)
//以下各项互斥
#define MSG_NULL 0 //没有消息(用于判断)
#define MSG_BREAK 1 //断开连接(服务器和客户端接收)
#define MSG_TEXT 2 //文本消息(服务器和客户端)
//文件操作
#define MSG_FILE 4 //文件名(服务器和客户端)
#define MSG_GFILE 8 //获取文件(仅限服务器接收)
#define MSG_DFILE 16 //下载文件(服务器和客户端)
#define MSG_END 32 //文件传输结束(服务器和客户端)
//用户登录注册
#define MSG_REGISTER 64 //注册消息(仅限服务器)
#define MSG_LOGIN 128 //登录消息(仅限服务器)
//操作
#define MSG_CMD 256 //CMD消息(仅限服务器)
#define MSG_ERROR 2048 //操作失败,比如打开文件(仅限客户端)
//文件夹操作
#define MSG_PATH 512 //设置路径(仅限服务器)
二、
有了数据标志位了以后就可以开始封装TCP的发送和接收消息了(注:代码并不算完善,仅供参考),我的封装代码如下:
//数据发送函数的封装
参数解释
/*
@brief TCP发送数据
@param sd 发送的目标套接字
@param Type 文件类型
@param Cmd 要做的操作
@param date 要发送的数据
@param Len 文件长度(0~1010)
@param CmdMsg 需要发送的CMD命令
@param (*callback)(char*date,int len) 文件加密回调函数,用户自己实现加密算法
@return 发送的文件长度
*/
int MSend(int fd, Type_t Type, Cmd_t Cmd, char *Date, int Len, void (*callback)(char *date, int len), int Flag)
{
char buf[1040]={0};
memset(buf,0,1024);
char len[5]={0};
//长度
sprintf(len,"%-4d",Len);
//检查Type里的1是否唯一
for(int i=0;i<32;i++)
{
//保留第一个遇到的1
if(Type&1<<i)
{
Type&=1<<i;
//取反,防止提前结束buf(为什么要取反?----因为我们发送的是二进制数据,并且每次能发送的数据类类型只能有一种,那么Type这个标志位就可能出现前面三个字节都为0的情况,那么在send函数是,它将前面三个字节都翻译为'\0',消息就会在一开始就结束)
Type=~Type;
break;
}
}
//将整形的二进制位映射到字符串避免大小端问题
IntToCharBin(buf,Type);
ShrotToCharBin(buf+4,~Cmd);
//添加长度
strcat(buf+5,len);
//加密
if(Cmd&FLAG_PASS&&callback)
{
//调用回调函数进行加密
callback(Date,Len);
}
//添加数据
strcat(buf+9,Date);
send(fd,buf,Len+10,Flag);
}
//数据接收函数的封装,在这里我没有将数据长度传出
/*
@brief TCP接收数据
@param sd接收目标的套接字
@param date接收到的数据
@param void (*callback)(char*date,int len)接数据的解算法
@return 文件类型
*/
Type_t MRcv(int fd, char *date,int* outlen,void (*callback)(char *date, int len), int Flag)
{
if(!date)return MSG_NULL;
char head[24]={0};
unsigned int Type=0;
unsigned short Cmd=0;
memset(head,0,24);
memset(date,0,1024);
int len=1;
int Len=1;
//接收协议头 !这儿并没有实际读取文件头数据,只是读取查看缓冲区中数据的长度
while(10>len&&len>0)
len=recv(fd,head,10,MSG_PEEK);
//实际读取文件头数据的地方
if(0>=recv(fd,head,10,0))
{
//客户端断开连接
return MSG_BREAK;
}
//解析类型 !将二进制数据还原为原来的数据
CharToIntBin(head,&Type);
Type=~Type;
//解析操作
CharToShrotBin(head+4,&Cmd);
Cmd=~Cmd;
//解析长度
Len=atoi(head+6);
memset(head,0,24);
//接收消息
len=0;
while(Len>len&&len>0)
{
len=recv(fd,date,Len,MSG_PEEK);
}
if(0>=recv(fd,date,Len,0))
{
return MSG_BREAK;
}
//消息由有加密就解密
if(Cmd&FLAG_PASS&&callback)
{
callback(date,len);
}
return Type;
}
发送消息和接受消息的代码已经封装好了,那么接下来就应该对接收到的消息进行处理了(注:这里我将消息处理函数分为了客户端和服务器两种),我的消息处理代码如下:
客户端:
//我为这个函数单独开了一个线程来处理( 注:数据类型含义可能和前面的宏定义有区别,以这儿为主,上传文件这儿并没有写出)
void *ClientMsgHandle(void* argv)
{
int sd=*(int*)argv;
int fd=-1;
char buf[1024]={0};
Type_t Type=0;
//客户端接受的消息只有五种消息类型,
while(1)
{
memset(buf,0,1024);
Type=MRcv(sd,buf,NULL,NULL,NULL,0);
if(Type==MSG_FILE)
{
char *name=basename(buf);
strcpy(buf,name);
}
switch (Type)
{
//文本消息
case MSG_TEXT:
puts(buf);
break;
//错误消息
case MSG_ERROR:
puts(buf);
break;
//文件名
case MSG_FILE:
//这儿写你的创建文件的逻辑(开始下载文件)
break;
//文件体
case MSG_DFILE:
//这儿写向文件写入内容的逻辑
break;
//文件结束
case MSG_END:
//这儿写文件下载完成的逻辑
break;
}
if(Type==MSG_BREAK)break;
}
puts("服务器断开连接!!!");
}
服务器:
void ServerMsgHandle(int fd,char* Msg,Type_t Type,ThreadPoll* poll)
{
if(!Msg)return;
switch (Type)
{
case MSG_REGISTER:
//这儿写用户注册逻辑
break;
case MSG_LOGIN:
//这儿写用户登录逻辑
break;
//创设置路径
case MSG_PATH:
//这儿写路径设置逻辑
break;
//客户端下载项目(另一个线程)
case MSG_GFILE:
//这儿写文件下载逻辑
case MSG_DFILE:
//这儿写文件上传逻辑
case MSG_TEXT:
puts(Msg);
break;
}
}
以上大概就是整个简易通行协议的框架了,由于此代码比较乱,笔者并没有进行重构,如思路较乱,还请见谅。