尝试使用TCP封装一个简易的通信协议

本文仅为笔者的思考和学习记录,不做教程使用,不保证内容正确和完美,如有错误还请指正。

一、

在开始对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;
    }
}

以上大概就是整个简易通行协议的框架了,由于此代码比较乱,笔者并没有进行重构,如思路较乱,还请见谅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值