Question

I have small server and client Python scripts where the client sends a string and the server responds with the reverse. When the client enters a quit string, the client exits and then the server exits.

I want the server's "receive, reverse, send" procedure running in the background while the program is constantly checking stdin for a quit string.

I've tried using threading but because of the blocking that many socket calls cause it wouldn't work properly.

Just so you can get an idea of what I've already done.

server.py:

import socket
from time import sleep

sock = socket.socket()
sock.bind(("127.0.0.1",12346))
sock.listen(3)
print "Waiting on connection"
conn = sock.accept() 
print "Client connected"

while True:
    m = conn[0].recv(4096)
    if m == "exit":
        sleep(1)
        break
    else:
        conn[0].send(m[::-1])

sock.shutdown(socket.SHUT_RDWR)
sock.close()

client.py:

import socket

sock = socket.socket()
sock.connect(("127.0.0.1",12346))

while True:
    s = raw_input("message: ")
    sock.send(s)

    if s == "exit":
        print "Quitting"
        break

    print sock.recv(4096)

sock.shutdown(socket.SHUT_RDWR)
sock.close()
Was it helpful?

Solution

Since you want the server process to be able to handle the client while in the same time receiving input from the server's stdin, you can just put the whole current server code in a Thread, then wait input from stdin.

import socket
from time import sleep
import threading

def process():
    sock = socket.socket()
    sock.bind(("127.0.0.1",12346))
    sock.listen(3)
    print "Waiting on connection"
    conn = sock.accept()
    print "Client connected"

    while True:
        m = conn[0].recv(4096)
        conn[0].send(m[::-1])

    sock.shutdown(socket.SHUT_RDWR)
    sock.close()

thread = threading.Thread(target=process)
thread.daemon = True
thread.start()
while True:
    exit_signal = raw_input('Type "exit" anytime to stop server\n')
    if exit_signal == 'exit':
        break

and you can remove the "exit" checking in client.

In this code, the server will be doing nothing after the client disconnect, though, it will just wait for "exit" to be typed in the stdin. You might want to extend the code to make the server able to accept new clients, since you don't want the client to have the ability to close the server. In that case, you can put another while loop from conn = sock.accept() to sock.close().

And as @usmcs suggested, if you don't have any other command to be sent to the server, it will be better if you use CTRL-C (KeyboardInterrupt) instead, so you don't need the thread, while it can still end the server gracefully (meaning no error due to the CTRL-C is reported) with this code:

import socket
from time import sleep
import threading

sock = socket.socket()
sock.bind(("127.0.0.1",12346))
sock.listen(3)
print "Waiting on connection"
conn = sock.accept()
print "Client connected"

while True:
    try:
        m = conn[0].recv(4096)
        conn[0].send(m[::-1])
    except KeyboardInterrupt:
        break

sock.close()

OTHER TIPS

This is an example of non-blocking socket receiving. In case of no-data to receive socket will throw an exception.

import sys
import socket
import fcntl, os
import errno
from time import sleep

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',9999))
fcntl.fcntl(s, fcntl.F_SETFL, os.O_NONBLOCK)

while True:
    try:
        msg = s.recv(4096)
    except socket.error, e:
        err = e.args[0]
        if err == errno.EAGAIN or err == errno.EWOULDBLOCK:
            sleep(1)
            print 'No data available'
            continue
        else:
            # a "real" error occurred
            print e
            sys.exit(1)
    else:
        # got a message, do something :)

Here is an example of non-blocking stdin read:

import sys
import select

# If there's input ready, do something, else do something
# else. Note timeout is zero so select won't block at all.
while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
  line = sys.stdin.readline()
  if line:
    something(line)
  else: # an empty line means stdin has been closed
    print('eof')
    exit(0)
else:
  something_else()

Basically, you want to combine them and may be add some timeout to force reading stdin on a regular basis in case of many connections.

I took a gist I had previously published for building a pre-forked JSON-RPC Server in Python and modified the code to solve this problem. Gist Here: https://gist.github.com/matthewstory/4547282

$ python server.py localhost 9999 5
exit
$

Looking more into why this works. The main fork spawns N many forks (in the example above 5), each of which goes into an accept loop:

# simple pre-fork server, fork before accept
for i in range(int(argv[2])):
    # fork our current process
    pid = os.fork()

    # if we are the child fork ...
    if 0 == pid:
        # die without unhandled exception
        for signum in ( signal.SIGINT, signal.SIGTERM, ):
            signal.signal(signum, _gogentle)

        # under the hood, this calls `socket.accept`
        s.serve_forever()
        os._exit(0)

    # if we are the papa fork
    else:
        _PIDS.append(pid)

These child forks will handle any incoming requests to localhost:9999. The main fork then drops into a select/waitpid combo loop:

# setup signal relaying for INT and TERM
for signum in ( signal.SIGINT, signal.SIGTERM, ):
    signal.signal(signum, _kronos)

# wait on the kids
while len(_PIDS):
    # 1s timeout here means we're checking for exiting children at most
    # 1x per second, prevents a busy loop
    reads, _, _ = select.select([sys.stdin], [], [], 1)
    if sys.stdin in reads:
        # blocking, read 1 line
        cmd = sys.stdin.readline()
        # kill ourselves ... kronos will propegate
        if cmd.strip() == 'exit':
            os.kill(os.getpid(), signal.SIGTERM)

    # check for exited children, non-blocking
    while True:
        pid, rc = os.waitpid(-1, os.WNOHANG)
        if not pid:
            break
        _PIDS.remove(pid)

The select will either indicate that stdin is ready for reading, in which case we will read 1 line from stdin, or it will timeout after at most 1s, in which case it will fall-through directly to our check for any exited children (using os.waitpid with the WNOHANG flag).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top