Python - Adding a state to a program
Question: I have this program that's supposed to take a user's lowercase sentence and
capitalize it. There's currently two states, one that takes in the message
(which I think is referred to as sockCanSend) and the other that capitalizes
it (sockCanReceive). The problem is that I'm supposed to add another
method/state so that the program is only in the send state if the buffer is
completely empty and only goes to the receive state if the buffer is
completely full. The intermediate state should be accessed if the buffer is
only partially full. I think this intermediate state is supposed to continue
accepting input up until the buffer is completely full, at which point it
changes state to the send state. Meanwhile, I think it's supposed to keep
capitalizing the user's message so long as a message is received. If you're in
this state and the buffer is empty, there's some sort of problem and the
connection should terminate. The instructor for this course (it's a network
course) didn't explain any of this very well and I've tried talking to him
about it but his explanation still didn't make any sense. I figured that in
order to reach this new state I'd need a new method (sockCanSendOrRecv) but
that's about all I know. How can I add this new state?

    
    
    from socket import *
    from select import select
    import sys                              # for exit
    
    # select sets used to map sock#->function to call
    rmap, wmap, xmap = {}, {}, {}           # read, write, except.
    stop = 0                                # set to true when should stop (on socket failure)
    
    # setup server socket
    serverSock = socket(AF_INET, SOCK_STREAM) # ipv4, tcp
    serverSock.bind(('', 2000))             # bind to port 2000
    serverSock.listen(3)                    # up to 3 pending accepts
    serverSock.setblocking(False)           # non-blocking (readable means accept will succeed)
    
    # constants for connection state
    readState = 0                           # when interested in reading
    writeState = 1                          # when interested in writing
    readOrWriteState = 2                    # when buffer is partially full
    
    class connection:
        def __init__(self, sock):           # constructor
            sock.setblocking(False)
            print "new connection on fd %d" % sock.fileno()
            self.sock = sock
            self.buf = bytearray(1024)
            self.fd = fd = sock.fileno()
            self.state = readState
            xmap[fd] = lambda : self.sockError()   # call sockError on sock error
            self.setSelect()
    
        def sockCanReceive(self):
            "called when socket is ready for pending read"
            fd, buf = self.fd, self.buf
            self.start = 0
            self.nbytes = nbytes = self.sock.recv_into(buf, 1024)
            print "rec'd %d bytes on fd %d: <%s>" % (self.nbytes, fd, buf[0:nbytes])
            if nbytes != 0:                                # read succeeded
                buf[0:nbytes] = str(buf[0:nbytes]).upper() # convert to uppercase
                self.state = writeState
                self.setSelect()
            else:                           # socket closed
                print "zero length read from %d.  Assuming that other end is closed." % fd
                self.close()
    
        def sockCanSend(self):
            "called when socket is ready for pending write"
            fd = self.fd
            start = self.start              # first unsent
            toSend = self.nbytes - start    # number of bytes to send
            sent = self.sock.send(self.buf[start:toSend])
            self.start += sent
            print "sent %d bytes of %d on fd %d" % (sent, toSend, fd)
            if sent == toSend:              # all sent, switching to receive
                self.state = readState
                self.setSelect()
    
        def sockCanSendOrRecv(self):
            "called when buffer is partially full"
    
    
        def sockError(self):
            "called when socket is in error condition"
            fd = self.fd
            print "socket error for %d. Shutting down."
            self.close()
        def close(self):
            "close down this connection"
            fd = self.fd
            print "closing connection on fd %d" % fd
            for x in rmap, wmap, xmap:      # remove from select sets
                if x.has_key(fd): del x[fd]
            try:
                self.sock.close()
            except:
                pass
        def setSelect(self):
            "set select read/write sensitivity based on connection state"
            fd = self.fd
            if self.state == readState:
                rmap[fd] = lambda : self.sockCanReceive() # interested in reading
                if wmap.has_key(fd): del wmap[fd]   # not interested in writing
            else:  # writeState
                wmap[fd] = lambda : self.sockCanSend()    # interested in writing
                if rmap.has_key(fd): del rmap[fd]     # not interested in reading
    
    
    def ssockAccept():
        "called when a client has connected to server sock"
        connSock, addr = serverSock.accept()
        print "accepted connection on fd %d from %s" % (connSock.fileno(), addr)
        connection(connSock)
    
    def ssockPanic():
        "called when the server socket has failed"
        print "Panic: listener socket failed.  Aborting."
        sys.exit(1)
    
    rmap[serverSock.fileno()] = ssockAccept # call ssockAccept() when client connects
    xmap[serverSock.fileno()] = ssockPanic  # call ssockPanic() if socket fails
    
    # this is the main select loop
    while not stop:
        print "select(r=%s, w=%s, x=%s, 5s)" % (rmap.keys(), wmap.keys(), xmap.keys())
        r,w,x = select(rmap.keys(), wmap.keys(), xmap.keys(), 5)
        print "=======> returned: r=%s, w=%s, x=%s" % (r,w,x)
        for fd in r:
            rmap[fd]()                      # call read handler
        for fd in w:
            wmap[fd]()                      # call write handler
        for fd in x:
            xmap[fd]()                      # call eXception handler
    


Answer: > I figured that in order to reach this new state I'd need a new method
> (sockCanSendOrRecv) but that's about all I know.

You don't need a new method for the new state `readOrWriteState`; just extend
`setSelect` to handle the state - e. g.

    
    
    def setSelect(self):
        "set select read/write sensitivity based on connection state"
        fd = self.fd
        if self.state == readState:
            rmap[fd] = lambda : self.sockCanReceive() # interested in reading
            if wmap.has_key(fd): del wmap[fd]     # not interested in writing
        elif self.state == readOrWriteState:
            rmap[fd] = lambda : self.sockCanReceive() # interested in reading
            wmap[fd] = lambda : self.sockCanSend()    # interested in writing            
        else:  # writeState
            wmap[fd] = lambda : self.sockCanSend()    # interested in writing
            if rmap.has_key(fd): del rmap[fd]     # not interested in reading
    

\- and adapt `sockCanReceive` to set `state` to `readOrWriteState` as long as
the buffer is not full, `sockCanSend` to set `state` to `readOrWriteState` as
long as the buffer is not empty, and both `sockCanReceive` and `sockCanSend`
to be able to handle a partially full buffer.

