TCP 组包和拆包算法

/*************************************
文件名: server.c
TCP 组包和拆包实现算法


作者:   马中海
QQ:     284358503
Email: zhonghaima001@163.com
*/
#include <stdlib.h>
#include <sys/types.h>
#include <stdio.h>
#include <sys/socket.h>
#include <linux/in.h>
#include <string.h>


#include <stdio.h>
#include <stdlib.h>
#include <string.h>



#define BUF_SIZE 1024*5


//#define TCP_PACK_DEBUG 1


int main()
{

    int nBufuseLen=0;       //缓冲区里用到的长度
    int nRevOnceLen=0;      //read 一次读到的长度
    int nPackLen=0;         //每一包的长度,包括头和两个长度和尾字节
    int bufSize=BUF_SIZE;


    unsigned char buf[BUF_SIZE];     //read 的缓存


    int i=0;


    int sfp,nfp; /* 定义两个描述符 */
    struct sockaddr_in s_add,c_add;
    int sin_size;
    unsigned short portnum=6000; /* 服务端使用端口 */
    printf("Hello,welcome to my server !\r\n");
    sfp = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sfp)
    {
        printf("socket fail ! \r\n");
        return -1;
    }
    printf("socket ok !\r\n");
    /* 填充服务器端口地址信息,以便下面使用此地址和端口监听 */
    bzero(&s_add,sizeof(struct sockaddr_in));
    s_add.sin_family=AF_INET;
    s_add.sin_addr.s_addr=htonl(INADDR_ANY); /* 这里地址使用全0,即所有 */
    s_add.sin_port=htons(portnum);
    /* 使用bind进行绑定端口 */
    if(-1 == bind(sfp,(struct sockaddr *)(&s_add), sizeof(struct sockaddr)))
    {
        printf("bind fail !\r\n");
        return -1;
    }
    printf("bind ok !\r\n");
    /* 开始监听相应的端口 */
    if(-1 == listen(sfp,5))
    {
        printf("listen fail !\r\n");
        return -1;
    }
    printf("listen ok\r\n");
    //  while(1)
    {


        //    char buf[1027];
        //    int readlen;


        sin_size = sizeof(struct sockaddr_in);
        /* accept服务端使用函数,调用时即进入阻塞状态,等待用户进行连接,在没有客户端进行连接时,程序停止在此处,
           不会看到后面的打印,当有客户端进行连接时,程序马上执行一次,然后再次循环到此处继续等待。
           此处accept的第二个参数用于获取客户端的端口和地址信息。
            */
        nfp = accept(sfp, (struct sockaddr *)(&c_add), &sin_size);
        if(-1 == nfp)
        {
            printf("accept fail !\r\n");
            return -1;
        }
        printf("accept ok!\r\nServer start get connect from %#x : %#x\r\n",ntohl(c_add.sin_addr.s_addr),ntohs(c_add.sin_port));
        /* 这里使用write向客户端发送信息,也可以尝试使用其他函数实现 */
        if(-1 == write(nfp,"hello,welcome to my server \r\n",32))
        {
            printf("write fail!\r\n");
            return -1;
        }


//头0xF9   两个长度字节   数据   尾0xF8
        while(1)
        {
#ifdef TCP_PACK_DEBUG
            {
                printf("bufSize = %d ,nBufuseLen = %d\n",bufSize,nBufuseLen);
            }
#endif
            if(bufSize>nBufuseLen)
            {
                nRevOnceLen=read(nfp,buf+nBufuseLen,bufSize-nBufuseLen);
                nBufuseLen=nBufuseLen+nRevOnceLen;


#ifdef TCP_PACK_DEBUG
                {
                    printf("nBufuseLen = %d>3\n",nBufuseLen);
                }
#endif
                /*
                                printf(" nRevOnceLen data:\n");
                                for(i=0; i<nRevOnceLen; i++)
                                {
                                    printf(" 0x%x ",buf[i]&0xFF);
                                }
                                printf(" \n");
                */


            }
            else
            {
                printf("buf if full\n");
            }




            while(nBufuseLen>3)            //用到的大于3个字节
            {


                /*            printf(" nBufuseLen data:\n");
                            for(i=0; i<nBufuseLen; i++)
                            {
                                printf(" 0x%x ",buf[i]&0xFF);
                            }
                            printf(" \n");
                */

#ifdef TCP_PACK_DEBUG
                {
                    printf("buf[0] = 0x%x \n",buf[0]&0xFF);
                }
#endif
                if((buf[0]&0xFF)==0xF9)         //头
                {
                    nPackLen=(buf[1]*256)+buf[2];


#ifdef TCP_PACK_DEBUG
                    {
                        printf("nBufuseLen=%d ,nPackLen=%d\n",nBufuseLen,nPackLen);
                    }
#endif


                    if(nBufuseLen>=nPackLen)                //大于和=
                    {
#ifdef TCP_PACK_DEBUG
                        {
                            printf("buf[nPackLen-1] = 0x%x \n",buf[nPackLen-1]&0xFF);
                        }
#endif


                        if((buf[nPackLen-1]&0xFF)==0xF8)                  //尾
                        {
                            //找到一包数据,所有的数据都往前移动nPackLen个字节
#ifdef TCP_PACK_DEBUG
                            {
                                printf("head 0x%x length %d tail 0x%x \n", buf[0]&0xFF,nPackLen,buf[nPackLen-1]&0xFF);
                            }
#endif


                            for(i=0; i<nBufuseLen-nPackLen; i++)
                            {
                                buf[i]=buf[nPackLen+i];
                            }


                            nBufuseLen=nBufuseLen-nPackLen;      //用到的字节变小了
                            //   continue;           //不要read了
                        }
                        else
                        {


#ifdef TCP_PACK_DEBUG
                            printf("buf[%d-1]=0x%x,(buf[0]!=0xF8)\n",nPackLen,(buf[nPackLen-1]!=0xF8));        //这里应该不会执行
                            for(i=0; i<5; i++)
                            {
                                printf(" 0x%x ",buf[i]&0xFF);
                            }
                            printf(" \n");
#endif
                            printf(" (buf[nPackLen-1]&0xFF)!=0xF8 \n");
                            nBufuseLen=0;           //清除错误的包   //这里应该不会执行




                            break;


                        }
                    }
                    else
                    {


#ifdef TCP_PACK_DEBUG
                        printf(" nBufuseLen<=nPackLen\n");
#endif
                        break;
                    }
                }
                else
                {
                    printf(" (buf[0]!=0xF9)\n");        //这里应该不会执行
                    nBufuseLen=0;           //清除错误的包   //这里应该不会执行
                    break;
                }
            }


            /*
                        if((0xFF&buf[0])!=0xF2)
                        {
                            printf("readlen = %d buf  0x%x  0x%x  0x%x \n",readlen,0xFF&buf[0],0xFF&buf[1],0xFF&buf[2]);
                        }




                        if(readlen!=1027)
                            printf("readlen = %d buf  0x%x  0x%x  0x%x \n",readlen,0xFF&buf[0],0xFF&buf[1],0xFF&buf[2]);


            */
        }
        //  close(nfp);
    }
    // close(sfp);
    return 0;
}
<think>好的,用户想了解CTCP协议中如何处理拆包包和的问题。首先,我需要确认CTCP协议的具体定义,但根据提供的引用,可能是指TCP协议中的相关问题,或者是某种定制化的TCP实现。由于引用中提到了Cisco Packet Tracer和TCP协议的分析,这里假设CTCP可能是指TCP,或者是用户笔误。 接下来,拆包包和TCP通信中常见的问题。拆包(Packet Splitting)指的是应用层的数据TCP分成多个报文传输;(Packet Combining)是接收方将多个TCP报文重为完整的数据;粘(Sticky Packet)则是指接收方收到的数据粘连在一起,无法区分边界。 根据引用中的内容,引用[3]提到了应用层Packet的设计,可能需要继承Packet类并处理消息体的编码。这里可能涉及自定义头来标记数据长度,以解决粘问题。例如,固定长度的消息头(HEADER_LENGTH = 4)可能用于存储消息体的长度,接收方根据该长度进行拆包。 另外,引用[2]提到通过配置和分析TCP连接来理解其过程,括连接建立、数据传输等。TCP本身是面向流的协议,不保留消息边界,因此应用层需要自己处理消息的分界。常见的方法括使用固定长度、分隔符或长度字段。 解决方案方面,需要结合应用层协议设计,比如在头添加长度信息,或者使用特定的分隔符。例如,在引用[3]的示例中,消息头长度是4字节,可能用来存储消息体的长度,这样接收方可以先读取头部的4字节,解析出长度后再读取对应长度的消息体,避免粘问题。 此外,用户可能需要具体的实现步骤或代码示例。根据引用中的代码结构,可以展示如何定义含长度头的Packet类,并实现拆包的逻辑。同时,需要注意缓冲区管理和数据读取的完整性,防止数据不完整或多余的数据影响后续处理。 最后,生成相关问题时要围绕TCP协议处理、应用层设计、具体实现技术等方面,帮助用户深入理解解决方案。</think>关于CTCP协议中拆包及粘问题的解决方法,结合网络协议栈实现原理和应用层设计,具体解决方案如下: ### 一、粘问题的本质 TCP作为面向字节流的传输层协议,不保留应用层报文边界[^2]。当应用层连续发送$N$字节和$M$字节数据时,接收端可能收到$N+M$字节的粘连数据,这种现象称为粘。需要应用层协议自行定义报文边界。 ### 二、CTCP协议的解决方案 #### 1. 固定长度头法(推荐方案) ```java // 引用[3]中Packet设计的改进实现 public class CTCPPacket extends Packet { // 固定4字节报文头存储body长度 public static final int HEADER_LENGTH = 4; private byte[] body; // 时先写入body长度 public byte[] toBytes() { ByteBuffer buffer = ByteBuffer.allocate(HEADER_LENGTH + body.length); buffer.putInt(body.length); buffer.put(body); return buffer.array(); } } ``` 此方法通过$$L_{header} = \lceil \log_{256}(MaxBodySize) \rceil$$计算头部长度,保证能表示最大报文长度[^3] #### 2. 特殊分隔符法 在报文尾部添加特定分隔符如`\r\n`,但需确保分隔符不出现在数据内容中: $$Packet = Body_{data} + Delimiter_{symbol}$$ #### 3. 动态缓冲区管理 接收端维护环形缓冲区实现拆包: ```python class BufferManager: def __init__(self): self.buffer = bytearray() def append(self, data): self.buffer.extend(data) def extract_packet(self): if len(self.buffer) >= 4: length = int.from_bytes(self.buffer[:4], 'big') if len(self.buffer) >= 4 + length: packet = self.buffer[4:4+length] self.buffer = self.buffer[4+length:] return packet return None ``` ### 三、性能优化要点 1. **滑动窗口机制**:通过TCP窗口大小动态调整传输速率,避免发送过快导致缓冲区溢出 2. **Nagle算法控制**:在实时性要求高的场景禁用该算法(设置`TCP_NODELAY`选项) 3. **MTU适配**:确保单个报文不超过网络最大传输单元,典型值为: $$MTU_{以太网} = 1500\ bytes$$ ### 四、异常处理机制 1. 超时重传:设置RTO(Retransmission Timeout)应对丢 2. 校验和验证:通过CRC32或MD5校验报文完整性 3. 序号确认:采用SEQ/ACK序列号保证有序性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值