linux管道命令recvBuf,Linux系统编程—管道

Linux 实现 IPC 其中的一种方式——管道

管道又分:

1、无名管道:无名管道只能用于有亲缘关系的进程。

2、有名管道:有名管道用于任意两进程间通信。

无名管道

你就可以把管道理解成位于进程内核空间的“文件”。

d341b996288d

图1 假想的位于内核的管道文件.png

给文件加引号,是因为它和文件确实很像,因为它也有描述符。但是它确实又不是普通的本地文件,而是一种抽象的存在。

当进程使用 pipe 函数,就可以打开位于内核中的这个特殊“文件”。同时 pipe 函数会返回两个描述符,一个用于读,一个用于写。如果你使用 fstat函数来测试该描述符,可以发现此文件类型为 FIFO。

而无名管道的无名,指的就是这个虚幻的“文件”,它没有名字。本质上,pipe 函数会在进程内核空间申请一块内存(比如一个内存页,一般是 4KB),然后把这块内存当成一个先进先出(FIFO)的循环队列来存取数据,这一切都由操作系统帮助我们实现了。

pipe 函数

int pipe(int pipefd[2]);

pipe 函数打开的文件描述符是通过参数(数组)传递出来的,而返回值表示打开成功(0)或失败(-1)。

它的参数是一个大小为 2 的数组。此数组的第 0 个元素用来接收以读的方式打开的描述符,而第 1 个元素用来接收以写的方式打开的描述符。也就是说,pipefd[0] 是用于读的,而 pipefd[1] 是用于写的。

打开了文件描述符后,就可以使用 read(pipefd[0]) 和 write(pipefd[1]) 来读写数据了。

注意事项

1、这两个分别用于读写的描述符必须同时打开才行,否则会出问题。

2、如果关闭读 (close(pipefd[0])) 端保留写端,继续向写端 (pipefd[1]) 端写数据(write 函数)的进程会收到 SIGPIPE 信号。

3、如果关闭写 (close(pipefd[1])) 端保留读端,继续向读端 (pipefd[0]) 端读数据(read 函数),read 函数会返回 0。

pipe 进程间的通信

当在进程用 pipe 函数打开两个描述符后,我们可以 fork 出一个子进程。这样,子进程也会继承这两个描述符,而且这两个文件描述符的引用计数会变成 2。

如果你需要父进程向子进程发送数据,那么得把父进程的 pipefd[0] (读端)关闭,而在子进程中把 pipefd[1] 写端关闭,反之亦然。为什么要这样做?实际上是避免出错。传统上 pipe 管道只能用于半双工通信(即一端只能发,不能收;而另一端只能收不能发),为了安全起见,各个进程需要把不用的那一端关闭(本质上是引用计数减 1)。

步骤一:fork 子进程

d341b996288d

图2 fork 后的半双工管道 .png

步骤二:关闭父进程读端,关闭子进程写端

d341b996288d

图3 从父进程到子进程的管道 .png

实例

父进程 fork 出一个子进程,通过无名管道向子进程发送字符,子进程收到数据后将字符串中的小写字符转换成大写并输出。

#include

#include

#include

#include

void child(int *fd) {

close(fd[1]); // 子进程关闭写端

char buf[64];

int n = 0,i;

while(1) {

n = read(fd[0], buf, 64); // 如果没有数据可读,read 会阻塞;如果父进程退出,read 返回 0.

for (i = 0; i < n; ++i) putchar(toupper(buf[i]));

if (*buf == 'q') {

close(fd[0]);

exit(0);

}

if (n == 0) {

puts("no data to read!");

sleep(1);

}

}

exit(0);

}

int main() {

int fd[2];

int n = 0;

char buf[64] = { 0 };

if (pipe(fd) < 0) {

perror("pipe");

return -1;

}

pid_t pid = fork();

if (pid == 0) {

child(fd);

}

close(fd[0]);// 父进程关闭读端

while (1) {

n = read(STDIN_FILENO, buf, 64);

write(fd[1], buf, n);

if (*buf == 'q') {

close(fd[1]);

exit(0);

}

}

return 0;

}

有名管道

