当前位置: 首页 > news >正文

关于“网络编程“组件之 “Buffer“


本文立足TCP协议以及LINUXS socket 编程
用户空间:用户空间是操作系统中用户程序运行的环境
内核空间:是操作系统内核运行的环境,(包含内核代码、数据结构和系统资源。是网络协议栈工作的地方)


1TCP协议的缓冲区

TCP 是一种面向连接的、可靠的传输层协议,它通过缓冲区来管理数据的发送和接收。TCP 缓冲区存储在操作系统的内核空间中。(由操作系统决定)

1.1 TCP 缓冲区的定义与作用

TCP 缓冲区分为两种:
发送缓冲区(Send Buffer):存储应用程序发送但尚未被 TCP 协议确认的数据。
接收缓冲区(Receive Buffer):存储从网络接收到但尚未被应用程序读取的数据。

1.1.1 发送缓冲区

定义:发送缓冲区是 TCP 协议在发送端为应用程序提供的一个缓冲区域,用于存储应用程序已经写入但尚未被 TCP 协议发送到网络中的数据。
作用

  • 提高性能:允许应用程序快速写入数据,而不需要等待网络传输完成。
  • 流量控制:通过缓冲区,TCP 可以根据网络状况逐步发送数据,避免网络拥塞。
  • 解耦应用与网络:应用程序可以快速写入数据,而 TCP 协议会负责将数据逐步发送到网络
1.1.2 接收缓冲区

定义:接收缓冲区是 TCP 协议在接收端为应用程序提供的一个缓冲区域,用于存储从网络接收到但尚未被应用程序读取的数据。
作用:

  • 提高性能:允许网络数据逐步到达,而应用程序可以按需读取。
  • 流量控制:通过缓冲区,TCP 可以根据接收端的处理能力,通知发送端调整发送速率。
  • 解耦网络与应用:网络数据可以逐步到达,而应用程序可以按需读取,避免数据丢失。

1.2 TCP 缓冲区的行为与限制

1.2.1 发送缓冲区的行为
  • 写入数据:当应用程序调用 send 或 write 时,数据会被写入发送缓冲区。
  • 缓冲区已满:如果发送缓冲区已满,send 或 write 会阻塞,直到缓冲区中有空间。
  • 数据发送:TCP 协议会从发送缓冲区中读取数据,逐步发送到网络中。
  • 确认机制:发送的数据需要等待接收方的确认(ACK),只有在收到确认后,数据才会从发送缓冲区中移除。(非三次确定 不需要验证要读取能力)
接收缓冲区的行为
  • 接收数据:当网络数据到达时,TCP 协议会将数据存储到接收缓冲区。
  • 缓冲区已满:如果接收缓冲区已满,TCP 会通过滑动窗口机制通知发送方减慢发送速度。
  • 读取数据:当应用程序调用 read 或 recv 时,数据会从接收缓冲区中读取。
  • 数据处理:只有在应用程序读取数据后,缓冲区中的空间才会被释放。
1.2.3 TCP 缓冲区的大小
  • 默认大小
sysctl net.core.rmem_default  # 接收缓冲区默认大小
sysctl net.core.wmem_default  # 发送缓冲区默认大小
  • 动态调整
int size = 1024 * 1024; // 1MB
setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &size, sizeof(size));//发送
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size));//接受

缓冲区大小的影响

发送缓冲区:

  • 较大的发送缓冲区可以提高吞吐量,但会增加内存占用。
  • 较小的发送缓冲区可能导致频繁阻塞,降低性能。
    接收缓冲区:
  • 较大的接收缓冲区可以更好地处理突发流量,但会增加内存占用。
  • 较小的接收缓冲区可能导致数据丢失或发送方发送速率受限

对于高吞吐量的场景(如文件传输),可以增大缓冲区。
对于低延迟的场景(如实时通信),可以减小缓冲区。

2 Socket IO 读取的细节

2.1 Socket IO 读取的行为

  • 阻塞模式
    行为:当应用程序调用 read 或 recv 时,如果接收缓冲区中没有数据,系统调用会阻塞,直到数据到达或超时。
    优点:代码简单,适合低并发场景。
    缺点:在高并发场景下,阻塞会导致资源浪费。
  • 非阻塞模式
    行为:当应用程序调用 read 或 recv 时,如果接收缓冲区中没有数据,系统调用会立即返回错误(如 EWOULDBLOCK 或 EAGAIN)。
    优点:适合高并发场景,可以提高资源利用率。
    缺点:需要额外的机制(如 select、poll 或 epoll)来检测数据是否到达

设置非阻塞

void Socket::setnonblocking() {
    fcntl(fd,F_SETFL,fcntl(fd,F_GETFD)|O_NONBLOCK);
}

2.2 Socket IO的函数

read

定义:read 是一个通用的系统调用,用于从文件描述符(如文件、管道、socket 等)中读取数据。
功能:简单地从文件描述符中读取数据,不提供额外的控制选项。
函数原型ssize_t read(int fd, void *buf, size_t count);

fd:文件描述符。
buf:存储读取数据的缓冲区。
count:要读取的最大字节数。

返回值

返回值 > 0:表示成功读取的字节数。例如,返回值为 10,表示成功读取了 10 个字节。
返回值 = 0:表示已经到达文件末尾(EOF),没有更多数据可读。这通常发生在读取文件时,文件内容已经全部读完//对于一个socket而言就是关系连接达到结束。
返回值 < 0:表示读取操作失败。此时,errno 会被设置为相应的错误码。

recv

定义:recv 是专门为 socket 设计的系统调用,用于从 socket 中读取数据。
功能:除了读取数据,还提供了额外的控制选项(如处理带外数据、查询数据长度等)。
函数原型ssize_t recv(int sockfd, void *buf, size_t len, int flags);

