线程和服务器客户端应用程序:尝试从多个线程增加计数器

时间:2014-07-18 15:54:45

标签: python

我用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变量?

2 个答案:

答案 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上找到大量的例子。