Чат NCurses плохо себя ведет, блокируется при выборе

Я написал приложение на C для сети общения, а также простой чат в комнате. Я использовал ncurses, сокеты и базовые сетевые штуки.

Проблема в том, что моя функция использует select() для чтения из серверного сокета И stdin, поэтому, когда я начинаю писать сообщение, окно вывода зависает и показывает сообщения от других клиентов только после того, как я нажму Enter.

Я перепробовал все возможное. Есть ли способ исправить это?

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

Вот код:

ssize_t safePrefRead(int sock, void *buffer)
{
    size_t length = strlen(buffer);

    ssize_t nbytesR = read(sock, &length, sizeof(size_t));
    if (nbytesR == -1)
    {
        perror("read() error for length ! Exiting !\n");
        exit(EXIT_FAILURE);
    }

    nbytesR = read(sock, buffer, length);
    if (nbytesR == -1)
    {
        perror("read() error for data ! Exiting !\n");
        exit(EXIT_FAILURE);
    }

    return nbytesR;
}

ssize_t safePrefWrite(int sock, const void *buffer)
{
    size_t length = strlen(buffer);

    ssize_t nbytesW = write(sock, &length, sizeof(size_t));
    if (nbytesW == -1)
    {
        perror("write() error for length ! Exiting !\n");
        exit(EXIT_FAILURE);
    }

    nbytesW = write(sock, buffer, length);
    if (nbytesW == -1)
    {
        perror("write() error for data ! Exiting !\n");
        exit(EXIT_FAILURE);
    }

    return nbytesW;
}

void activeChat(int sC, const char *currentUser, const char *room)
{
    char inMesg[513], outMesg[513];
    char user[33];


    int winrows, wincols;
    WINDOW *winput, *woutput;

    initscr();
    nocbreak();
    getmaxyx(stdscr, winrows, wincols);
    winput = newwin(1, wincols, winrows - 1, 0);
    woutput = newwin(winrows - 1, wincols, 0, 0);
    keypad(winput, true);
    scrollok(woutput, true);
    wrefresh(woutput);
    wrefresh(winput);



    fd_set all;
    fd_set read_fds;
    FD_ZERO(&all);
    FD_ZERO(&read_fds);
    FD_SET(0, &all);
    FD_SET(sC, &all);

    wprintw(woutput, "Welcome to room '%s' \n Use /quitChat to exit !\n!", room);
    wrefresh(woutput);

    while (true)
    {
        memcpy( &read_fds, &all, sizeof read_fds );
        if (select(sC + 1, &read_fds, NULL, NULL, NULL) == -1)
        {
            perror("select() error or forced exit !\n");
            break;
        }

        if (FD_ISSET(sC, &read_fds))
        {
            memset(inMesg, 0, 513);
            safePrefRead(sC, user);
            safePrefRead(sC, inMesg);
            wprintw(woutput, "%s : %s\n", user, inMesg);
            wrefresh(woutput);
            wrefresh(winput);
        }

        if (FD_ISSET(0, &read_fds))
        {

            //wgetnstr(winput, "%s", outMesg);

            int a, i = 0;

            while ( i < MAX_BUF_LEN && (a = wgetch(winput)) != '\n')
            {
                outMesg[i] = (char)a;
                i++;
            }
            outMesg[i] = 0;


            if (outMesg[0] == 0)
                continue;
            if (strcmp(outMesg, "/quitChat") == 0)
            {
                safePrefWrite(sC, outMesg);
                break;
            }
            safePrefWrite(sC, outMesg);
            delwin(winput);
            winput = newwin(1, wincols, winrows - 1, 0);
            keypad(winput, true);
            wrefresh(winput);
        }
    }

    delwin(winput);
    delwin(woutput);
    endwin();
}

-safePrefWrite и safePrefRead являются оболочками для предварительного чтения/записи и обработки ошибок. -sC - это сокет сервера.

LE: Я пытался использовать fork и threads. Использование fork вело себя так же, а потоки были катастрофой, терминал был испорчен.

