0

I will create a multiplayer "Snake Game" in Python. On the server I'm using threads to be able to handle multiple clients, but now I do not know what to do to send a socket to all clients. I'll need it to inform all users to the new position of "food" when someone "eat the food."

Here is my server code:

from socket import *
import threading, os, sys, random
#from constants import *
def isOnline(username):
 return username in playerList
def getNewColor():
 r = random.randrange(0, 255)
 g = random.randrange(0, 255)
 b = random.randrange(0, 255)
 return (r, g, b)
def genFood():
 x = random.randrange(1, 49)
 y = random.randrange(1, 49)
 return (x, y)
def handler(clientsocket, clientaddr):
 print ('New Client ', clientaddr)
 player = ''
 points = 0
 index = 0
 while 1:
 try:
 data = clientsocket.recv(1024).decode()
 if data != '':
 print(data)
 if data.split(' ')[0] == 'login':
 if isOnline(data.split(' ')[1]):
 clientsocket.send('already_on'.encode())
 else:
 color = getNewColor()
 clientsocket.send(('success ' + str(color)).encode())
 player = data.split(' ')[1]
 index = playerList.index(player)
 playerList.append(player)
 pointList.append(0)
 if player != '':
 if data == 'eat':
 points += 1
 pointList[index] = points
 foodX, foodY = genFood() 
 except:
 print('Disconnected Client')
 clientsocket.close()
 if player != '':
 del playerList[index]
 del pointList[index]
 return 0
addr = ('localhost', 50000)
serversocket = socket(AF_INET, SOCK_STREAM)
serversocket.bind(addr)
serversocket.listen(20)
playerList = []
pointList = []
while 1:
 clientsocket, clientaddr = serversocket.accept()
 threading._start_new_thread(handler, (clientsocket, clientaddr))
serversocket.close()
asked Jul 28, 2014 at 22:21
5
  • 3
    Well at least it's an appropriate language to create a snake game. Commented Jul 28, 2014 at 22:24
  • So you are able to create multiple socket connections successfully using threads? And the issue is sending out messages to all clients, not maintaining messages. Is that correct? Commented Jul 28, 2014 at 22:26
  • 1
    As a side note, you cannot expect each recv to contain exactly one command; it could contain half a command, or two commands. You need to write a protocol. For your use case, since your commands are all plain text with no possible embedded newlines, I'd suggest using "each message is a line" as your protocol, so you can use f = clientsocket.makefile('rb') and then just for line in f: as your main loop. Commented Jul 28, 2014 at 22:27
  • @TheSoundDefense yes, just want to send one message to all clients, but the problem is that each thread has its clientsocket, does not have access to this variable in other threads. Commented Jul 28, 2014 at 22:28
  • As an alternative to the solutions posted, I would suggest using a single thread design with non-blocking sockets. You don't gain a great deal by using threads with Python due to the GIL, and it adds a lot of complexity. Commented Jul 28, 2014 at 22:50

3 Answers 3

2

The quick&dirty solution is to store all of the client sockets in a list, like this:

clientsockets = []
# ...
clientsocket, clientaddr = serversocket.accept()
clientsockets.append(clientsocket)
threading._start_new_thread(handler, (clientsocket, clientaddr))

And of course remember to remove the socket from the list when a client closes.

Then, to send a message to everyone:

for socket in clientsockets:
 socket.send(msg)

Except that you'll want some error handling there so one dead client doesn't bring the whole server down.


However, this has a problem: A send is never guaranteed to send the entire message. You can fix that by using sendall instead, but that's not guaranteed to be atomic; it's always possible that one thread will send part of a message, then another thread will send part of its message, than the first thread will send the rest of its message.

So, you will need to add some locking.

A simpler solution is to create a read thread and a write thread for each client, with a queue.Queue for each write thread. Queues, unlike sockets, are guaranteed to be atomic, so you don't need to worry about thread safety.

(You can also just create a single "broadcast" queue, but then each client needs to wait on its broadcast queue and its client-specific queue, at which point you're writing the exact same kind of select-like code you were hoping to avoid with threads.)


There's also a similar problem on the read side. You're just calling recv, expecting to get exactly one message. But sockets are byte streams, not message streams. There's no guarantee that you won't get half a message, or two messages, in a single recv. You have to write some protocol, and buffer up incoming bytes and split off the messages from the buffer.

In this case, your messages are just lines of text, with (as far as I can tell) no possibility of embedded newlines in the messages. This allows for a dead-simple protocol: each line is a message. And socket.makefile works perfectly as a handler for this protocol. Like this:

def handler(clientsocket, clientaddr):
 # your existing initial setup code
 with clientsocket.makefile('rb') as f:
 for data in f:
 try:
 # your existing loop, minus the recv command

You may want to consider using a Client class to wrap up the two handlers and the three lists, instead of keeping them all separate, and then you can also make the list of clients an attribute of the class instead of a global, but the basic idea is the same.

answered Jul 28, 2014 at 22:33
Sign up to request clarification or add additional context in comments.

Comments

1

If you want all of your client threads to be able to see new data, then you need to establish some sort of global variable before any of the threads are created. A common construct for threads is to create a queue for the worker threads to take information from and process; the problem, though, is that you need for each thread to see everything that goes into that queue, so threads can't be taking things out of it.

Here's my suggestion: maintain a queue of "messages" that include data the threads need to pass along to each client. Attach a timestamp to each of these messages, so that each thread can know when a message is new or old. Each thread will keep track of the timestamp of the most recent message it sent, so it will know if an instruction in the queue is something that it's already sent. If each thread keeps its timestamp in a list that is visible to the main thread, then the main thread can look and see which instructions are guaranteed to have been seen/sent to every client, and can remove outdated instructions accordingly.

Keep in mind I haven't had to work with threads for a little while, but I'm hoping the logic is sound. And as others have said, there may be other frameworks that will get you the results you want more efficiently. But if you're sticking with the "each socket has its own thread" mechanism, this should get you started with how to think this through.

answered Jul 28, 2014 at 22:37

1 Comment

The only problem with this is that the lists of messages aren't atomic, so you're going to need to add the locking in anyway, at which point the queues aren't really helping anymore. (Of course you sometimes do need both queues and explicitly locked objects, but if you can get by with just queues, it's a lot easier.) As long as you can afford to waste space putting a reference to each message in each client's queue (which you almost always can), this extra complexity isn't necessary. (For something like a high-throughput streaming video server, it might be necessary...)
0

I would recommend using an established webserver framework. Network issues are hard to diagnose and fix. Your server code as-is will have trouble responding to your demands. Personally I recommend Tornado for simple applications like this.

answered Jul 28, 2014 at 22:33

4 Comments

Except that he's not trying to do this over HTTP, and there's really no reason to add an extra layer here. It's worth learning how to do TCP/IP programming, write protocols, etc., and now is as good a time as any for him to learn.
thanks for the suggestion, but I intend to use python :)
@user3500017: Tornado is a Python web server framework, where you write all your handlers in Python. (And then you can write the client side in Python too, using urllib.request or requests or whatever you prefer.) But using HTTP raises issues of its own—e.g., it's a request-response protocol, so if you want to be able to send unsolicited commands from server to client you need some kind of long-polling "idle" command. It's worth learning how to do things that way as well, but it's not an either-or thing; you'll want to learn both.
Tornado also supports websockets which are like a standardized way of doing this simple server/client back and forth.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.