当前位置:   article > 正文

8.添加Buffer类,缓冲区很有必要_为什么hp接口需要buffer做缓冲

为什么hp接口需要buffer做缓冲

这节需要我们明白为什么non_blocking网络编程中应用层的buffer是必要的。这节的很多内容都是陈硕《Linux多线程服务器编程》书中的内容原话。

为什么non_blocking网络编程中应用层的buffer是必要的

在使用epoll中,我们一般是搭配非阻塞IO一起使用,而非阻塞IO的核心思想是避免阻塞在read()或write()或其他IO系统调用上,这样可以最大限度的复用thread-of-control。IO线程只能阻塞在IO multiplexing函数上,如select/poll/epoll_wait。这样一来,应用层的缓冲是必要的,每个 TCP sockt都需要有stateful的input buffer和output buffer。

与客户端的通信中(Connetcin)需要有output buffer。

假设程序想要发送100KB的数据,但是在调用write中,操作系统只接受了80KB(受TCP advertised window控制),那这时调用者肯定不想原地等待。如果有buffer,调用者只管将数据放入buffer,其它由网络库处理即可。对于应用程序而言,它不应该关心数据到底是一次性发送还是分成几次发送的,这些都由网络库来操心

与客户端的通信中(Connetcin)需要有input buffer 

TCP是一个无边界的字节流协议,接收方必须要处理“收到的数据尚不构成一条完整的消息”和“一次收到两条消息的数据”等情况。如果有buffer,网络库收到数据之后,先放到input buffer,等构成一条完整的消息再通知程序进行业务逻辑的操作。

Buffer类

数据结构图

  • prependable :可以表示数据包的字节数
  • readerIndex:应用程序从readerIndex指向的位置开始读缓冲区,[readerIndex, writerIndex]表示待读取的数据,读完后向readerIndex后移动len(使用retrieve函数)
  • writerIndex:应用程序从writerIndex指向的位置开始写缓冲区,写完后writerIndex向后移动len长度(使用append函数)
  • [readerIndex_, writerIndex_]:标识可读数据区间(使用readableBytes函数得到区间大小)
  1. class Buffer
  2. {
  3. public:
  4. static const size_t KCheapPprepend = 8; //数据包长度8字节
  5. static const size_t KInitailSize = 1024;//缓冲区初始的大小
  6. explicit Buffer(size_t initialSize=KInitailSize)
  7. :buffer_(KCheapPprepend+initialSize)
  8. ,readerIndex_(KCheapPprepend)
  9. ,writerIndex_(KCheapPprepend)
  10. {}
  11. //待读取的数据大小
  12. const size_t readableBytes()const {
  13. return writerIndex_ - readerIndex_;
  14. }
  15. //可写的空闲空间大小
  16. const size_t writeableBytes()const {
  17. return buffer_.size() - writerIndex_;
  18. }
  19. const size_t prependableBytes()const {
  20. return readerIndex_;
  21. }
  22. private:
  23. std::vector<char> buffer_;
  24. size_t readerIndex_;
  25. size_t writerIndex_;
  26. };

成员函数

1. peek()函数

就是返回缓冲区中可读取的起始地址

  1. //缓冲区中可读取的起始位置
  2. const char* peek()const {
  3. return begin() + readerIndex_;
  4. }
  5. //返回Buffer底层的数据首地址
  6. char* begin() {
  7. return &*buffer_.begin();
  8. }
  9. //常对象只能调用常方法,不能调用普通的,而这里是一定要使用const char*,用char*会出现 "错误:从“const char*”到“char*”的无效转换[-fpermissive]"
  10. const char* begin()const {
  11. return &*buffer_.begin();
  12. }

2. retrieve函数

用在读取了buffer中的可读取的数据后,更新readerIndex_,即是把readerIndex_往后移动len长度。

  1. //读取了数据后,更新readerIndex_
  2. void retrieve(size_t len) {
  3. if (len < readableBytes()) {
  4. readerIndex_ += len;
  5. }
  6. else {
  7. readerIndex_ = KCheapPprepend;
  8. writerIndex_ = KCheapPprepend;
  9. }
  10. }

3. append函数

不管是从fd中读取数据到缓冲区inputBuffer_,还是发送数据也要写入outputBuffer_,都是要往writeable区间内添加数据

  1. void append(const char* data, size_t len) {
  2. if (writeableBytes() < len) {
  3. makeSpace(len); //扩容
  4. }
  5. std::copy(data, data + len, beginWirte());
  6. writerIndex_ += len;
  7. }
  8. char* beginWirte() {
  9. return begin() + writerIndex_;
  10. }
  11. const char* beginWirte()const {
  12. return begin() + writerIndex_;
  13. }
  14. void makeSpace(size_t len) {
  15. if (writeableBytes() + prependableBytes() < len + KCheapPprepend) {
  16. buffer_.resize(writerIndex_ + len);
  17. }
  18. else {
  19. auto readable = readableBytes();
  20. //挪动
  21. std::copy(begin() + readerIndex_, begin() + writerIndex_, begin() + KCheapPprepend);
  22. //更新readerIndex_, writerIndex_
  23. readerIndex_ = KCheapPprepend;
  24. writerIndex_ = readerIndex_ + readable;
  25. }
  26. }

4. readFd()函数

从fd上读取数据,存放到writeIndex_指向的地址,返回实际读取的数据大小

  1. ssize_t Buffer::readFd(int fd, int* saveErrno)
  2. {
  3. char extrabuffer[65535];
  4. struct iovec vec[2];
  5. auto writable = writeableBytes();
  6. vec[0].iov_base = begin() + writerIndex_; //第一块缓冲区
  7. vec[0].iov_len = writable; //iov_base缓冲区可写的空间大小
  8. vec[1].iov_base = extrabuffer; //第二快缓冲区
  9. vec[1].iov_len = sizeof(extrabuffer);
  10. //若Buffer有65535字节的空间空间,就不适用栈上的缓冲区
  11. auto iovcnt = (writable < sizeof(extrabuffer)) ? 2 : 1;
  12. auto n = ::readv(fd, vec, iovcnt);
  13. if (n < 0) {
  14. *saveErrno = errno;
  15. }
  16. else if (static_cast<size_t>(n) <= writable) {
  17. writerIndex_ += n;
  18. }
  19. else {
  20. //Buffer底层的可写空间不够存放n字节数据,
  21. writerIndex_ = buffer_.size(); //更新writerIndex_为末尾,再使用append
  22. append(extrabuffer, n - writable);
  23. }
  24. return n;
  25. }

Buffer缓冲区是有大小的(占用堆区内存),但是我们无法知道fd上的流式数据有多少,如果我们将缓冲区开的非常大,大到肯定是能容纳所有读取的数据,这就太浪费空间了,muduo使用readv(2)结合栈上空间巧妙解决了这种问题。

1.如果读取的数据不多并且Buffer写空间大小满足,则直接写入Buffer。
2。如果读取的数据长度超过Buffer写空间大小,超过的部分会读取到栈空间extrabuf,
然后程序再把extrabuf里的数据append()到buffer中。

 

添加了Buffer类后使用,在这节中用法变化不大,在Server类中添加了成员变量Buffer inputBuffer_,并且在成员函数handleEvent()中使用inputBuffer_。

完整源代码:https://github.com/liwook/CPPServer/tree/main/code/server_v8

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/601707
推荐阅读
相关标签
  

闽ICP备14008679号