Спасибо.


person xSkyripper    schedule 06.01.2016    source источник
comment
Пожалуйста, вставьте свой код сюда.   -  person A J    schedule 06.01.2016
comment
Я перепробовал все, что только можно.. -- Пожалуйста, укажите здесь, каковы эти возможности.   -  person Jeya Suriya Muthumari    schedule 06.01.2016
comment
Я вставлю код позже здесь, так как я не могу сделать это сейчас. Под всем я подразумевал форсирование echo(), печать символ за символом в winput, когда я читал их с помощью waddch(), вилки, потоки ... Я не могу вспомнить их все, потому что эта проблема съела 7 часов моей жизни.   -  person xSkyripper    schedule 06.01.2016
comment
Код pastebin кажется фрагментом большого файла. Среди прочего, какие заголовочные файлы #include включаются? как называется опубликованный код.   -  person user3629249    schedule 06.01.2016
comment
Похоже, что «удаленный» пользователь не может изящно выйти из программы. Когда локальный пользователь выходит из программы, текст, вводимый локальными пользователями, транслируется, а не что-то вроде: «‹user-name› помахал на прощание, выходя из комнаты`   -  person user3629249    schedule 06.01.2016
comment
Это для школьного проекта, который я должен закончить как можно скорее. Вам не кажется, что то, что вы сказали, мои ПОСЛЕДНИЕ проблемы? Действительно. Я только что попросил решить проблему с блокировкой...   -  person xSkyripper    schedule 06.01.2016


Ответы (3)


измените цикл while(true), чтобы обрабатывать только один символ за раз для стандартного ввода.

Что в основном означает для стандартного ввода, прочитайте один символ:

если char равен '\n', то обрабатывать как сейчас,

в противном случае просто добавьте char в буфер для записи.

Всегда перед добавлением символа в буфер для записи проверяйте, не заполнен ли буфер.

добавить код для обработки случая, когда буфер для записи заполнен

завершите функцию этой последовательностью:

delwin(winput);
delwin(woutput);
endwin();
endwin();

чтобы закрыть оба окна.

Не вызывайте endwin() во время обработки ввода сокета.

Не вызывайте endwin(), когда select() возвращает состояние ошибки

fd_set не является внутренним размером в C, поэтому используйте memcpy(), чтобы установить read_fds из all. предлагать:

memcpy( &read_fds, &all, sizeof read_fds );

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

 (void)currentUser;

чтобы исключить предупреждающее сообщение компилятора.

для удобочитаемости и простоты понимания предложите #define магическим числам 513 и 33 с осмысленными именами, а затем используйте эти осмысленные имена во всем коде.

#define MAX_BUF_LEN (513)
#define MAX_USER_LEN (33)  

эта строка: outMesg[i] = a; вызывает предупреждение компилятора, предложите:

outMesg[i] = (char)a;

Эта строка: while ( (a = wgetch(winput)) != '\n') может привести к переполнению буфера outMesg[], что приведет к неопределенному поведению и может привести к событию сбоя сегмента. предлагать:

while ( i < MAX_BUF_LEN && (a = wgetch(winput)) != '\n')

Предложите опубликовать прототипы функций safePrefWrite() и safePrefRead(), например:

void safePrefRead( int, char * );
void safePrefWrite( int, char * );
person user3629249    schedule 06.01.2016
comment
Я не понимаю, что вы имеете в виду, изменяя цикл while (true). Разве у меня уже нет этого во втором if ? Я изменил то, что вы сказали, но я до сих пор не понимаю, как исправить мой код... - person xSkyripper; 06.01.2016
comment
при чтении со стандартного ввода введите только один символ (не все доступные символы), прежде чем вернуться в начало цикла while(). Единственный раз, когда нужно делать что-либо еще при обработке стандартного ввода, это когда вводится '\n'. Затем и только тогда обработайте всю входную строку, включая проверку на /quitchat и вызов safePrefWrite(), тогда ввод-вывод для удаленного пользователя не будет заморожен, ожидая, пока локальный пользователь завершит ввод следующей строки для передачи на удаленный пользователь. - person user3629249; 06.01.2016

Как отметил пользователь @user3629249, есть несколько критических замечаний, которые можно применить к примеру кода. Однако вопрос OP не решается этими улучшениями.

ОП, похоже, упустил из виду эти функции:

  • cbreak или raw, чтобы wgetch читал небуферизованные данные, т. е. не дожидаясь '\n'.
  • nodelay или timeout, чтобы контролировать количество времени, которое wgetch тратит на ожидание ввода.

Между прочим, заставить select работать с программой curses будет делать предположения о внутреннем поведении библиотеки curses: обеспечение ее надежной работы может быть проблематичным.

person Thomas Dickey    schedule 06.01.2016
comment
Разве cbreak() не заставляет мой select() устанавливать стандартный ввод, как только набирается первая буква? Я имею в виду, в чем смысл? Я также попробовал nodelay(), но не увидел никаких улучшений. Каковы мои варианты, чтобы сделать эту работу правильно? - person xSkyripper; 06.01.2016
comment
опубликованный код использует wgetch() для каждого входного символа. Символ уже будет доступен из-за оператора select(), поэтому нет необходимости «тайм-аут» любой из функций wgetch(), поскольку он никогда не будет ждать. Если ввод выполняется в режиме raw, то каждое нажатие клавиши будет доступно немедленно; однако тогда программе нужно будет обрабатывать все непечатаемые нажатия клавиш. Это сильно усложнит логику кода. но, возможно, стоит включить более быстрый ответ на ввод от удаленного пользователя. И этот более быстрый ответ — это именно то, к чему относится мой ответ (наряду с несколькими другими проблемами. - person user3629249; 06.01.2016
comment
Я использовал select() с ncurses в нескольких проектах, это работает очень хорошо. При использовании «подготовленного» режима ввода select() не будет запускаться локальным пользователем, вводящим нажатия клавиш (однако эти нажатия клавиш будут отображаться и могут быть отредактированы backspace и т. д.) Только когда локальный пользователь вводит '\n' или EOF будет "срабатывать" select(). Не вызывайте wgetch(), если select() уже не указал, что символ доступен для ввода - person user3629249; 06.01.2016
comment
Можете ли вы дать мне пример кода для этого? Потому что, как бы я ни думал об этом решении, я не могу понять его правильно. - person xSkyripper; 06.01.2016

Наконец, исправил это, используя только большой цикл.

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

if (FD_ISSET(0, &read_fds))
    {


        inChar = wgetch(winput);

        if (inChar == 27)
        {
            safePrefWrite(sC, "/quit");
            break;
        }

        if (inChar == KEY_UP || inChar == KEY_DOWN || inChar == KEY_LEFT || inChar == KEY_RIGHT)
            continue;

        if (inChar == KEY_BACKSPACE || inChar == KEY_DC || inChar == 127)
        {
            wdelch(winput);
            wrefresh(winput);
            if (i != 0)
            {
                outMesg[i - 1] = 0;
                i--;
            }
        }
        else
        {
            outMesg[i] = (char)inChar;
            i++;
        }


        if (outMesg[i - 1] == '\n')
        {
            outMesg[i - 1] = 0;
            i = 0;

            if (outMesg[0] == 0)
                continue;

            if (strcmp(outMesg, "/quit") == 0)
            {
                safePrefWrite(sC, outMesg);
                break;
            }

            safePrefWrite(sC, outMesg);
            delwin(winput);
            winput = newwin(1, wincols, winrows - 1, 0);
            keypad(winput, true);
            wrefresh(winput);
            memset(outMesg, 0, 513);
        }
    }

Я также использую raw() для отключения сигналов и обработки кодов так, как я хочу. Все остальное выше и ниже этого «если» так же, как в 1-м посте.

person xSkyripper    schedule 07.01.2016