Skip to content

TCP 粘包问题及解决方案

Posted on:April 16, 2023 at 12:48 PM

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。


[迁移博客]

粘包问题的准确描述

如何解决粘包问题

解决方案:

新增数据头

发送端解决方案

  1. 根据待发送的数据长度N动态申请一块固定空间,空间大小为:N+4(4 是包头占用的字节数)
  2. 将待发送数据长度写入前四个字节,将其转换为网络字节序(大端)
  3. 将待发送的数据拷贝到包头后面的空间中,将数据包完整发出(字符串不需要考虑字节序问题,但要考虑部分I/O问题)
  4. 释放动态申请到的堆内存

SHOW ME THE CODE

/*
    writen() 的参数与write()相同
    循环使用了write()系统调用
    确保请求的字节数总是能够得到全部传输
    ssize_t writen(int fd, void *buffer, size_t count);
    return number of bytes written, -1 on failure
*/

#include <unistd.h>
#include <sys/socket.h>

ssize_t writen(int fd, const char *msg, size_t size)
{
    // 只读,所以const
    const char *buf = msg;
    int count = size;
    // 循环处理, 直至全部发送
    while(count > 0) {
        // 专用于套接字的 I/O 系统调用
        int len = send(fd, buf, count, 0);
        if(len == -1) {
            close(fd);
            return -1;
        }else if (len == 0){
            continue;
        }
        // offset
        buf += len;
        // 计算剩余待发送量
        count -= len;
    }

    return size;
}
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>


int sendMsg(int cfd, char* msg, int len)
{
    // 错误处理
    if(cfd <= 0 || msg == NULL || len <= 0){
        return -1;
    }

    // 分配空间
    char *data = (char *)malloc(len+4);
    /*
    字符串没有字节序问题
    数据头为整型,存在字节序问题
    需要将数据头转换网络字节序
    */
    int bigLen = htonl(len);

    // 写入数据
    memcpy(data, &bigLen, 4);
    memcpy(data+4, msg, len);

    // 发送数据
    int ret = writen(cfd, data, len+4);

    // 释放内存
    free(data);

    return ret;
}

接收端解决方案

  1. 接收数据头 4 字节数据,将其从网络字节序转换为主机字节序,得到待接收数据长度
  2. 根据得到的长度申请固定大小的堆内存,用来存储数据
  3. 接受固定数目的数据保存到申请到的内存之中
  4. 处理数据
  5. 释放堆内存

SHOW ME THE CODE

/*
    readn() 的参数与read()相同
    循环使用了read()系统调用
    确保请求的字节数总是能够得到全部传输
    ssize_t readn(int fd, void *buffer, size_t count);
    return number of bytes read, 0 on EOF, -1 on failure
*/

#include <unistd.h>
#include <sys/socket.h>

ssize_t readn(int fd, char *buf, size_t size) {
    char *p = buf;
    int count = size;
    while (count > 0) {
        int len = recv(fd, p, count, 0);
        if(len == -1) {
            return -1;
        }else if(len == 0) {
            return size-count;
        }
        p += len;
        count -= len;
    }
    return size;
}
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

int recvMsg(int cfd, char **msg) {
    // 首先接受报头
    int len = 0;
    readn(cfd, (char*)&len, 4);
    len = ntohl(len);
    printf("需接收的数据包大小: %d\n", len);

    // 根据大小分配内存
    char *buf = (char *)malloc(len + 1);
    int ret = readn(cfd, buf, len);
    if(ret != len) {
        close(cfd);
        free(buf);
        return -1;
    }
    buf[len] = '\0';

    *msg = buf;

    return ret;
}

参考