简体   繁体   English

多人蛇游戏Python

[英]Multiplayer Snake Game Python

I will create a multiplayer "Snake Game" in Python. 我将在Python中创建一个多人游戏“ Snake Game”。 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()

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. 当然,请记住,当客户端关闭时,从列表中remove套接字。

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. 但是,这有一个问题:一个send从不保证整封邮件发送。 You can fix that by using sendall instead, but that's not guaranteed to be atomic; 您可以改为使用sendall来解决此问题,但这不能保证是原子性的。 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. 一个更简单的解决方案是为每个客户端创建一个读线程和一个写线程,并为每个写线程创建一个queue.Queue 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.) (您也可以只创建一个“广播”队列,但是每个客户端都需要等待其广播队列和特定于客户端的队列,此时,您正在编写与希望的完全相同的select类代码避免使用线程。)


There's also a similar problem on the read side. 阅读方面也存在类似的问题。 You're just calling recv , expecting to get exactly one message. 您只是在调用recv ,期望得到一条消息。 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 . 不能保证在单个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. socket.makefile可以完美地用作此协议的处理程序。 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. 您可能需要考虑使用Client类包装两个处理程序和三个列表,而不是将它们全部分开,然后还可以将客户端列表作为类的属性,而不是全局的,但基本想法是一样的。

I would recommend using an established webserver framework. 我建议使用已建立的Web服务器框架。 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. 我个人建议将Tornado用于此类简单的应用程序。

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. 但是,如果您坚持使用“每个套接字都有自己的线程”机制,那么这应该让您开始思考。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM