我用Python编写了一个服务器。此服务器正在侦听端口,并且每当新客户端连接时,它都会创建一个新线程来管理与此客户端的通信(服务器将返回接受新的传入连接):
import socket
import select
import thread
import sys
import time
class Server(object):
def __init__(self, host, port):
self.host = host
self.port = port
# Sockets creation
self.sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.i = 0
def open(self):
try:
self.sck.bind((self.host, self.port))
# Set maximum number of queued connections to 5
self.sck.listen(5)
except socket.error, (errno, msg):
print 'Error: ' + str(errno) + ' - ' + str(msg)
sys.exit(1)
def close(self):
self.sck.close()
def loop(self):
while True:
conn, addr = self.sck.accept()
thread.start_new_thread(self.handle, (conn, ))
print 'Connected with ' + addr[0] + ':' + str(addr[1])
def run(self):
self.open()
self.loop()
self.close()
def handle(self, conn):
conn.send('Welcome! Type something and hit enter.\n')
while True:
data = conn.recv(1024)
if not data:
break
if data == 'count\n':
conn.send(str(self.i) + '\n')
else:
self.i += 1
conn.close()
test = Server('localhost', 2020)
test.run()
请注意Server
类有一个变量self.i
,当客户端发送消息count\n
时会返回该变量。如果消息不同,则此变量将递增。当我用ncat
手动连接到服务器时,这似乎工作得很好(连接多个终端的多个客户端也很好):
[terminal 1]$ ncat localhost 2020
Welcome! Type something and hit enter.
[terminal 2]$ ncat localhost 2020
Welcome! Type something and hit enter.
[terminal 1]$ count
0
foo
count
1
[terminal 2]$ count
1
qwer
asdf
[terminal 1]$ count
3
当我尝试尽可能快地发送大量消息时出现问题。我想做的是能够连接(例如)10
个客户端(10
个线程),并同时发送来自每个线程的1000
个消息。然后,当我查看变量self.i
时,我希望它是10 * 1000 = 10000
。
我一定做错了,或者我可能会遗漏一些问题,因为这个(当连续/快速接收到许多消息时递增self.i
)即使使用单个客户端/线程也不起作用。
我尝试使用ncat
但没有成功:
seq 1000 | ncat localhost 2020
使用Python客户端也没有成功:
import socket
import select
import thread
import sys
import time
class Client(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.sck = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def open(self):
try:
self.sck.connect((self.host, self.port))
except socket.error, (errno, msg):
print 'Error: ' + str(errno) + ' - ' + str(msg)
sys.exit(1)
def close(self):
self.sck.close()
def loop(self):
for i in range(1000):
self.sck.send(str(i))
def run(self):
self.open()
self.loop()
self.close()
test = Client('localhost', 2020)
test.run()
在这两种情况下,self.i
都会增加,但很少,不会像预期的那样增加。
我做错了什么?在发送下一条消息之前,是否必须等待服务器的响应/确认?也许这种方法不正确,线程不应该修改服务器的self.i
变量?
答案 0 :(得分:2)
几乎每个初学者到套接字编程都会犯同样的错误,因为此时互联网上99%的教程都是错误的。
你的问题就在这一行
data = conn.recv(1024)
TCP是一种流媒体协议。这意味着,data
最多可包含1024个字节或更少。甚至只有1个字节是可能的,命令'count'可以分成5个单独的recv
调用。在你的情况下,它是相反的。一个recv
接收许多命令,最多1024个字节。很多你的柜台都比预期的少得多。
因此,您必须将传入的流解析为消息,这些消息在您的情况下由换行符分隔:
def handle_message(self, conn, message):
if data == 'count':
conn.sendall('%i\n' % self.i)
else:
self.i += 1
def handle(self, conn):
conn.sendall('Welcome! Type something and hit enter.\n')
buf = ''
while True:
data = conn.recv(1024)
buf += data
while '\n' in buf:
message, buf = buf.split('\n',1)
self.handle_message(conn, message)
if not data:
break
conn.close()
要始终发送数据sendall
。
答案 1 :(得分:1)
问题在于您没有锁定对该变量的访问权限。
基本上,这句话:
self.i += 1
可以拆分为这些不同的操作:
temp = self.i
temp = temp + 1
self.i = temp
如果两个线程设法读取相同的self.i
并继续他们的快乐方式,那么你就失去了一个增量。
简而言之,线程安全的代码不容易编写,但至少从锁定所有共享状态开始。
我不知道如何锁定Python,但你应该在网上或SO上找到大量的例子。