python socket and socketserver
Question: I've been trying to make a multi-client server and i finally have and it works
perfectly but what I want to do now is to instead of getting the clients
address, get the client to type their name in and then the program will say
"Bob: Hi guys" instead of "127.0.0.1: Hi guys".

I used a pre-made server and client from python docs. Here's the server:

    
    
    import socketserver
    
    class MyUDPHandler(socketserver.BaseRequestHandler):
    
    
        def handle(self):
            data = self.request[0].strip()
            name = self.request[0].strip()
            socket = self.request[1]
            print(name,"wrote:".format(self.client_address[0]))
            print(data)
            socket.sendto(data.upper(), self.client_address)
    
    if __name__ == "__main__":
        HOST, PORT = "localhost", 9999
        server = socketserver.UDPServer((HOST, PORT), MyUDPHandler)
        server.serve_forever()
    

And here's the client:

    
    
    import socket
    import sys
    
    HOST, PORT = "localhost", 9999
    data = "".join(sys.argv[1:])
    name = "".join(sys.argv[1:])
    
    
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    sock.sendto(bytes(name + "Bob", 'utf-8'), (HOST, PORT))
    sock.sendto(bytes(data + "hello my name is Bob", "utf-8"), (HOST, PORT))
    received = str(sock.recv(1024), "utf-8")
    
    print("Sent:     {}".format(data))
    print("Received: {}".format(received))
    

Everything works fine but for some reason I get this in the server once the
client has connected.

    
    
    b'Bob'  wrote:
    b'Bob'
    b'hello my name is bob'  wrote:
    b'hello my name is bob'
    

I want it to be like:

    
    
    Bob wrote:
    b'Hello my name is bob'
    

I hope someone can help me, thanks.


Answer: You've got multiple problems here.

* * *

The first is that you're printing `bytes` objects directly:

    
    
    print(name,"wrote:".format(self.client_address[0]))
    

That's why you get `b'Bob' wrote:` instead of `Bob wrote:`. When you print a
`bytes` object in Python 3, this is what happens. If you want to decode it to
a string, you have to do that explicitly.

You have code that does that all over the place. It's usually cleaner to use
the `decode` and `encode` methods than the `str` and `bytes` constructors, and
if you're already using `format` there are even nicer ways to deal with this,
but sticking with your existing style:

    
    
    print(str(name, "utf-8"), "wrote:".format(self.client_address[0]))
    

* * *

Next, I'm not sure why you're calling `format` on a string with no format
parameters, or why you're mixing multi-argument `print` functions and `format`
calls together. It looks like you're trying to get `self.client_address[0]`,
but you're not doing that. Then again, your desired output doesn't show it,
so… just remove that `format` call if you don't want it, add a `{}` somewhere
in the format string if you do. (You're also probably going to want to decode
`client_address[0]`, too.)

* * *

Next, you store the same value in `name` and `data`:

    
    
    data = self.request[0].strip()
    name = self.request[0].strip()
    

So, when you later do this:

    
    
    print(data)
    

… that's just printing `name` again—and, again, without decoding it. So, even
if you fix the decoding problem, you'll still get this:

    
    
    Bob wrote:
    Bob
    

… instead of just:

    
    
    Bob wrote:
    

To fix that, just get rid of the `data` variable and the `print(data)` call.

* * *

Next, you're sending two separate packets, one with the `name` and the other
with the `data`, but trying to recover both out of each packet. So, even if
you fix all of the above, you're going to get `Bob` in one packet as the name,
and `hello my name is bob` in the next packet, resulting in:

    
    
    Bob wrote:
    hello my name is bob wrote:
    

If you want this to be stateful, you need to actually store the state
somewhere. In your case, the state is incredibly simple—just a flag saying
whether this is the first message from a given client—but it still has to go
somewhere. One solution is to associate a new state with each address using a
dictionary—although in this case, since the state is either "seen before" or
nothing at all, we can just use a set.

Putting it all together:

    
    
    class MyUDPHandler(socketserver.BaseRequestHandler):
        def __init__(self, *args, **kw):
            self.seen = set()
            super().__init__(*args, **kw)
    
        def handle(self):
            data = self.request[0].strip()
            addr = self.client_address[0]
            if not addr in self.seen:
                print(str(data, "utf-8"), "wrote:")
                self.seen.add(addr)
            else:
                print(str(data, "utf-8"))
            socket.sendto(data.upper(), self.client_address)
    

* * *

Meanwhile, it seems like what you _actually_ want is to store the name from
the first request as your per-client state, so you can reuse it in every
future request. That's almost as easy. For example:

    
    
    class MyUDPHandler(socketserver.BaseRequestHandler):
        def __init__(self, *args, **kw):
            self.clients = {}
            super().__init__(*args, **kw)
    
        def handle(self):
            data = str(self.request[0].strip(), 'utf-8')
            addr = self.client_address[0]
            if not addr in self.clients:                
                print(data, "joined!")
                self.clients[addr] = data
            else:
                print(self.clients[addr], 'wrote:', data)
            socket.sendto(data.upper(), self.client_address)
    

