1

I have a standard server-client TCP setup. The basic idea is a chat system. Looking at only the client's side of the conversation, the client prompts the user for input with:

sys.stdout.write('<%s> ' % username)
sys.stdout.flush()

using the following logic:

while True:
    socket_list = [sys.stdin, s]
    read_sockets, write_sockets, error_sockets = select.select(socket_list, [], [])
    for sock in read_sockets:
        if sock == s:
            data = sock.recv(4096)
            if data:
                output('\a\r%s' % data) #output incoming message
                sys.stdout.write('<%s> ' % username) #prompt for input
                sys.stdout.flush()
            else:
                raise SystemExit
        else:
            msg = getASCII(sys.stdin.readline()) # returns only the ascii
            if msg:
                s.send(msg)
            sys.stdout.write('<%s> ' % username)
            sys.stdout.flush())

(Note: truncated snippet. Full code can be found here Linked code has been updated and so is no longer relevant.)

The problem is, when the user is typing and it gets an incoming message from the server, the client outputs the message and prompts for input again. The message that was being typed is still in the stdin buffer but has gone from the screen. If the user presses enter to send the message, the entire message will be sent including what was in the buffer, but on the user's screen, only the second part of the message, the part after the interruption, will be displayed.

I have a possible solution, which is that when I prompt for input, I check if there's anything in the buffer and output that along with the prompt, but I have no idea how to implement it. Any help is appreciated.

Awn
  • 817
  • 1
  • 16
  • 33
  • What version of Python are you using? – Jon Clements Aug 05 '16 at 19:48
  • Python 2. Support for 3 shouldn't be difficult though. – Awn Aug 05 '16 at 20:47
  • To implement your solution, you will have to read from stdin in an unbuffered way. `readline()` and `read()` block until an EOL or EOF. You need the data from stdin BEFORE the return key is pressed. To achieve that, this might prove helpful: http://code.activestate.com/recipes/134892-getch-like-unbuffered-character-reading-from-stdin/ When you are about to write data, you could then read from stdin, store it somewhere and output it again after outputting the message. As select won't be called for stdin, make a separate read-thread that reads stdin. Use locks for accessing stdin's data so far. – Munchhausen Aug 07 '16 at 01:52
  • Oh and I should have mentioned, I'm on Linux so select works. – Awn Aug 07 '16 at 09:05
  • What @Munchhausen said. You can do this with select() but it will be messy. A simpler way to think of this problem is to completely separate what the client reads from the server and what the client writes to the server into separate threads. One thread just reads the keyboard and sends what it gets to the server. Another thread just reads messages from the server and puts them in the client's output window. The two threads never have to look at each other's data, so there's no issue with concurrency management. – Tom Barron Aug 09 '16 at 09:01
  • I have an idea based on what @Munchhausen said about getch(). Could I have a thread that calls a function in a `while char != '\n'` and when it encounters a newline it could concatenate the characters and append the resulting string to a send buffer or similar. The main thread repeatedly checks this send buffer for data and if it encounters any, it sends it off. If a message comes in then the main thread just accesses the memory space of the other thread and grabs whatever data has already been typed from there. – Awn Aug 09 '16 at 10:41
  • Update: Just tried that but ran into a problem. Backspaces don't work. Is there an alternative to getch() that handles this? – Awn Aug 09 '16 at 11:02
  • What do you mean by don't work? Does the terminal not delete characters or does getch return something different than expected? – Munchhausen Aug 09 '16 at 11:04
  • getch() returns exactly what is expected. But if you think about it from a chat's point of view, users enter what they want to send so they have to be able to see what they are typing. getch() doesn't display what you're typing so I write the char to stdout myself. However, when I press the backspace, getch just returns and prints the ascii code for a backspace. – Awn Aug 09 '16 at 11:11
  • Actually I managed to figure that out using `\b` – Awn Aug 09 '16 at 11:32
  • @Munchhausen do you mind posting an answer so that I can award you the bounty? Your comments really helped me out. – Awn Aug 11 '16 at 21:58
  • @Aurora Done! Sorry, I did not see your comments from 2 days ago. I did not get a notification. About the backspace thing: I guess you have to implement your own line deletion then? – Munchhausen Aug 11 '16 at 22:49
  • @Munchhausen Yeah I used `\b \b` to accomplish that. [Check it](https://github.com/libeclipse/edgychat). :) – Awn Aug 11 '16 at 23:09

3 Answers3

1

To implement your solution, you will have to read from stdin in an unbuffered way. readline() and read() block until an EOL or EOF. You need the data from stdin BEFORE the return key is pressed. To achieve that, this might prove helpful: http://code.activestate.com/recipes/134892-getch-like-unbuffered-character-reading-from-stdin/ When you are about to write data, you could then read from stdin, store it somewhere and output it again after outputting the message. As select won't be called for stdin, make a separate read-thread that reads stdin. Use locks for accessing stdin's data so far.

Awn
  • 817
  • 1
  • 16
  • 33
Munchhausen
  • 442
  • 4
  • 16
0

As an alternative to implementing your own editing line input function as discussed in the question's comments, consider this approach: Change the scrolling region to leave out the screen's bottom line (the user input line) and enter the scrolling region only temporarily to output incoming server messages. That answer contains an example.

Community
  • 1
  • 1
Armali
  • 18,255
  • 14
  • 57
  • 171
0

The problem seems to be that you are letting the messages from the other user interrupt the typing. I recommend you either only listen to one thing at a time (when the user is typing you let him finish and press enter before listening for remote messages) or you listen for the user's input one key at a time and build up your own buffer (see Polling the keyboard (detect a keypress) in python). The downside to the later approach is that you need to implement key editing, etc. There may be a library that accomplishes this for you.

Note that in most chat programs, the area you type is in a separate window/screen region than where you are seeing the messages. All messages (yours as well as others) show up when complete in this message area. Perhaps you can use just display messages (independent of input) somewhere else on the screen.

Community
  • 1
  • 1
Steve DeNeefe
  • 121
  • 1
  • 6