views:

345

answers:

2

My code basically needs to start up a simple chat server with a client. Where the server and the client can talk back and forth to each other. I've gotten everything to be implemented correctly, but I can't figure out how to shut down the server whenever I'm done. (I know it's ss.shutdown()).

I'm wanting to end right now based on a keyword shared between the two (something like "bye"), but I don't know if I can somehow send a message to my SocketServer from BaseRequestHandler to shutdown() whenever it receives the message.

Eventually, my goal is to incorporate Tkinter to make a GUI, but I wanted to get everything else to work first, and this is my first time dealing with sockets in Python.

from sys import argv, stderr
from threading import Thread
import socket
import SocketServer
import threading
import sys

class ThreadedRecv(Thread):
    def __init__(self,socket):
        Thread.__init__(self)
        self.__socket = socket
        self.__message = ''
        self.__done = False
    def recv(self):
        while self.__message.strip() != "bye" and not self.getStatus():
            self.__message = self.__socket.recv(4096)
            print 'received',self.__message
        self.setStatus(True)

    def run(self):
        self.recv()

    def setStatus(self,status):
        self.__done = status

    def getStatus(self):
        return self.__done

class ThreadedSend(Thread):
    def __init__(self,socket):
        Thread.__init__(self)
        self.__socket = socket
        self.__message = ''
        self.__done = False
    def send(self):
        while self.__message != "bye" and not self.getStatus():
            self.__message = raw_input()
            self.__socket.send(self.__message)
        self.setStatus(True)

    def run(self):
        self.send()

    def setStatus(self,status):
        self.__done = status

    def getStatus(self):
        return self.__done



class HostException(Exception):
    def __init__(self, value):
        self.value = value
    def __str__(self):
        return repr(self.value)

class EchoServer(SocketServer.BaseRequestHandler):
    def setup(self):
        print self.client_address, 'is connected!'
        self.request.send('Hello ' + str(self.client_address) + '\n')
        self.__done = False
    def handle(self):

        sender = ThreadedSend(self.request)
        recver = ThreadedRecv(self.request)
        sender.start()
        recver.start()
        while 1:
            if recver.getStatus():
                sender.setStatus(True)
                break
            if sender.getStatus():
                recver.setStatus(True)
                break         

    def finish(self):
        print self.client_address, 'disconnected'
        self.request.send('bye client %s\n' % str(self.client_address))
        self.setDone(True)

    def setDone(self,done):
        self.__done = done

    def getDone(self):
        return self.__done



def setup(arg1, arg2, arg3):
    server = False
    defaultPort,defaultHost = 2358,"localhost"
    hosts = []
    port = defaultPort
    serverNames = ["TRUE","SERVER","S","YES"]
    arg1 = arg1.upper()
    arg2 = arg2.upper()
    arg3 = arg3.upper()
    if arg1 in serverNames or arg2 in serverNames or arg3 in serverNames:
    server = True
    try:
        port = int(arg1)
        if arg2 != '':
            hosts.append(arg2)
    except ValueError:
        if arg1 != '':
            hosts.append(arg1)
        try:
            port = int(arg2)
            if arg3 != '':
                hosts.append(arg3)
        except ValueError:
            if arg2 != '':
                hosts.append(arg2)
            try:
                port = int(arg3)
            except ValueError:
                if arg3 != '':
                    hosts.append(arg3)
                port = defaultPort

    for sn in serverNames:
        if sn in hosts:
            hosts.remove(sn)

    try:
        if len(hosts) != 1:
            raise HostException("Either more than one or no host "+ \
                                "declared.  Setting host to localhost.")
    except HostException as error:
        print error.value, "Setting hosts to default"
        return (server,defaultHost,port)

    return (server,hosts[0].lower(),port)

def main():
    bufsize = 4096
    while len(argv[1:4]) < 3:
        argv.append('')
    settings = setup(*argv[1:4])
    connections = (settings[1],settings[2])
    print connections
    if not settings[0]:
        try:
            mySocket = socket.socket(socket.AF_INET,\
                                     socket.SOCK_STREAM)
        except socket.error, msg:
            stderr.write("[ERROR] %s\n" % msg[1])
            sys.exit(1)
        try:
            mySocket.connect(connections)
        except socket.error, msg:
            stderr.write("[ERROR] %s\n" % msg[1])
            sys.exit(2)

        message = ""
        print "Enter a message to send to the server. "+\
              "Enter \"bye\" to quit."
        sender = ThreadedSend(mySocket)
        recver = ThreadedRecv(mySocket)
        sender.start()
        recver.start()
        while 1:
            if sender.getStatus():
                recver.setStatus(True)
                break
            if recver.getStatus():
                sender.setStatus(True)
                break    

    else:
        xserverhandler = EchoServer
        serversocket = SocketServer.ThreadedTCPServer(\
            connections,xserverhandler)
        server_thread = Thread(target = serversocket.serve_forever)
        server_thread.setDaemon(True)
        server_thread.start()
        # I would like to shut down this server whenever 
        # I get done talking to it.
        """while 1:
            if xserverhandler.getDone():
                print 'This is now true!'
                serversocket.shutdown()
                break"""

if __name__ == '__main__':
    main()

Yeah, I know setup() is a terrible function right now with the try's and catches, but it works for now, so I was going to fix it later.

My question is basically: How can I get the server to actually end based on a message that it receives? If possible, is there a way to access the Request Handler after it's started?

A: 

Request handler object is created for each new request. So you have to store "done" flag in server, not handler. Something like the following:

class EchoServer(SocketServer.BaseRequestHandler):
    ...
    def setDone(self):
        self.server.setDone() # or even better directly self.server.shutdown()
Denis Otkidach
+2  A: 

Please fix your code so it works, and include some way to use it. You need to add

class ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
    pass

since SocketServer doesn't actually include that class (at least not in my version of 2.6 nor 2.7). Instead, it's an example from the SocketServer definition.

Please include an example of how to start/use the code. In this case to start the server you need to do:

ss.py SERVER localhost 8001

and the client as

ss.py localhost 8001

If you do that then you can't do server_thread.setDaemon(True) because there are no other threads running, which means the server will exit immediately.

Once that's done the solution is to add a call (or two) to self.server.shutdown() insdie of your EchoServer.handle method, like:

    while 1:
        if recver.getStatus():
            sender.setStatus(True)
            self.server.shutdown()
            break

However, I can't get that to work, and I think it's because I inherited things wrong, or guessed wrong in what you did.

What you should do is search for someone else who has done a chat server in Python. Using Google I found http://www.slideshare.net/didip/socket-programming-in-python and there are certainly others.

Also, if you are going to mix GUI and threaded programming then you should look into examples based on that. There are a number of hits when I searched for "tkinter chat". Also, you might want to look into twisted, which has solved a lot of these problems already.

What problems? Well, for example, you likely want an SO_REUSEADDR socket option.

Andrew Dalke