【问题标题】:NCurses chat misbehaving, blocking in selectNCurses 聊天行为不端,阻止选择
【发布时间】:2016-01-06 05:46:54
【问题描述】:

我为社交网络和简单的基于房间的聊天编写了一个 C 应用程序。我使用了 ncurses、sockets 和基本的网络东西。

问题是我的函数使用 select() 从服务器套接字和标准输入中读取,所以当我开始编写消息时,输出窗口冻结,并且在我按 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 和线程。使用 fork 的行为相同,线程是一场灾难,终端被搞砸了。

谢谢。

【问题讨论】:

  • 请在此处粘贴您的代码。
  • "我尝试了所有可能的方法......" -- 请在此处说明这些可能性。
  • 稍后我将在此处粘贴代码,因为我现在无法这样做。我的意思是强制 echo(),当我用 waddch()、forks、threads 阅读它们时,在 wininput 中逐个字符打印它们。我不记得它们,因为这个问题占用了我生命的 7 个小时。
  • pastebin 代码似乎是从一个较大的文件中提取出来的。除其他事项外,#include 包含哪些头文件?发布的代码是如何被调用的。
  • “远程”用户似乎无法优雅地退出程序。当本地用户退出程序时,本地用户的文本输入会被广播,而不是类似:' 在他们退出房间时挥手告别'

标签: c sockets select networking ncurses


【解决方案1】:

修改while(true) 循环以一次只为标准输入处理一个字符。

这主要是指标准输入,读取单个字符:

如果 char 是 '\n' 则按当前处理,

否则,只需将 char 附加到要写入的缓冲区。

始终,在将 char 附加到要写入的缓冲区之前,请检查缓冲区是否已满。

添加代码来处理要写入的缓冲区已满的情况

用这个序列结束函数:

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

结束两个窗口。

在处理套接字输入期间不要调用 endwin()。

select() 返回错误条件时不要调用 endwin()

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[] 溢出,导致未定义的行为,并可能导致 seg 故障事件。建议:

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

建议发布 safePrefWrite() 和 safePrefRead() 函数的原型,类似于:

void safePrefRead( int, char * );
void safePrefWrite( int, char * );

【讨论】:

  • 我不明白您所说的“修改”while(true) 循环是什么意思。我不是已经在第二个 if 了吗?我修改了你所说的,但我仍然不明白如何修复我的代码......
  • 从标准输入读取时,只输入一个字符(不是所有可用字符),然后返回到while()循环的顶部。唯一在处理标准输入时做任何其他事情的时候是输入 '\n' 时。然后,仅在那时,处理整个输入字符串,包括检查 /quitchat 和调用 safePrefWrite(),然后在等待本地用户完成输入下一行到远程用户的 I/O 将不会被冻结传输给远程用户。
【解决方案2】:

正如@user3629249 所指出的,有一些批评可以应用于示例代码。但是,这些改进并未解决 OP 的问题。

OP 似乎忽略了这些功能:

  • cbreakraw,使wgetch 读取未缓冲的数据,即不等待'\n'
  • nodelaytimeout,用于控制 wgetch 等待输入的时间。

顺便说一句,让 select 与 curses 程序一起工作将对 curses 库的内部行为做出假设:让它可靠地工作可能很麻烦。

【讨论】:

  • cbreak() 不会让我的 select() 在输入第一个字母后立即设置标准输入吗?我的意思是,有什么意义?我也试过 nodelay() 但我没有看到任何改进。我有哪些选择可以使这项工作以正确的方式进行?
  • 发布的代码对每个输入字符使用 wgetch()。由于select() 语句,该字符已经可用,因此无需“超时”任何 wgetch() 函数,因为它永远不会等待。如果输入在raw 模式下运行,那么每个按键都将立即可用;但是,程序将需要处理所有不可打印的击键。这将使代码逻辑大大复杂化。但可能值得更快地响应来自远程用户的输入。而这种更快的响应正是我的答案所要解决的问题(以及其他几个问题。
  • 我在几个项目中使用了select()和ncurses,效果很好。如果使用'cooked'输入模式,则select()不会被本地用户输入击键触发(但是这些击键将被回显并且可以由backspace等编辑)只有当本地用户输入一个'\n' 或 EOF 将“触发”select()。不要调用 wgetch() 除非 select() 已经表明一个字符可用于输入
  • 你能给我一个示例代码吗?因为我一直在考虑这个解决方案,但我无法做到。
【解决方案3】:

最后只使用大循环修复它。

如果以后有人遇到同样的问题,这里是代码:

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 篇文章中的一样。

【讨论】:

    猜你喜欢
    • 2012-07-21
    • 1970-01-01
    • 2015-03-09
    • 2017-02-23
    • 2015-05-23
    • 1970-01-01
    • 1970-01-01
    • 2013-02-09
    • 1970-01-01
    相关资源
    最近更新 更多