linux网络编程系列(十三)--缓冲区设计及收发大量数据
发布日期:2021-05-08 05:59:43 浏览次数:22 分类:精选文章

本文共 1899 字,大约阅读时间需要 6 分钟。

自定义缓冲区与TCP/IP编程

在TCP/IP编程中,除了操作系统提供的socket缓冲区外,通常还需要自定义一个数据的收发缓冲区。这是为了应对网络通信中数据传输的不确定性,确保数据能够高效且可靠地发送和接收。


为什么需要自定义缓冲区

在实际应用中,发送数据时可能会遇到以下情况:

假设需要发送40kB的数据,但操作系统的TCP发送缓冲区仅有25kB的剩余空间。这意味着剩下的15kB数据无法直接通过socket发送。为了避免阻塞当前线程,网络库需要将这15kB数据缓存到TCP连接的应用层发送缓冲区中,等socket变得可写时立即发送。这样可以避免发送操作被阻塞。
此外,如果随后又有50kB的数据需要发送,而发送缓冲区中尚有未发送的数据,则需要将新数据追加到缓冲区的末尾,而不是立即尝试写入。这是为了确保数据的传输顺序不会被打乱。
对于部分数据未读取的情况,网络库也需要能够暂存已经读到的数据,等待剩余数据到达后再进行处理。


缓冲区设计的原则

在设计缓冲区时需要权衡以下几点:

  • 减少系统调用:一次读取尽可能多的数据,这样可以减少频繁的系统调用,提升性能。
  • 减少内存占用:如果有10000个并发连接,每个连接都分配一个50kB的读写缓冲区,内存占用将高达1GB。为了优化内存占用,可以使用readv(2)函数结合栈上空间来处理。

  • 建立缓冲区的方式

    在实际实现中,可以采用以下几种方式来建立缓冲区:

    1. 每次都重新申请缓冲区

    这种方法适用于小数据量的场景。每次接收数据时,动态申请一个缓冲区,将数据填入其中,然后将连接信息和缓冲区内容压入任务队列。任务线程在处理时会清空缓冲区。

    优点:接收线程可以独立运行,任务线程负责处理数据,避免了锁的竞争。
    缺点:每次都动态申请内存(如malloc),内存分配和释放的开销较大。

    2. 预先申请缓冲区

    为每个连接预先分配一个大缓冲区。接收线程在缓冲区尾部插入新数据,任务线程处理时将缓冲区中的数据清空,并将后续数据前移。

    优点:减少了动态内存分配的开销。
    缺点:插入和使用数据时需要加锁,且可能存在大量数据拷贝操作。

    3. 使用线程池

    每个线程独立读取数据,当数据满足一个包的大小时,就作为任务处理。新数据到达时启动一个新线程处理。

    优点:减少了锁的竞争,数据拷贝量也较少。
    缺点:线程上下文切换的开销较大,可能影响数据接收的吞吐量。


    读写大量内容的实现

    为了高效处理大量数据,可以自定义readn和writen函数:

    readn函数

    ssize_t readn(int fd, char *buf, int size) {    char *pbuf = buf;    int total = 0;    int nread;    for (; total < size; ) {        nread = read(fd, pbuf, size - total);        if (nread == 0) {            return total;        }        if (nread == -1) {            if (errno == EINTR) {                continue;            }            return -1;        }        total += nread;        pbuf += nread;    }    return total;}

    writen函数

    ssize_t writen(int fd, char *buf, int size) {    char *pbuf = buf;    int total = 0;    int nwrite;    for (; total < size; ) {        nwrite = write(fd, pbuf, size - total);        if (nwrite <= 0) {            if (nwrite == -1 && errno == EINTR) {                continue;            }            return -1;        }        total += nwrite;        pbuf += nwrite;    }    return total;}

    这两个函数可以分别用于读取和写入大量数据,确保数据传输的高效性和可靠性。

    上一篇:c++11&14-编译
    下一篇:linux网络编程系列(十二)--滑动窗口、拥塞控制、断线重连机制

    发表评论

    最新留言

    留言是一种美德,欢迎回访!
    [***.207.175.100]2025年04月03日 03时23分13秒