有名管道打破了无名管道的限制,进化出了一个实实在在的 FIFO 类型的文件。这意味着即使没有亲缘关系的进程也可以互相通信了。所以,只要不同的进程打开 FIFO 文件,往此文件读写数据,就可以达到通信的目的。

创建 FIFO 类型文件

1、文件属性前面标注的文件类型是 p

2、代表管道文件大小是 0

3、fifo 文件需要有读写两端,否则在打开 fifo 文件时会阻塞

通过命令 mkfifo 创建

mkfifo test

通过函数 mkfifo创建

int mkfifo(const char *pathname, mode_t mode)

函数返回 0 表示成功,-1 失败。

例如:

mkfifo("hello", 0664);

FIFO 文件的特性

cat 命令打印 test文件内容

cat test

接下来你的 cat 命令被阻塞住。

开启另一个终端,执行:

echo "hello world" > test

然后你会看到被阻塞的 cat 又继续执行完毕,在屏幕打印 “hello world”。如果你反过来执行上面两个命令,会发现先执行的那个总是被阻塞。

实例

有两个程序,分别是发送端 send 和接收端面 recv。程序 send 从标准输入接收字符,并发送到程序 recv,同时 recv 将接收到的字符打印到屏幕。

发送端

#include

#include

#include

#include

#include

int main() {

char buf[64];

int n = 0;

int fd = open("test", O_WRONLY);

if (fd < 0) { perror("open fifo"); return -1;}

puts("has opend fifo");

while((n = read(STDIN_FILENO, buf, 64)) > 0) {

write(fd, buf, n);

if (buf[0] == 'q') break;

}

close(fd);

return 0;

}

接收端

#include

#include

#include

#include

#include

int main() {

char buf[64];

int n = 0;

int fd = open("test", O_RDONLY);

if (fd < 0) { perror("open fifo"); return -1;}

puts("has opened fifo");

while((n = read(fd, buf, 64)) > 0) {

write(STDOUT_FILENO, buf, n);

}

if (n == 0) {

puts("remote closed");

}

else {

perror("read fifo");

return -1;

}

close(fd);

return 0;

}

编译

gcc send.c -o send

gcc recv.c -o recv

运行

./send

因为 recv 端还没打开test文件,这时候 send 是阻塞状态的。

再开启另一个终端:

./recv

这时候 send 端和 recv 端都在终端显示has opend fifo

此时在 send 端输入数据,recv 打印。

d341b996288d

image.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux C++中,可以通过`setsockopt`函数设置套接字的接收缓存大小。以下是一个示例代码: ```c++ // 创建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockfd == -1) { perror("Create socket failed"); return 1; } // 设置接收缓存大小为2048字节 int recvBufSize = 2048; if (setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &recvBufSize, sizeof(recvBufSize)) == -1) { perror("Set socket receive buffer size failed"); close(sockfd); return 1; } // 连接到目标主机 struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = inet_addr("目标主机IP地址"); serverAddr.sin_port = htons(目标主机端口号); if (connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) { perror("Connect failed"); close(sockfd); return 1; } // 接收数据 char recvBuf[2048]; ssize_t recvLen = recv(sockfd, recvBuf, sizeof(recvBuf), 0); if (recvLen == -1) { perror("Receive data failed"); close(sockfd); return 1; } // 关闭套接字 close(sockfd); ``` 在上面的代码中,我们通过调用`setsockopt`函数来设置套接字的接收缓存大小,然后通过调用`recv`函数来接收数据。需要注意的是,设置套接字的接收缓存大小可能会影响网络性能,因此应该根据具体情况进行适当的调整。 另外,Linux系统也提供了`sysctl`命令来动态调整网络参数,包括接收缓存大小。您可以使用`sysctl`命令来查询和修改系统参数,例如: ```sh # 查询当前的TCP接收缓存大小 sysctl net.ipv4.tcp_rmem # 修改TCP接收缓存大小为2048, 4096, 8192字节 sysctl -w net.ipv4.tcp_rmem="2048 4096 8192" ``` 需要注意的是,修改系统参数可能会影响整个系统的性能和稳定性,因此应该根据具体情况进行适当的调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值