Linux C: «Интерактивный сеанс» с отдельными именованными каналами чтения и записи? - PullRequest
3 голосов
/ 06 мая 2010

Я пытаюсь работать с " Введение в межпроцессное взаимодействие с использованием именованных каналов - полнодуплексное взаимодействие с использованием именованных каналов ", 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
:~$ cat /tmp/np2 

и в другом случае записать (используя echo) в канал чтения сервера:

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

возвращаясь к первому терминалу, можно увидеть:

:~$ cat /tmp/np2 
HEEELLLOOOO
0[1]+  Exit 13                 ./fd_server 2> /dev/null

То, что я хотел бы сделать, это сделать своего рода «интерактивный» (или «подобный оболочке») сеанс; то есть сервер работает как обычно, но вместо cat и echo я бы хотел использовать что-то похожее на 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));
}

Edit:

Правильно, просто для пояснения - кажется, я нашел документ , где обсуждается нечто очень похожее, это - модификация скрипта там (" Например, следующий скрипт конфигурирует устройство и запускает фоновый процесс для копирования всех полученных данных с последовательного устройства на стандартный вывод ...") для указанной выше программы:

# 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 поможет с , но это не так: просто убивает реакцию на Enter и Ctrl - C :))

Я просто бродил, если уже существует существующая команда (похожая на screen в отношении последовательных устройств, я думаю), которая приняла бы два таких именованных канала в качестве аргументов и установила бы "терминал" или "оболочку", как сессия через них; или мне придется использовать сценарии, как указано выше, и / или запрограммировать собственный «клиент», который будет вести себя как терминал.

1 Ответ

3 голосов
/ 06 мая 2010

Если вы просто хотите получать несколько строк, а не выходить после одной, это просто.Вам просто нужно поместить цикл вокруг вашего кода чтения / записи, например, так (быстро и грязно):

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 передаст все, что вы вводите, в вашу программу pipe.Вы можете вставить EOF, используя Ctrl + D, что заставит cat перестать читать stdin.Что происходит с вашей программой канала, зависит от того, как вы обрабатываете EOF в цикле чтения.

Теперь, если вам нужна другая программа, которая выполняет все операции ввода-вывода, без cat (так что в итоге вы получите программу 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.Чтение, запись и открытие ваших каналов должны быть идентичны вашей серверной программе, за исключением того, что чтение и запись обращены вспять.

...