我将创建一个多人游戏" Snake Game"在Python中。在服务器上,我使用线程来处理多个客户端,但现在我不知道如何将套接字发送给所有客户端。我需要它来通知所有用户" food"当有人"吃食物时。"
这是我的服务器代码:
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()
答案 0 :(得分:2)
快速和彻底的解决方案是将所有客户端套接字存储在列表中,如下所示:
clientsockets = []
# ...
clientsocket, clientaddr = serversocket.accept()
clientsockets.append(clientsocket)
threading._start_new_thread(handler, (clientsocket, clientaddr))
当然,当客户端关闭时,请记住remove
列表中的套接字。
然后,向所有人发送消息:
for socket in clientsockets:
socket.send(msg)
除非你想要一些错误处理,否则一个死客户端不能将整个服务器关闭。
但是,这有一个问题:永远不能保证send
发送整个邮件。您可以使用sendall
来修复此问题,但这并不能保证是原子的;它总是有可能一个线程将发送一部分消息,然后另一个线程将发送其部分消息,而不是第一个线程将发送其余消息。
因此,您需要添加一些锁定。
更简单的解决方案是为每个客户端创建一个读线程和一个写线程,每个写线程都有一个queue.Queue
。与套接字不同,队列保证是原子的,因此您不必担心线程安全。
(您也可以创建一个"广播"队列,但是每个客户端都需要等待其广播队列及其特定于客户端的队列,此时您正在编写确切的队列同样类型的select
- 类似于您希望通过线程避免使用的代码。)
在阅读方面也有类似的问题。您只是致电recv
,期望只收到一条消息。但是sockets are byte streams, not message streams。我们无法保证您在一个recv
中无法获得半条消息或两条消息。您必须编写一些协议,并缓冲传入的字节并从缓冲区中分离出消息。
在这种情况下,您的消息只是文本行,(据我所知)消息中不可能嵌入换行符。这允许一个死简单的协议:每一行都是一条消息。并且socket.makefile
完全可以作为此协议的处理程序。像这样:
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
您可能需要考虑使用Client
类来包装两个处理程序和三个列表,而不是将它们全部分开,然后您还可以将客户列表作为类的属性来代替一个全球性的,但基本的想法是一样的。
答案 1 :(得分:0)
我建议使用已建立的网络服务器框架。网络问题很难诊断和修复。您的服务器代码将无法满足您的需求。就个人而言,我个人推荐Tornado。
答案 2 :(得分:0)
如果希望所有客户端线程都能够查看新数据,则需要在创建任何线程之前建立某种全局变量。线程的一个常见构造是为工作线程创建一个队列,以从中获取信息并进行处理;但问题是,每个线程都需要查看进入该队列的所有内容,因此线程无法从中获取内容。
这是我的建议:维护一个“消息”队列,其中包含线程需要传递给每个客户端的数据。将时间戳附加到每个消息,以便每个线程可以知道消息何时是新的还是旧的。每个线程将跟踪它发送的最新消息的时间戳,因此它将知道队列中的指令是否已经发送。如果每个线程将其时间戳保存在主线程可见的列表中,那么主线程可以查看并确保哪些指令被保证已被发送/发送给每个客户端,并且可以相应地删除过时的指令。
请记住,我没有必要使用线程一段时间,但我希望逻辑是正确的。正如其他人所说,可能还有其他框架可以让您更有效地获得所需的结果。但是如果你坚持使用“每个套接字都有自己的线程”机制,那么这应该让你开始考虑如何思考这个问题。