5. 设计应用层协议(粘包)

为什么要自己设计协议?
先引入一个问题:

在这里插入图片描述
左边是客户端 右边是服务端
直觉是打印:
Recvive:A
Recvive:B
Recvive:C

服务端输出:
在这里插入图片描述
分开三次发送,服务端应该分开收到三次字符,但是结果是一次性收到了三个字符,与期望的结果不一致。这种问题就叫粘包。解决的方法之一就是自定义协议!

原因分析:
客户端与服务端之间用tcp连接,可以进行可靠的数据传输,可靠体现在 数据能够按序到达,发送的时候ABC按序发送、到达。tcp属于流式传输,数据源源不断传输,导致发送接受不对等。

在这里插入图片描述
发送的数据先进入发送缓冲区,再由操作系统发送给远程主机。
接受数据时接受到的数据存入接受缓冲区,程序从缓冲区读取数据。
在这里插入图片描述
A B C都在接受缓冲区,所以读取的时候一次性接受。
数据接收端无法知道数据的发送方式(独立发送还是报答报送)
在这里插入图片描述
在这里插入图片描述
解决方法:自己设计协议,包含长度、类型等信息
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
先接受12个字节,就知道数据区有多少字节了(前12字节中的数据长度区域可以知道数据区长度),这样就不会粘包!
在这里插入图片描述
最后一个柔性数组不占空间
柔性数组在《C语言剖析》中记载过…

//message.h
#ifndef _MESSAGE_H_
#define _MESSAGE_H_

typedef struct message
{
    unsigned short type;
    unsigned short cmd;
    unsigned short index;
    unsigned short total;
    unsigned int length;
    unsigned char payload[];
}Message;

Message* Message_New(unsigned short type,unsigned short cmd,
                    unsigned short index,unsigned total,
                    const char* payload,unsigned int length);



#endif
//message.c
#include "message.h"
#include <malloc.h>
#include <string.h>

Message* Message_New(unsigned short type,unsigned short cmd, unsigned short index,unsigned total,const char* payload,unsigned int length)
{
    Message* ret = malloc(sizeof(Message) + length);
    if(ret)
    {
        ret->type=type;
        ret->cmd=cmd;
        ret->index=index;
        ret->total=total;
        ret->length=length;

        if(payload)
        {
            memcpy(ret+1,payload,length);
        }
    }
    return ret;
}
//client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include "message.h"

int main()
{
    int sock = 0;
    struct sockaddr_in addr = {0};
    int len = 0;
    char buf[128] = {0};
    char input[32] = {0};
    int r = 0;
    Message* pm = NULL;

    sock = socket(PF_INET, SOCK_STREAM, 0);

    if( sock == -1 )
    {
        printf("socket error\n");
        return -1;
    }

    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    addr.sin_port = htons(8888);

    if( connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 )
    {
        printf("connect error\n");
        return -1;
    }

    printf("connect success\n");

    pm = Message_New(0, 0, 1, 3, "A", 1);

    send(sock, pm, sizeof(Message) + 1, 0);

    pm = Message_New(0, 0, 2, 3, "B", 1);

    send(sock, pm, sizeof(Message) + 1, 0);

    pm = Message_New(0, 0, 3, 3, "C", 1);

    send(sock, pm, sizeof(Message) + 1, 0);

    close(sock);

    return 0;
}
//server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{
    int server = 0;
    struct sockaddr_in saddr = {0};
    int client = 0;
    struct sockaddr_in caddr = {0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {0};
    int r = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if( server == -1 )
    {
        printf("server socket error\n");
        return -1;
    }
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);
    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
        printf("server bind error\n");
        return -1;
    }
    if( listen(server, 1) == -1 )
    {
        printf("server bind error\n");
        return -1;
    }
    printf("server start success\n");
    while( 1 )
    {
        asize = sizeof(caddr);
        client = accept(server, (struct sockaddr*)&caddr, &asize);
        if( client == -1 )
        {
            printf("client accept error\n");
            return -1;
        }
        printf("client: %d\n", client);
        do
        {
            r = recv(client, buf, sizeof(buf), 0);
            if( r > 0 )
            {
                int i = 0;
                for(i=0; i<r; i++)
                {
                    printf("%02X ", buf[i]);
                }
                printf("\n");
            }
        } while ( r > 0 );
        close(client);
    }
    close(server);
    return 0;
}

在这里插入图片描述
通过眼睛解析打印输出:
前四个字节分别是type cmd,后四个字节为index total,紧接着的四个字节是长度1,之后就是数据41(a),重复三次 后面两次分别是想发送b、c 这样我们就知道 他想发送a b c过来 而且是分三次发送 这样 粘包问题就解决了!

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值