sockfd:socket 文件描述符。
buf:存储读取数据的缓冲区。
len:缓冲区的大小。
flags:控制读取行为的标志(如 MSG_PEEK、MSG_WAITALL、MSG_OOB 等)。

write

定义:write 是一个通用的系统调用,用于向文件描述符(如文件、管道、socket 等)写入数据。
功能:简单地将数据写入文件描述符,不提供额外的控制选项。
函数原型:ssize_t write(int fd, const void *buf, size_t count);

fd:文件描述符。
buf:要写入的数据缓冲区。
count:要写入的最大字节数。
返回值:成功时返回实际写入的字节数,失败时返回 -1,并设置 errno。

send

定义:send 是专门为 socket 设计的系统调用,用于向 socket 发送数据。
功能:除了发送数据,还提供了额外的控制选项(如处理带外数据、指示后续数据等)。
函数原型ssize_t send(int sockfd, const void *buf, size_t len, int flags);

sockfd:socket 文件描述符。
buf:要发送的数据缓冲区。
len:缓冲区的大小。
flags:控制发送行为的标志(如 MSG_OOB、MSG_CONFIRM、MSG_MORE

2.3 错误处理

正常返回

返回值 > 0:表示成功读取的字节数。
返回值 = 0:表示对端已关闭连接(EOF)。
返回值 < 0:表示读取失败,需要检查 errno 以确定错误原因。

错误处理

EINTR:系统调用被信号中断。
EAGAIN 或 EWOULDBLOCK:非阻塞模式下,接收缓冲区中没有数据。
ECONNRESET:连接被对端重置。
EPIPE:连接已断开。

3 实战


std::vector:
适合存储二进制数据或任意字节数据。
在需要频繁插入和删除的场景下表现较好。
适合需要对数据进行复杂操作的场景。
std::string:
适合存储文本数据,特别是需要使用字符串专用操作的场景。
在需要与 C 风格字符串交互的场景下更方便。
提供了许多方便的字符串处理函数。


创建一个简单缓冲区

#include <string>

class Buffer
{
private:
    std::string buf;
public:
    Buffer();
    ~Buffer();
    void append(const char* _str, int _size);//加入数据
    ssize_t size();//buf大小
    const char* c_str();//转化为c风格 方便与read 交互
    void clear();//清理
    void getline();//从标准输入 得到数据
};
#include "Buffer.h"
#include <string.h>
#include <iostream>
Buffer::Buffer()
{
}

Buffer::~Buffer()
{
}


void Buffer::append(const char* _str, int _size){
    for(int i = 0; i < _size; ++i){
        if(_str[i] == '\0') break;
        buf.push_back(_str[i]);
    }
}

ssize_t Buffer::size(){
    return buf.size();
}

const char* Buffer::c_str(){
    return buf.c_str();
}

void Buffer::clear(){
    buf.clear();
}

void Buffer::getline(){
    buf.clear();
    std::getline(std::cin, buf);
}

echo

void Connection::echo(int sockfd){
    char buf[1024];     //这个buf大小无所谓
    while(true){    //由于使用非阻塞IO,读取客户端buffer,一次读取buf大小数据,直到全部读取完毕
        bzero(&buf, sizeof(buf));
        ssize_t bytes_read = read(sockfd, buf, sizeof(buf));
        if(bytes_read > 0){
            readBuffer->append(buf, bytes_read);
        } else if(bytes_read == -1 && errno == EINTR){  //客户端正常中断、继续读取
            printf("continue reading");
            continue;
        } else if(bytes_read == -1 && ((errno == EAGAIN) || (errno == EWOULDBLOCK))){//非阻塞IO,这个条件表示数据全部读取完毕
            printf("message from client fd %d: %s\n", sockfd, readBuffer->c_str());
            errif(write(sockfd, readBuffer->c_str(), readBuffer->size()) == -1, "socket write error");
            readBuffer->clear();
            break;
        } else if(bytes_read == 0){  //EOF,客户端断开连接
            printf("EOF, client fd %d disconnected\n", sockfd);
            deleteConnectionCallback(sock);
            break;
        }
    }
}

相关文章:

  • Python中的数值运算函数及math库详解
  • 【vant 手机端封装表格】
  • C# virtual 和 abstract 详解
  • Android 中如何配置 targetSdk 值
  • 操作系统 4.1-I/O与显示器
  • MySQL 进阶 - 2 ( 15000 字详解)
  • 使用opentelemetry 可观测监控springboot应用的指标、链路实践,使用zipkin展示链路追踪数据,使用grafana展示指标
  • 2025年- H7-Lc114-15.三数之和(双指针)--java版
  • oracle update 原理
  • C++的inline函数
  • 如何用MDM批量配置和管理TV Box(机顶盒)?
  • E8流程多行明细行字符串用I分隔,赋值到主表
  • 记一次api接口出现重复请求的处理过程
  • 使用人工智能大模型腾讯元宝,如何免费快速做高质量的新闻稿?
  • 【 vue + js 】引入图片、base64 编译显示图片
  • 项目日志配置模板示例
  • python的多线程和多进程程序编程
  • 语音识别——根据声波能量、VAD 和 频谱分析实时输出文字
  • 企业数据孤岛如何破
  • Harmony实战之简易计算器
  • 哪个网站做二手车抵押/网络广告宣传平台
  • 一个网站多个域名/竞价托管哪家公司好
  • 做信息类网站/什么是企业营销型网站
  • 成年男女做羞羞视频网站/东莞网络公司电话
  • 郑州网站开发公司电话/给公司做网站要多少钱
  • 网站建设策划书的撰写/网站源码交易平台