如何防止过度输入

3
我正在尝试使用C++制作一个简单的聊天应用程序。它可以工作,但如果有人在别人输入时输入了什么,就会覆盖他们正在输入的内容。我在下面附上了示例图片。
客户端和服务器所使用的代码可以在这里找到: 客户端 服务器 图片:
之前 enter image description here 之后 enter image description here

6
这与套接字和/或Winsock没有任何关系——纯粹是关于在屏幕上显示东西。 - Jerry Coffin
2
如果尚未清楚,这远非简单。如果你只是为了练习而做这个,就不要费心了... 只需在另一侧的文本之前和之后打印额外的换行符,然后就完成了。如果你是为了真正的聊天应用程序而做它,最好使用GUI应用程序,特别是在Windows上。 - hyde
3个回答

7
一种方法是在用户开始输入文本行时设置标志 - 如果在该标志设置的同时从网络接收到数据,则仅将接收到的数据排队在某个本地数据结构中(即暂不打印),直到本地用户按下回车键。 当本地用户按下回车键后,您将打印出在他输入期间排队的所有数据,并取消标志。
当然,这种方法有几个缺点:
- 如果本地用户输入一些文本并且从未按回车键,则永远不会看到任何传入的远程文本。例如,如果用户按空格键并走开,这可能会成为问题。 - C / C ++中的cin / stdin功能通常按行工作,您需要将终端设置为原始/非规范模式,以使其报告本地用户仅输入了一个字符(而不是缓冲字符直到本地用户按下回车键,然后一次性向程序报告整个文本行)。
另一种方法是将本地用户的文本和远程用户的文本保持在物理上分离的区域(例如窗口的上半部分和下半部分,就像许多聊天程序所做的那样)。要做到这一点,需要比原始的C/C++ stdin/stdout/cin/cout API更多的控制;您需要创建一个带有两个独立文本小部件的GUI窗口(使用Win32或Qt或其他GUI API),或者如果您想将所有内容都保留在MS-DOS窗口内,可能会使用类似PDCurses的东西来实现。
不过,任何一种选项都不容易 -- 它们很可能需要比您的聊天应用程序的其余部分更多的时间和精力来实现。如果我是您,而聊天应用程序只是一个学习练习,我可能会简单地将当前行为记录为“已知限制”,不必担心修复它。

2

Jeremy的帖子非常详细。我会再添加一个可用的选项:跟踪正在输入的用户输入的字符,直到他最终按下ENTER。这样,当远程用户输入文本时,你所要做的就是:

向终端输出适当数量的退格字符('\b')(即与本地用户键入的文本长度相同),然后输出新的输入行文本,并输出本地用户之前键入的所有字符。然后继续正常进行。

它看起来就像新输入的文本“滑动”到了正确位置。


由于提问者似乎在使用Windows,实现基于字符的输入最简单的方法是使用conio.h中的getch()getche()函数:http://en.wikipedia.org/wiki/Conio.h - hyde
@hyde:当然,这对O.P.没有帮助,因为问题是如何“覆盖”当前显示在屏幕上的文本,而不是如何读取输入。 - Nik Bougalis
读取用户输入的字符是你的答案所必需的,不是吗?否则你怎么能重新打印已输入的字符呢? - hyde

1
#include <Conio.h>
#include <mutex> // C++11, if not C++11 find your own mutex

std::vector<char> inputBuffer;
std::mutex inputGuard;
struct Locker {
  std::mutex* m;
  Locker(std::mutex& m_):m(&m_) { m->lock(); }
  void release() { if (m) m->unlock(); m = 0; }
  ~Locker() { release(); }
}
void AddToInputBuffer( char c ) {
  Locker lock(inputGuard);
  inputBuffer.push_back(c);
  printf("%c",c);
}
std::string FinishInputBuffer() {
  Locker lock(inputGuard);
  std::string retval( inputBuffer.begin(), inputBuffer.end() );
  inputBuffer.clear();
  printf("\n");
  return retval;
}
void OverlappedPrintln( std::string s ) {
  Locker lock(inputGuard);
  for (int i = 0; i < inputBuffer.size(); ++i) {
    printf("%c", 8 /*backspace*/);
  }
  printf("%s\n", s.c_str());
  for (int i = 0; i < inputBuffer.size(); ++i) {
    printf("%c", inputBuffer[i]);
  }
}

std::string readCharactersFromUser() {
  // TODO: Handle exiting
  while( int input = getch() ) {
    if (input == '\n') {
      return FinishInputBuffer();
    AddToInputBuffer(input);
  }
}

现在,将cin.getline(message, 256);替换为std::string message = readCharactersFromuser(),然后在执行(message[0] == 's' && message[1] == 'a' && message[2] == 'y' && message[3] == ' '之前,检查字符串的长度。

在程序的其他地方,用printf代替,以构建一个std::string并使用它调用OverlappedPrintln

这样做的效果是,当您想要打印输出时,锁定互斥量,在用户输入上回退,打印出消息,进行换行,然后打印您回退的文本,并释放您的互斥量。在99%的情况下,特别是在现代计算机速度下,不需要互斥量,但这是一种好的做法。

以上代码未经测试或编译。它的质量也不是最高的。

选择非回显获取字符是因为我无法在锁定状态下从用户输入读取,并且异步回显到输出可能会导致乱码。因此,我在互斥体内部进行读取而不回显,然后回显,以便inputBuffer始终包含用户键入并已回显到屏幕上的字符。
这是@NikBougalis答案的实现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接