[C] Watch input for quit command WITHOUT interfering with the program?

Discussion in 'Mac Programming' started by ravenvii, Apr 13, 2013.

  1. ravenvii macrumors 604


    Mar 17, 2004
    Melenkurion Skyweir
    Before I begin -- YES, this is via a homework assignment. However, the functionality I'm asking about is beyond the scope of the homework itself.

    This might be a strange question -- and if my thinking is going down the wrong alley, let me know! -- but I'm looking for a way to watch the input for a quit command WITHOUT interfering with the program.

    The program is a simple CLI socket-based server-client chat program using multithreading. I got all of it working great in the scope of the assignment.

    However, it bothers me that the program cannot quit gracefully -- I have to hit Ctrl-C to quit it. So I went to work on getting it to quit gracefully. I've managed to make it detect if the server or client is no longer running, and quit.

    However, that still requires me to Ctrl-C the *other* program.

    The obvious step would be a simple "if input equals "/quit" then return 0" and fork it off into it's own infinite loop.

    Obvious problem: this is a chat program, so input is being monitored for read/write via socket. Before you ask: I've tried putting the if-statement just before the write() call, but this doesn't work because even though it terminates the writing thread, it doesn't terminate the reading thread.

    So is there a way to fork main and have that "passively" watch input, and execute as soon as it sees a matching input?

    This needs to be without blocking, obviously.

    I'm probably over-thinking this, but I figured this would be a good learning experience, even though it's beyond the scope of the assignment itself.

    Whew, sorry for the lengthy post!

    TL;DR: How to passively monitor input in CLI without blocking, for a matching input that would exit the program?
  2. chown33 macrumors 604

    Aug 9, 2009
    1. What language are you working in?

    2. What libraries are you working with? Standard C? Posix? C++? Objective-C?

    3. Where do you want the "quit command" request to come from? The keyboard? Remote? Somewhere else, like a signal via kill(2) or sigaction(2)? The origin may seem clear to you because you're involved in the program from its inception, but it's not entirely clear to me, even after reading your entire post a few times.

    4. How do you read the sendable "chat" text now? Which function? What happens after reading it? Are you reading char-by-char or line-by-line? Doing any post-processing before sending? Post your code.

    5. Is it a point-to-point chat program (exactly 2 endpoints), or a group chat (multiple endpoints)? I've seen both kinds called "chat programs", so it's unclear what you have.

    Without code, this means nothing.

    A point-to-point chat program has at least two inputs, one being the local keyboard, the other being the remote connection (presumably a socket). So when you say "input is being monitored for read/write via socket", it sounds like you want the QUIT command to come from the remote connection, i.e. the remote chat participant. That doesn't make a lot of sense to me.

    A group chat program has multiple inputs, each one being remote, and no local keyboard input (typically). It's essentially a faceless daemon, listening for and accepting any number of connections, and mirroring any input onto all connected output sockets.

    If your design doesn't fall into one of those areas, then please explain exactly what your program does.

    And post your code, so we can see what the threads are doing, and where this write() call is.
  3. ravenvii, Apr 14, 2013
    Last edited: Apr 16, 2013

    ravenvii thread starter macrumors 604


    Mar 17, 2004
    Melenkurion Skyweir
    I'm writing in C.

    write() is just a function in unistd.h

    It's just a simple server-to-client chat program using the command line. Input (at both ends) is via keyboard using fgets.
  4. chown33 macrumors 604

    Aug 9, 2009
    The simplest place to look for a "/quit" command is between the fgets() and the write(). If it sees a "/quit" in the buffer, it should terminate the thread without sending the "/quit".

    Or since your main() isn't doing anything else useful, it can run the sending() function directly, instead of spawning a separate thread for it. Then when "/quit" appears in the buffer, sending() just returns.

    It's not necessary to terminate the reading() thread. Simply exit the program and all threads will terminate.

    There are other ways of doing it, such as having a thread-safe condition variable in each thread's while loop. I.e. where you have while(1) use while(threadSafeCondition) instead, then have the detection of "/quit" clear the condition variable.

    You can also look for EOF on stdin after fgets(), rather than an overt "/quit" command. This is left as an exercise for the reader.
  5. ravenvii thread starter macrumors 604


    Mar 17, 2004
    Melenkurion Skyweir
    I did that, but it didn't terminate the receiving thread. I didn't think of simply removing pthread_join for the receiving thread. That'll work.

    (Side-question: Should I just let main return, or should I explicitly put in pthread_cancel() before returning? Which is the better practice?)

    I thought of this as well, but as this is homework, the instructor requires that both sending + receiving be threads, so that's that :)

    I'm in the middle of doing just this actually -- a global int set to 0 which changes to 1 if /quit is detected. This terminates both the sending and receiving loops. However, it doesn't work very well because the program ends up waiting for the loop to terminate before noticing :)

    (And I'm trying to minimize the use of global variables -- they're bad practice... right?)

    Thanks for the help/input!
  6. chown33, Apr 14, 2013
    Last edited: Apr 14, 2013

    chown33 macrumors 604

    Aug 9, 2009
    It depends on the scope of the design. This is a simplified homework design, not a design intended for reuse or even real-world deployment. I would choose simplicity, and recognize that the inevitable exit will terminate all threads anyway. If you feel the need to demonstrate that knowledge, put it in a comment.

    Post your code. If you want someone to comment on code or make suggestions, you have to post it. Otherwise all we can do is guess what you actually wrote.

    Again, it depends on the design. Excessive coupling can be bad practice. There may be perfectly sound reasons for globals, but if there's no reason, then there's no reason.

    What's the absolute smallest variable scope that will work?

    Clearly, it doesn't need to be global (visible in other compilation units), because both thread functions are in a single file. What's the C keyword that limits visibility to a single compilation unit, while also making it visible to the entire compilation unit?

    Then what's the C keyword that ensures reads/writes will be suitably thread-safe (with the minimal amount of thread-safety)?

    Answers (in white text, so select and copy/paste to read)
    [ static volatile ]
  7. cqexbesd macrumors regular

    Jun 4, 2009
    You need to use some form of asynchronous notification to do this well. Probably the easiest is to use pthread_kill to send a signal to a particular thread when you want it to exit. Make sure the thread tests the return value of all the functions it calls. If one fails and errno is set to EINTR then you know to check some form of flag (e.g. a global variable) to see if its time to exit. You still need to set up your signal handlers and the like. It takes a lot of thought to get this right in all but the trivialest of cases to avoid race conditions or long delays in processing the quit.

    It may not be worth the trouble in your case. Usually if I write a moderately complex threaded app I will dedicate one thread to housekeeping (usually the main thread). It receives signals aimed at he whole process (e.g. TERM, HUP) or notifications that its time to exit and is responsible for letting everyone know and trying to shut things down gracefully. It can be one of the more complex tasks in the whole program sometimes, esp on fatal error conditions.

    The alternative is to be single threaded and use select/kqueue/poll etc to multiplex the different streams. For simple tasks this can be much easier to work with.
  8. ravenvii thread starter macrumors 604


    Mar 17, 2004
    Melenkurion Skyweir
    I ended up just dumping the disconnect detection and leave '/quit' as the only way to exit the program.

    Covers much more use-cases, and guarantees a way to shut down both the server and client.

    Thanks guys!

Share This Page