execl对重定向的影响、execl对缓冲区类型的影响
——lvyilong316
这篇文章我们将要探讨三个问题:
(1) 对打开的文件描述符或者标准输入输出重定向后,在通过execl执行新的进程,重定向的效果还能保持吗?
(2) 如果我们将标准输出设置为无缓冲(默认是行缓冲)的,那么execl执行新的进程后,新进程的标准输出缓冲类型还是无缓冲吗?
(3) 文件描述符重定向对和文件描述符关联的IO流有什么影响?
为了回答以上三个问题,我们通过一个例子来说明和验证。
有python程序pyth.py如下:
-
#! /usr/bin/python
-
import sys
-
import os
-
print 'Hi!'
-
while(raw_input()):
-
print 'hello'
-
print 'world'
其功能是首先输出’Hi!’,随后在控制台上输入任何数据回车后就输出”hello world”(分两行输出),运行效果如图1.
图1
我们想实现的效果是从一个机器上的客户端向这个python程序发送命令(相当于在控制台上输入数据),然后接收程序返回来的”hello world”串。
总体设计方案如图2。
图2
当服务端的server程序收到客户端的连接后,就创建子进程,并且将子进程的标准输入、标准输出、标准错误重定向到已连接的套接字,然后execl执行pyth.py,这样客户端client和pyth.py程序的交互就相当于在本机控制台上交互是一样的了。
编写客户端程序tcpclient.c如下:
l tcpClient.c
-
#include <stdio.h>
-
#include <string.h>
-
#include <unistd.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <stdlib.h>
-
#include <memory.h>
-
#include <arpa/inet.h>
-
#include <netinet/in.h>
-
#define PORT 9999
-
#define Buflen 1024
-
int main(int argc,char *argv[])
-
{
-
struct sockaddr_in server_addr;
-
int n,err;
-
int sockfd;
-
char recvline[Buflen];
-
char* cmd="a\n";
-
setbuf(stdout,NULL);
-
/********************socket()*********************/
-
sockfd= socket(AF_INET,SOCK_STREAM,0);
-
/*******************connect()*********************/
-
//设置服务器地址结构,准备连接到服务器
-
memset(&server_addr,0,sizeof(server_addr));
-
server_addr.sin_family = AF_INET;
-
server_addr.sin_port = htons(PORT);
-
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
server_addr.sin_addr.s_addr = inet_addr(argv[1]);
-
err = connect(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
-
if(err == 0)
-
{
-
printf("client : connect to server\n");
-
}
-
else
-
{
-
printf("client : connect error\n");
-
return -1;
-
}
-
//与服务器端进行通信
-
memset(recvline,0,sizeof(recvline));
-
if( (n=read(sockfd,recvline,Buflen))>0 )
-
{
-
recvline[n]=0;
-
printf("%s",recvline);
-
}
-
-
write(sockfd,cmd,strlen(cmd)); //这里相当于在pyth.py的标准输入上输入数据
-
while( (n=read(sockfd,recvline,Buflen))>0 )
-
{
-
recvline[n]='\0';
-
printf("%s",recvline);
-
}
-
close(sockfd);
-
}
-
编写服务端tcpServer.c如下。
l tcpServer.c
点击(此处)折叠或打开
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <strings.h>
-
#include <sys/types.h>
-
#include <sys/socket.h>
-
#include <memory.h>
-
#include <unistd.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
#include <string.h>
-
#define PORT 9999 //定义通信端口
-
#define BACKLOG 5 //定义侦听队列长度
-
#define buflen 1024
-
int listenfd,connfd;
-
int main(int argc,char *argv[])
-
{
-
struct sockaddr_in server_addr; //存储服务器端socket地址结构
-
struct sockaddr_in client_addr; //存储客户端 socket地址结构
-
int err; //返回值
-
pid_t pid;
-
/*****************socket()***************/
-
listenfd = socket(AF_INET,SOCK_STREAM,0);
-
/******************bind()****************/
-
//初始化地址结构
-
memset(&server_addr,0,sizeof(server_addr));
-
server_addr.sin_family = AF_INET; //协议族
-
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址
-
server_addr.sin_port = htons(PORT);
-
err = bind(listenfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
-
if(err<0)
-
{
-
printf("server : bind error\n");
-
return -1;
-
}
-
/*****************listen()***************/
-
err = listen(listenfd,BACKLOG); //设置监听的队列大小
-
if(err < 0)
-
{
-
printf("server : listen error\n");
-
return -1;
-
}
-
for(;;)
-
{
-
socklen_t addrlen = sizeof(client_addr);
-
//accept返回客户端套接字描述符
-
connfd = accept(listenfd,(struct sockaddr *)&client_addr,&addrlen);
-
-
if((pid = fork()) == 0) //子进程,与客户端通信
-
{
-
close(listenfd);
-
dup2(connfd,STDOUT_FILENO); //重定向标准输出
-
dup2(connfd,STDERR_FILENO); //重定向标准错误
-
dup2(connfd,STDIN_FILENO); //重定向标准输入
-
if ( (err=execl("./pyth.py",(char*)0))<0 ) //执行pyth.py
-
{
-
perror("execl error");
-
exit(1);
-
}
-
}
-
else
-
{
-
close(connfd);
-
}
-
}
-
}
分别编译运行tcpServer.c为server,编译tcpClient.c为client并运行,效果如图3.
图3
我们发现客户端并没有收到服务端pyth.py输出的”Hi!”。难道是重定向的问题吗?经过考虑和一些验证我觉得问题应该在标准IO流的缓冲。我们知道标准IO的默认缓冲类型是:
(1) 标准错误是不带缓冲的;
(2) 若涉及终端设备,则标准输入和输出为行缓冲,否则是全缓冲。
这里标准输入和输出重定向到了已连接套接字connfd,所以是全缓冲,即只有缓冲区满或者显式flush缓冲区中的内容才会真正输出。所以字符串”Hi!”应该停留在pyth.py的缓冲区中。
随后对程序tcpServer.c修改如下:
-
if((pid = fork()) == 0) //子进程,与客户端通信
-
{
-
close(listenfd);
-
setbuf(stdout,NULL); //将标准输入、输出、错误都设置成无缓冲
-
setbuf(stdin,NULL);
-
setbuf(stderr,NULL);
-
dup2(connfd,STDOUT_FILENO); //将标准输入、输出、错误重定向到connfd
-
dup2(connfd,STDERR_FILENO);
-
dup2(connfd,STDIN_FILENO);
-
if ( (err=execl("./pyth.py",(char*)0))<0 ) //执行pyth.py
-
{
-
perror("execl error");
-
exit(1);
-
}
-
}
之后再次运行server和client效果依然如图3所示。这是为什么呢?我们首先回答之前的两个问题:
l execl不影响之前打开的文件描述符,包括重定向的描述符,所以我们可以先将标准输入、输出、错误重定向,之后execl执行的新程序的标准输入、输出、错误依然保持之前的重定向。
l 每个程序有一个自己标准输入、输出、错误的IO流。当我们将这些IO流的缓冲区类型(行缓冲、全缓冲、无缓冲)改变后不影响execl后新进程的IO流类型。
所以之前我们在tcpServer.c中设置的IO缓冲类型其实对之后执行的pyth.py是无效的,那我们想改变pyth.py的IO缓冲类型应该怎么做呢?很简单,在pyth.py中设置就可以了。Python中设置标准输出为无缓冲有以下三种方法:
(1) 对python加-u参数
(2) 加 PYTHONUNBUFFERED环境变量
(3) sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)
我们采用第三种方法,添加 sys.stdout = os.fdopen(sys.stdout.fileno(), 'w', 0)到pyth.py中。再次运行client,结果如图4所示。
图4
我们看到得到了我们想要的结果,服务端server将pyth.py的输出返回给了client。
现在我们来考虑第三个问题——重定向对标准IO流有什么影响?一个IO流是由一个FILE对象来管理的,FILE对象通常是一个结构,它包含了标准IO库为管理该流的所需要的所有信息,包括:用于实际I/O的文件描述符、指向用于该流缓冲区的指针、缓冲区的长度、当前在缓冲区中的字符数以及出错标志等等——摘自apue
下面通过图5来说明重定向与标准IO流的关系。
图5
从图5我们有如下结论:
l 重定向只是改变了FILE对象中实际关联的fd(对于我们的程序来说是由终端fd变为了已连接的connfd);
l 重定向后,根据实际关联fd指向的对象确定IO流缓冲区(图中绿色部分)的类型,是行缓冲、全缓冲、或无缓冲。对于我们之前的程序由于connfd指向的不是终端设备,所以标准输入、输出为全缓冲,标准错误仍然是无缓冲。
l 要区别标准IO流的缓冲(图中绿色部分)和套接字的发送缓冲(图中黄色部分),前者被IO流所管理,位于用户空间,后者由内核协议栈来管理,位于内和空间。
l 设置缓冲区的类型是指设置用户空间(绿色部分的缓冲)而不是内和空间(黄色部分的缓冲)。
阅读(7923) | 评论(0) | 转发(2) |