Linux C:具有单独读写命名管道的“交互式会话”?

Posted

技术标签:

【中文标题】Linux C:具有单独读写命名管道的“交互式会话”?【英文标题】:Linux C: "Interactive session" with separate read and write named pipes? 【发布时间】:2010-05-06 13:06:42 【问题描述】:

我正在尝试使用“Introduction to Interprocess Communication Using Named Pipes - Full-Duplex Communication Using Named Pipes”,link;特别是fd_server.c(包括在下面供参考)

这是我的信息和编译行:

:~$ cat /etc/issue Ubuntu 10.04 LTS \n \l :~$ gcc --version gcc (Ubuntu 4.4.3-4ubuntu5) 4.4.3 :~$ gcc fd_server.c -o fd_server

fd_server.c 创建两个命名管道,一个用于读取,一个用于写入。可以做的是:在一个终端中,运行服务器并读取(通过cat)它的写管道:

:~$ ./fd_server & 2>/dev/null [1] 11354 :~$ 猫 /tmp/np2

另外,写入(使用 echo)到服务器的读取管道:

:~$ echo "heeellloooo" > /tmp/np1

回到第一个终端,可以看到:

:~$ 猫 /tmp/np2 嘻嘻嘻 0[1]+ 退出 13 ./fd_server 2> /dev/null

我想做的是进行某种“交互式”(或“壳”式)会话;也就是说,服务器照常运行,但不是运行catecho,我想使用类似于screen 的东西。我的意思是,屏幕可以像screen /dev/ttyS0 38400 一样被调用,然后它会进行一种交互式会话,在终端中输入的内容将传递给/dev/ttyS0,并将其响应写入终端。现在,当然,我不能使用screen,因为在我的情况下,程序有两个独立的节点,据我所知,screen 只能引用一个。

在这种情况下(使用两个单独的读/写管道)如何实现这种“交互式”会话?

代码如下:

#include <stdio.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//#include <fullduplex.h> /* For name of the named-pipe */
#define NP1     "/tmp/np1"
#define NP2     "/tmp/np2"
#define MAX_BUF_SIZE    255
#include <stdlib.h> //exit
#include <string.h> //strlen

int main(int argc, char *argv[])

    int rdfd, wrfd, ret_val, count, numread;
    char buf[MAX_BUF_SIZE];

    /* Create the first named - pipe */
    ret_val = mkfifo(NP1, 0666);

    if ((ret_val == -1) && (errno != EEXIST)) 
        perror("Error creating the named pipe");
        exit (1);
    

    ret_val = mkfifo(NP2, 0666);

    if ((ret_val == -1) && (errno != EEXIST)) 
        perror("Error creating the named pipe");
        exit (1);
    

    /* Open the first named pipe for reading */
    rdfd = open(NP1, O_RDONLY);

    /* Open the second named pipe for writing */
    wrfd = open(NP2, O_WRONLY);

    /* Read from the first pipe */
    numread = read(rdfd, buf, MAX_BUF_SIZE);

    buf[numread] = '0';

    fprintf(stderr, "Full Duplex Server : Read From the pipe : %sn", buf);

    /* Convert to the string to upper case */
    count = 0;
    while (count < numread) 
        buf[count] = toupper(buf[count]);
        count++;
    

    /*
     * Write the converted string back to the second
     * pipe
     */
    write(wrfd, buf, strlen(buf));

编辑:

对,只是为了澄清一下-似乎我发现document在讨论非常相似的东西,它是-那里的脚本修改(“例如,以下脚本配置设备并启动后台进程用于将所有接收到的数据从串行设备复制到标准输出...") 上面的程序如下:

# stty raw # 
( ./fd_server 2>/dev/null; )&
bgPidS=$!
( cat < /tmp/np2 ; )&
bgPid=$!
# Read commands from user, send them to device
echo $(kill -0 $bgPidS 2>/dev/null ; echo $?)
while [ "$(kill -0 $bgPidS 2>/dev/null ; echo $?)" -eq "0" ] && read cmd; do
   # redirect debug msgs to stderr, as here we're redirected to /tmp/np1
   echo "$? - $bgPidS - $bgPid" >&2
   echo "$cmd"
   echo -e "\nproc: $(kill -0 $bgPidS 2>/dev/null ; echo $?)" >&2
done >/tmp/np1
echo OUT
# Terminate background read process - if they still exist
if [ "$(kill -0 $bgPid 2>/dev/null ; echo $?)" -eq "0" ] ;
then
    kill $bgPid
fi
if [ "$(kill -0 $bgPidS 2>/dev/null ; echo $?)" -eq "0" ] ;
then
    kill $bgPidS
fi
# stty cooked

因此,将脚本保存为 starter.sh 并调用它,会产生以下会话:

$ ./starter.sh 
0
i'm typing here and pressing [enter] at end
0 - 13496 - 13497
I'M TYPING HERE AND PRESSING [ENTER] AT END
0~�.N=�(�~� ���������@������~� [garble]
proc: 0
OUT

这就是我所说的“交互式会话”(忽略调试语句)——服务器等待我输入命令;它在收到命令后给出输出(在这种情况下,它在第一个命令后退出,启动脚本也是如此)。除此之外,我不想缓冲输入,而是一个字符一个字符地发送(这意味着上述会话应该在第一次按键后退出,并且只打印一个字母 - 这是我所期望的 stty raw 将有助于,但它不会:它只会杀死对 EnterCtrl-C 的反应 :) )

如果已经有一个现有命令(我猜就串行设备类似于screen),我只是在徘徊,它将接受两个这样的命名管道作为参数,并建立一个“终端”或“外壳”,如通过他们进行会话;或者我是否必须使用上述脚本和/或程序自己的“客户端”,它将充当终端..

【问题讨论】:

使用 Ctrl+K 将代码缩进四个空格,这将创建一个语法高亮代码块。我为你做了,停止编辑。 ;-) 感谢 John Kugelman 的建议和编辑 :) 【参考方案1】:

