这是我的'游戏服务器'。这并不严重,我认为这是学习python和套接字的一些好方法。
首先,服务器类初始化服务器。 然后,当有人连接时,我们创建一个客户端线程。在这个帖子中,我们不断听取我们的套接字。
一旦某个命令进来(例如I12345001001),就会产生另一个线程。
最后一个线程的目的是向客户端发送更新。 但即使我看到服务器正在执行此代码,实际上并未发送数据。
有人能告诉它哪里出错吗? 这就像我必须在我能够发送之前收到一些东西。所以我猜我错过了某个地方
#!/usr/bin/env python
"""
An echo server that uses threads to handle multiple clients at a time.
Entering any line of input at the terminal will exit the server.
"""
import select
import socket
import sys
import threading
import time
import Queue
globuser = {}
queue = Queue.Queue()
class Server:
def __init__(self):
self.host = ''
self.port = 2000
self.backlog = 5
self.size = 1024
self.server = None
self.threads = []
def open_socket(self):
try:
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.bind((self.host,self.port))
self.server.listen(5)
except socket.error, (value,message):
if self.server:
self.server.close()
print "Could not open socket: " + message
sys.exit(1)
def run(self):
self.open_socket()
input = [self.server,sys.stdin]
running = 1
while running:
inputready,outputready,exceptready = select.select(input,[],[])
for s in inputready:
if s == self.server:
# handle the server socket
c = Client(self.server.accept(), queue)
c.start()
self.threads.append(c)
elif s == sys.stdin:
# handle standard input
junk = sys.stdin.readline()
running = 0
# close all threads
self.server.close()
for c in self.threads:
c.join()
class Client(threading.Thread):
initialized=0
def __init__(self,(client,address), queue):
threading.Thread.__init__(self)
self.client = client
self.address = address
self.size = 1024
self.queue = queue
print 'Client thread created!'
def run(self):
running = 10
isdata2=0
receivedonce=0
while running > 0:
if receivedonce == 0:
print 'Wait for initialisation message'
data = self.client.recv(self.size)
receivedonce = 1
if self.queue.empty():
print 'Queue is empty'
else:
print 'Queue has information'
data2 = self.queue.get(1, 1)
isdata2 = 1
if data2 == 'Exit':
running = 0
print 'Client is being closed'
self.client.close()
if data:
print 'Data received through socket! First char: "' + data[0] + '"'
if data[0] == 'I':
print 'Initializing user'
user = {'uid': data[1:6] ,'x': data[6:9], 'y': data[9:12]}
globuser[user['uid']] = user
print globuser
initialized=1
self.client.send('Beginning - Initialized'+';')
m=updateClient(user['uid'], queue)
m.start()
else:
print 'Reset receivedonce'
receivedonce = 0
print 'Sending client data'
self.client.send('Feedback: ' +data+';')
print 'Client Data sent: ' + data
data=None
if isdata2 == 1:
print 'Data2 received: ' + data2
self.client.sendall(data2)
self.queue.task_done()
isdata2 = 0
time.sleep(1)
running = running - 1
print 'Client has stopped'
class updateClient(threading.Thread):
def __init__(self,uid, queue):
threading.Thread.__init__(self)
self.uid = uid
self.queue = queue
global globuser
print 'updateClient thread started!'
def run(self):
running = 20
test=0
while running > 0:
test = test + 1
self.queue.put('Test Queue Data #' + str(test))
running = running - 1
time.sleep(1)
print 'Updateclient has stopped'
if __name__ == "__main__":
s = Server()
s.run()
答案 0 :(得分:3)
我不明白你的逻辑 - 特别是,为什么你故意设置两个线程同时写在同一个套接字上(它们都叫self.client
),没有任何同步或协调,似乎有可能导致问题的安排。
无论如何,你的代码中有一个明确的错误就是你使用send
方法 - 你似乎相信它保证发送它的所有参数字符串,但是这肯定是不案例,请参阅the docs:
返回发送的字节数。 应用程序负责 检查所有数据是否已发送; 如果只有一些数据 传输,应用程序需要 尝试交付剩余的 数据
sendall是您可能需要的方法:
与send()不同,此方法仍在继续 从字符串发送数据直到 所有数据都已发送或出错 发生。
其他问题包括updateClient
显然设计为永不终止(与其他两个线程类不同 - 当终止时,updateClient
实例不会,并且它们将保持不变运行并保持进程存活),冗余global
语句(无害,只是令人困惑),一些线程尝试读取dict(通过iteritems
方法)而其他线程正在更改它,同样没有任何锁定或协调等等 - 我确信可能会有更多的错误或问题,但是,在发现几个之后,一个人的眼睛往往会开始上釉; - )。
答案 1 :(得分:0)
你有三个主要问题。第一个问题可能就是你问题的答案。
阻止(问题)
socket.recv
正在阻止。这意味着暂停执行并且线程进入休眠状态,直到它可以从套接字读取数据。因此,您的第三个更新线程只是填充队列,但只有在您收到消息时它才会被清空。队列也一次被一条消息清空。
这可能是为什么除非您发送数据,否则它不会发送数据。
流协议上的消息协议
您正在尝试像消息流一样使用套接字流。我的意思是你有:
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
SOCK_STREAM
部分表示它是一个流而不是SOCK_DGRAM
之类的消息。但是,TCP不支持消息。所以你要做的就是构建消息,例如:
data =struct.pack('I', len(msg)) + msg
socket.sendall(data)
然后接收端将查找长度字段并将数据读入缓冲区。一旦有足够的数据在缓冲区中,它就可以获取整个消息。
您当前的设置正常工作,因为您的消息足够小,可以放在同一个数据包中,也可以放在一起放在套接字缓冲区中。但是,一旦您开始使用socket.send
或socket.sendall
通过多个调用发送大数据,您将开始读取多条消息和部分消息,除非您在套接字字节流之上实现消息协议。
<强>线程强>
即使线程在开始时更容易使用,但它们会带来很多问题,如果使用不当,尤其是在Python中,可能会降低性能。我喜欢线程,所以不要误会我的意思。 Python也存在GIL问题(全局解释器锁),因此在使用受CPU限制的线程时会出现性能不佳的问题。您的代码目前主要是I / O绑定,但将来它可能会受CPU限制。您还必须担心使用线程锁定。线程可以是快速修复但可能不是最好的修复。在某些情况下,线程很简单是打破一些耗时过程的最简单方法。所以不要把线程丢弃为邪恶或可怕。在Python中,由于并发问题,它们被认为主要是因为GIL和其他语言(包括Python),所以大多数人建议你使用Python的多个进程或使用异步代码。使用线程与否的主题非常复杂,因为它取决于语言(运行代码的方式),系统(单个或多个处理器)和争用(尝试与锁定共享资源)以及其他因素,但通常异步代码更快,因为它使用更多的CPU和更少的开销,特别是如果你不受CPU限制。
解决方案是使用Python中的select
模块或类似的东西。它将告诉您套接字何时有要读取的数据,并且您可以设置超时参数。
通过执行异步工作(异步套接字)可以获得更高的性能。要将套接字转换为异步模式,只需调用socket.settimeout(0)
即可使其无阻塞。但是,你会经常吃CPU旋转等待数据。 select
模块和朋友会阻止您旋转。
通常,对于性能,您希望尽可能多地执行异步(相同的线程),然后使用更多线程进行扩展,这些线程也尽可能地异步执行。但是如前所述,Python是这个规则的一个例外,因为GIL(全局解释器锁)实际上可以降低我读过的性能。如果你感兴趣,你应该尝试编写测试用例并找出答案!
您还应该检查threading
模块中的线程锁定原语。它们是Lock
,RLock
和Condition
。它们可以帮助多个线程共享数据而不会出现问题。
lock = threading.Lock()
def myfunction(arg):
with lock:
arg.do_something()
某些Python对象是线程安全的,而其他Python对象则不是。
异步发送更新(改进)
不是仅使用第三个线程来发送更新,而是使用客户端线程通过检查上次发送更新时的当前时间来发送更新。这会删除Queue
和Thread
的使用情况。此外,您必须将客户端代码转换为异步代码并在select
上超时,以便您可以间隔检查当前时间以查看是否需要更新。
<强>摘要强>
我建议你使用异步套接字代码重写代码。我还建议您为所有客户端和服务器使用单个线程。这将提高性能并减少延迟。它会使调试更容易,因为你没有可能的并发问题,就像你有线程一样。另外,在失败之前修复您的消息协议。