我有一个在Linux Debian 9下运行的c ++程序。我正在从文件描述符中进行简单的read():
int bytes_read = read(fd, buffer, buffer_size);
Run Code Online (Sandbox Code Playgroud)
想象一下,我想从套接字读取更多数据,但是我想先跳过一个已知的字节数,然后再获取我感兴趣的某些内容:
int unwanted_bytes_read = read(fd, unwanted_buffer, bytes_to_skip);
int useful_bytes = read(fd, buffer, buffer_size);
Run Code Online (Sandbox Code Playgroud)
在Linux中,是否存在一个系统范围内的“内置”位置,我可以将不需要的字节转储到该位置,而不必维护不需要的数据的缓冲区(unwanted_buffer如上例所示)?
我想我要寻找的东西MSG_PEEK与套接字世界相反(即),也就是说,内核将bytes_to_skip在下一个有用的recv调用之前从其接收缓冲区中清除。
如果我正在从文件中读取内容,lseek那就足够了。但是,如果您正在从套接字读取并且正在使用分散/聚集I / O,并且要删除其中一个字段,则这是不可能的。
我正在考虑这样的事情:
// send side
int a = 1;
int b = 2;
int c = 3;
struct iovec iov[3];
ssize_t nwritten;
iov[0].iov_base = &a;
iov[0].iov_len = sizeof(int);
iov[1].iov_base = &b;
iov[1].iov_len = sizeof(int);
iov[2].iov_base = &c;
iov[2].iov_len = sizeof(int);
nwritten = writev(fd, iov, 3);
// receive side
int a = -1;
int c = -1;
struct iovec iov[3]; // you know that you'll be receiving three fields and what their sizes are, but you don't care about the second.
ssize_t nread;
iov[0].iov_base = &a;
iov[0].iov_len = sizeof(int);
iov[1].iov_base = ??? <---- what to put here?
iov[1].iov_len = sizeof(int);
iov[2].iov_base = &c;
iov[2].iov_len = sizeof(int);
nread = readv(fd, iov, 3);
Run Code Online (Sandbox Code Playgroud)
我知道我可以b在接收端创建另一个变量,但是如果我不想这样做,我如何读取sizeof(int)文件中占用的字节,然后转储数据并继续进行操作c?我可以创建一个通用缓冲区以转储b到其中,我只想问默认情况下是否存在这样的位置。
[编辑]
遵循@inetknght的建议,我尝试了内存映射/ dev / null并将收集到的映射地址中:
int nullfd = open("/dev/null", O_WRONLY);
void* blackhole = mmap(NULL, iov[1].iov_len, PROT_WRITE, MAP_SHARED, nullfd, 0);
iov[1].iov_base = blackhole;
nread = readv(fd, iov, 3);
Run Code Online (Sandbox Code Playgroud)
但是,blackhole出来的时候0xffff,我得到了错误号13“权限被拒绝”。我尝试以su身份运行我的代码,但这也不起作用。也许我设置mmap不正确?
最后有一个 tl;dr 。
在我的评论中,我向您推荐了mmap()该/dev/null设备。然而,该设备似乎无法在我的机器上映射(错误19:)No such device。不过看起来/dev/zero是可以映射的。另一个问题/答案表明,这相当于首先MAP_ANONYMOUS使fd论证及其相关内容变得不必要。open()看一个例子:
#include <iostream>
#include <cstring>
#include <cerrno>
#include <cstdlib>
extern "C" {
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
}
template <class Type>
struct iovec ignored(void *p)
{
struct iovec iov_ = {};
iov_.iov_base = p;
iov_.iov_len = sizeof(Type);
return iov_;
}
int main()
{
auto * p = mmap(nullptr, 4096, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if ( MAP_FAILED == p ) {
auto err = errno;
std::cerr << "mmap(MAP_PRIVATE | MAP_ANONYMOUS): " << err << ": " << strerror(err) << std::endl;
return EXIT_FAILURE;
}
int s_[2] = {-1, -1};
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, s_);
if ( result < 0 ) {
auto err = errno;
std::cerr << "socketpair(): " << err << ": " << strerror(err) << std::endl;
return EXIT_FAILURE;
}
int w_[3] = {1,2,3};
ssize_t nwritten = 0;
auto makeiov = [](int & v){
struct iovec iov_ = {};
iov_.iov_base = &v;
iov_.iov_len = sizeof(v);
return iov_;
};
struct iovec wv[3] = {
makeiov(w_[0]),
makeiov(w_[1]),
makeiov(w_[2])
};
nwritten = writev(s_[0], wv, 3);
if ( nwritten < 0 ) {
auto err = errno;
std::cerr << "writev(): " << err << ": " << strerror(err) << std::endl;
return EXIT_FAILURE;
}
int r_ = {0};
ssize_t nread = 0;
struct iovec rv[3] = {
ignored<int>(p),
makeiov(r_),
ignored<int>(p),
};
nread = readv(s_[1], rv, 3);
if ( nread < 0 ) {
auto err = errno;
std::cerr << "readv(): " << err << ": " << strerror(err) << std::endl;
return EXIT_FAILURE;
}
std::cout <<
w_[0] << '\t' <<
w_[1] << '\t' <<
w_[2] << '\n' <<
r_ << '\t' <<
*(int*)p << std::endl;
return EXIT_SUCCESS;
}
Run Code Online (Sandbox Code Playgroud)
在上面的示例中,您可以看到我创建了一个fork()4KiB(在大多数系统上为单页大小)的私有(写入之后子级将不可见)匿名(不由文件支持)内存映射。然后它被使用两次来为两个 int 提供写入目的地——后一个 int 覆盖前一个 int。
这并不能完全解决你的问题:如何忽略字节。由于您正在使用readv(),我研究了它的姊妹函数,preadv()乍一看似乎可以执行您希望它执行的操作:跳过字节。但是,套接字文件描述符似乎不支持这一点。下面的代码给出了preadv(): 29: Illegal seek.
rv = makeiov(r_[1]);
nread = preadv(s_[1], &rv, 1, sizeof(int));
if ( nread < 0 ) {
auto err = errno;
std::cerr << "preadv(): " << err << ": " << strerror(err) << std::endl;
return EXIT_FAILURE;
}
Run Code Online (Sandbox Code Playgroud)
所以它看起来甚至在引擎盖下preadv()使用seek(),当然,在套接字上是不允许的。我不确定是否有(还?)一种方法告诉操作系统忽略/删除已建立的流中收到的字节。我怀疑这是因为@geza 是正确的:对于我遇到的大多数情况来说,写入最终(忽略)目的地的成本非常微不足道。而且,在被忽略字节的成本并非微不足道的情况下,您应该认真考虑使用更好的选项、实现或协议。
长话短说:
创建 4KiB 匿名私有内存映射实际上与连续分配容器没有区别(存在细微的差异,对于非常高端性能之外的任何工作负载来说不太重要)。使用标准容器也不太容易出现分配错误:内存泄漏、野指针等。所以我会说 KISS 并且这样做,而不是认可我上面写的任何代码。例如:std::array<char, 4096> ignored;或std::vector<char> ignored{4096};并设置iovec.iov_base = ignored.data();并将其设置.iov_len为您需要忽略的任何大小(在容器的长度内)。
| 归档时间: |
|
| 查看次数: |
179 次 |
| 最近记录: |