如果您只是希望能够接收多行,而不是在一行之后退出,这很简单。你只需要在你的读/写代码周围放置一个循环,就像这样(又快又脏):

while( 1 ) 
    numread = read(rdfd, buf, MAX_BUF_SIZE);

    fprintf(stderr, "Full Duplex Server : Read From the pipe : %sn", buf);

    /* Convert to the string to upper case */
    count = 0;
    while (count < numread) 
        buf[count] = toupper(buf[count]);
        count++;
    

    /*
     * Write the converted string back to the second
     * pipe
     */
    write(wrfd, buf, strlen(buf));

当然,现在你有一个永远不会退出的应用程序,一旦它得到一个 EOF 等就会开始什么都不做。所以,你可以重新组织它来检查错误:

numread = read(rdfd, buf, MAX_BUF_SIZE);
while( numread > 0) 
    /* ... etc ... */
    numread = read(rdfd,buf, MAX_BUF_SIZE);

if( numread == 0 )  
    /* ... handle eof ... */

if( numread < 0 ) 
    /* ... handle io error ... */
 

从手册页中, read 返回 0 表示 EOF,返回 -1 表示错误(您已经阅读了手册页,对吗?http://linux.die.net/man/2/read)。因此,它所做的就是不断从读取管道中获取字节,直到它到达 EOF 或某些错误,在这种情况下,您(可能)打印一条消息并退出。也就是说,您可能会在收到 EOF 时重新打开,以便获得更多输入。

一旦您将程序修改为连续阅读,交互式输入多行就很简单了。只需执行:

cat - > /tmp/np1

'-' 明确告诉 cat 从标准输入读取(这是默认设置,因此您实际上不需要破折号)。因此 cat 会将您输入的所有内容传递给您的管道程序。您可以使用 Ctrl+D 插入 EOF,这将导致 cat 停止读取标准输入。您的管道程序会发生什么取决于您如何处理读取循环中的 EOF。

现在,如果你想要另一个没有 cat 的程序来完成所有的 io,(所以你最终会得到一个 stdio echo 程序),伪代码看起来像这样:

const int stdin_fd = 0;  // known unix constant!
int readpipe_fd = open the read pipe, as before 
int writepipe_fd =  open the write pipe, as before 
read stdin into buffer
while( stdin is reading correctly ) 
     write data from stdin to read pipe
         check write is successful
     read write pipe into buffer
         check read is successful
     write buffer to stdout (fprintf is fine)
     read stdin into buffer.

如果您愿意,可以使用 read 系统调用来读取 stdin,但您也可以只使用 stdio。读取、写入和打开管道都应该与您的服务器程序相同,除了读/写都是相反的。

【讨论】:

嗨 ipeet - 感谢您的回复!我不太关心从服务器端处理多个命令——我感兴趣的正是“另一个执行所有 io 的程序”;可以接受来自另一个程序的输入和输出命名管道作为参数,并建立一个“类似终端”的会话(编辑:......而不是我一直在单独的终端中运行 cat 和 echo :)) - 我已经添加上面的编辑演示,希望它澄清一点。干杯! 我不知道已经存在任何此类程序。我已经在 C 中提供了这样一个程序的大纲,看起来你已经有一个 shell 脚本(我无法解释乱码)。我相信,如果您轮询相对较快,使用 O_NONBLOCK 打开一个“普通”文件将允许您逐个字符地输入。 (它应该只获取可用的任何内容并立即返回,它不会等待填满请求的缓冲区空间)。您还可以使用 fcntl 在打开的文件(例如标准输入)上设置 O_NONBLOCK。不过,O_NONBLOCK 对管道有一些奇怪的效果(参见 mkfifo 手册页) 另外,如果你想在 shell 脚本中逐字符输入,你可以使用 read -n 1

以上是关于Linux C:具有单独读写命名管道的“交互式会话”?的主要内容,如果未能解决你的问题,请参考以下文章

Linux_Centos进程间通信_管道(匿名管道_命名管道)

Linux进程间通信

Linux进程间通信

Linux进程间通信

Linux进程间通信

Linux--进程通信