Python中的线程同步

Python中的线程同步,python,multithreading,sockets,client,Python,Multithreading,Sockets,Client,我目前正在从事一个学校项目,其中的任务之一是建立一个线程服务器/客户端系统。当连接到系统中的每个客户机时,应该在服务器上为其分配自己的线程。此外,我希望服务器运行其他线程,一个涉及来自命令行的输入,另一个涉及向所有客户端广播消息。然而,我不能让它按我想要的方式运行。这两条线似乎在互相阻塞。我希望我的程序在服务器侦听连接的客户机的同时从命令行获取输入,等等 我是python编程和多线程的新手,尽管我认为我的想法很好,但我并不惊讶我的代码不起作用。问题是我不确定如何实现不同线程之间的消息传递。我也不

我目前正在从事一个学校项目,其中的任务之一是建立一个线程服务器/客户端系统。当连接到系统中的每个客户机时,应该在服务器上为其分配自己的线程。此外,我希望服务器运行其他线程,一个涉及来自命令行的输入,另一个涉及向所有客户端广播消息。然而,我不能让它按我想要的方式运行。这两条线似乎在互相阻塞。我希望我的程序在服务器侦听连接的客户机的同时从命令行获取输入,等等

我是python编程和多线程的新手,尽管我认为我的想法很好,但我并不惊讶我的代码不起作用。问题是我不确定如何实现不同线程之间的消息传递。我也不知道如何正确地实现资源锁命令。我将在这里发布我的服务器文件和客户端文件的代码,我希望有人能帮助我。我认为这实际上应该是两个相对简单的脚本。我试着在一定程度上尽可能好地评论我的代码

import select
import socket
import sys
import threading
import client

class Server:

#initializing server socket
def __init__(self, event):
    self.host = 'localhost'
    self.port = 50000
    self.backlog = 5
    self.size = 1024
    self.server = None
    self.server_running = False
    self.listen_threads = []
    self.local_threads = []
    self.clients = []
    self.serverSocketLock = None
    self.cmdLock = None
    #here i have also declared some events for the command line input
    #and the receive function respectively, not sure if correct
    self.cmd_event = event
    self.socket_event = event

def openSocket(self):
    #binding server to port
    try: 
        self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.server.bind((self.host, self.port))
        self.server.listen(5)
        print "Listening to port " + str(self.port) + "..."
    except socket.error, (value,message):
        if self.server:
            self.server.close()
        print "Could not open socket: " + message
        sys.exit(1)

def run(self):
    self.openSocket()

    #making Rlocks for the socket and for the command line input

    self.serverSocketLock = threading.RLock()
    self.cmdLock = threading.RLock()

    #set blocking to non-blocking
    self.server.setblocking(0)
    #making two threads always running on the server,
    #one for the command line input, and one for broadcasting (sending)
    cmd_thread = threading.Thread(target=self.server_cmd)
    broadcast_thread = threading.Thread(target=self.broadcast,args=[self.clients])
    cmd_thread.daemon = True
    broadcast_thread.daemon = True
    #append the threads to thread list
    self.local_threads.append(cmd_thread)
    self.local_threads.append(broadcast_thread)

    cmd_thread.start()
    broadcast_thread.start()


    self.server_running = True
    while self.server_running:

        #connecting to "knocking" clients
        try:
            c = client.Client(self.server.accept())
            self.clients.append(c)
            print "Client " + str(c.address) + " connected"

            #making a thread for each clientn and appending it to client list
            listen_thread = threading.Thread(target=self.listenToClient,args=[c])
            self.listen_threads.append(listen_thread)
            listen_thread.daemon = True
            listen_thread.start()
            #setting event "client has connected"
            self.socket_event.set()

        except socket.error, (value, message):
            continue

    #close threads

    self.server.close()
    print "Closing client threads"
    for c in self.listen_threads:
        c.join()

def listenToClient(self, c):

    while self.server_running:

        #the idea here is to wait until the thread gets the message "client
        #has connected"
        self.socket_event.wait()
        #then clear the event immidiately...
        self.socket_event.clear()
        #and aquire the socket resource
        self.serverSocketLock.acquire()

        #the below is the receive thingy

        try:
            recvd_data = c.client.recv(self.size)
            if recvd_data == "" or recvd_data == "close\n":
                print "Client " + str(c.address) + (" disconnected...")
                self.socket_event.clear()
                self.serverSocketLock.release()
                return

            print recvd_data

            #I put these here to avoid locking the resource if no message 
            #has been received
            self.socket_event.clear()
            self.serverSocketLock.release()
        except socket.error, (value, message):
            continue            

def server_cmd(self):

    #this is a simple command line utility
    while self.server_running:

        #got to have a smart way to make this work

        self.cmd_event.wait()
        self.cmd_event.clear()
        self.cmdLock.acquire()


        cmd = sys.stdin.readline()
        if cmd == "":
            continue
        if cmd == "close\n":
            print "Server shutting down..."
            self.server_running = False

        self.cmdLock.release()


def broadcast(self, clients):
    while self.server_running:

        #this function will broadcast a message received from one
        #client, to all other clients, but i guess any thread
        #aspects applied to the above, will work here also

        try:
            send_data = sys.stdin.readline()
            if send_data == "":
                continue
            else:
                for c in clients:
                    c.client.send(send_data)
            self.serverSocketLock.release()
            self.cmdLock.release()
        except socket.error, (value, message):
            continue

if __name__ == "__main__":
e = threading.Event()
s = Server(e)
s.run()
然后是客户端文件

import select
import socket
import sys
import server
import threading

class Client(threading.Thread):

#initializing client socket

def __init__(self,(client,address)):
    threading.Thread.__init__(self) 
    self.client = client 
    self.address = address
    self.size = 1024
    self.client_running = False
    self.running_threads = []
    self.ClientSocketLock = None

def run(self):

    #connect to server
    self.client.connect(('localhost',50000))

    #making a lock for the socket resource
    self.clientSocketLock = threading.Lock()
    self.client.setblocking(0)
    self.client_running = True

    #making two threads, one for receiving messages from server...
    listen = threading.Thread(target=self.listenToServer)

    #...and one for sending messages to server
    speak = threading.Thread(target=self.speakToServer)

    #not actually sure wat daemon means
    listen.daemon = True
    speak.daemon = True

    #appending the threads to the thread-list
    self.running_threads.append(listen)
    self.running_threads.append(speak)
    listen.start()
    speak.start()

    #this while-loop is just for avoiding the script terminating
    while self.client_running:
        dummy = 1

    #closing the threads if the client goes down
    print "Client operating on its own"
    self.client.close()

    #close threads
    for t in self.running_threads:
        t.join()
    return

#defining "listen"-function
def listenToServer(self):
    while self.client_running:

        #here i acquire the socket to this function, but i realize I also
        #should have a message passing wait()-function or something
        #somewhere
        self.clientSocketLock.acquire()

        try:
            data_recvd = self.client.recv(self.size)
            print data_recvd
        except socket.error, (value,message):
            continue

        #releasing the socket resource
        self.clientSocketLock.release()

#defining "speak"-function, doing much the same as for the above function       
def speakToServer(self):
    while self.client_running:
        self.clientSocketLock.acquire()
        try:
            send_data = sys.stdin.readline()
            if send_data == "close\n":
                print "Disconnecting..."
                self.client_running = False
            else:
                self.client.send(send_data)
        except socket.error, (value,message):
            continue

        self.clientSocketLock.release()

if __name__ == "__main__":
c = Client((socket.socket(socket.AF_INET, socket.SOCK_STREAM),'localhost'))
c.run()
我意识到这是相当多的代码行供您通读,但正如我所说的,我认为其中的概念和脚本本身应该很容易理解。如果有人能帮助我以适当的方式同步线程,那将是非常感激的=)

提前谢谢

---编辑---

嗯。因此,我现在已将代码简化为仅包含服务器和客户端模块中的发送和接收函数。连接到服务器的客户机有自己的线程,两个模块中的发送和接收函数在各自的线程中运行。这就像一个符咒,服务器模块中的广播功能将从一个客户端获得的字符串回送到所有客户端。到目前为止还不错

接下来我想让我的脚本做的事情是在客户端模块中执行特定的命令,即“close”,以关闭客户端,并将所有正在运行的线程加入到线程列表中。Im使用事件标志通知listenToServer和主线程speakToServer线程已读取输入“close”。似乎主线程跳出了while循环,启动了for循环,该循环应该连接其他线程。但它挂在这里。ListentServer线程中的while循环似乎从未停止过,即使在设置事件标志时,正在运行的server_应该设置为False

我在这里只发布客户机模块,因为我想让这两个线程同步的答案将涉及同步客户机和服务器模块中的更多线程

import select
import socket
import sys
import server_bygg0203
import threading
from time import sleep

class Client(threading.Thread):

#initializing client socket

def __init__(self,(client,address)):

threading.Thread.__init__(self) 
self.client = client 
self.address = address
self.size = 1024
self.client_running = False
self.running_threads = []
self.ClientSocketLock = None
self.disconnected = threading.Event()

def run(self):

#connect to server
self.client.connect(('localhost',50000))

#self.client.setblocking(0)
self.client_running = True

#making two threads, one for receiving messages from server...
listen = threading.Thread(target=self.listenToServer)

#...and one for sending messages to server
speak = threading.Thread(target=self.speakToServer)

#not actually sure what daemon means
listen.daemon = True
speak.daemon = True

#appending the threads to the thread-list
self.running_threads.append((listen,"listen"))
self.running_threads.append((speak, "speak"))
listen.start()
speak.start()

while self.client_running:

    #check if event is set, and if it is
    #set while statement to false

    if self.disconnected.isSet():
        self.client_running = False 

#closing the threads if the client goes down
print "Client operating on its own"
self.client.shutdown(1)
self.client.close()

#close threads

#the script hangs at the for-loop below, and
#refuses to close the listen-thread (and possibly
#also the speak thread, but it never gets that far)

for t in self.running_threads:
    print "Waiting for " + t[1] + " to close..."
    t[0].join()
self.disconnected.clear()
return

#defining "speak"-function      
def speakToServer(self):

#sends strings to server
while self.client_running:
    try:
        send_data = sys.stdin.readline()
        self.client.send(send_data)

        #I want the "close" command
        #to set an event flag, which is being read by all other threads,
        #and, at the same time set the while statement to false

        if send_data == "close\n":
            print "Disconnecting..."
            self.disconnected.set()
            self.client_running = False
    except socket.error, (value,message):
        continue
return

#defining "listen"-function
def listenToServer(self):

#receives strings from server
while self.client_running:

    #check if event is set, and if it is
    #set while statement to false

    if self.disconnected.isSet():
        self.client_running = False
    try:
        data_recvd = self.client.recv(self.size)
        print data_recvd
    except socket.error, (value,message):
        continue
return

if __name__ == "__main__":
c = Client((socket.socket(socket.AF_INET, socket.SOCK_STREAM),'localhost'))
c.run()
稍后,当我启动并运行这个服务器/客户机系统时,我将在实验室的一些电梯模型上使用这个系统,每个客户机接收楼层命令或“向上”和“向下”呼叫。服务器将运行分发算法,并更新最适合请求订单的客户机上的电梯队列。我意识到还有很长的路要走,但我想一个人应该一步一步地走


希望有人有时间调查此事。提前感谢。

我看到这段代码的最大问题是,您马上要做的事情太多,无法轻松调试您的问题。由于逻辑的非线性,线程可能变得极其复杂。尤其是当您不得不担心与锁同步时

您看到客户端相互阻塞的原因是您在服务器的ListentClient()循环中使用
serverSocketLock
的方式。老实说,这并不是你现在代码的问题,但当我开始调试它并将套接字转换为阻塞套接字时,它就成了问题。如果您将每个连接放在自己的线程中并从中读取,那么没有理由在这里使用全局服务器锁。它们都可以同时从自己的套接字读取数据,这就是线程的用途

以下是我给你的建议:

  • 摆脱所有不需要的锁和额外线程,从头开始
  • 让客户端像您一样连接,并像您一样将它们放入线程中。让他们每秒钟发送一次数据。验证是否可以连接和发送多个客户端,以及服务器是否正在循环和接收。此部件工作后,可以继续下一个部件
  • 现在,您已将套接字设置为非阻塞。这导致它们在数据未准备好时都在循环中快速旋转。由于您正在线程化,所以应该将它们设置为块。然后读取器线程将简单地坐着等待数据并立即响应 线程访问共享资源时使用锁。显然,在线程试图修改服务器属性(如列表或值)的任何时候,您都需要这样做。但当他们在自己的私有套接字上工作时就不是了

    您用来触发读者的事件在这里似乎没有必要。您已经收到客户端,然后启动线程。所以它已经准备好了

    简而言之,一次简化并测试一点。当它工作时,添加更多。现在线程和锁太多了

    下面是您的
    listenToClient
    方法的简化示例:

    def listenToClient(self, c):
        while self.server_running:
            try:
                recvd_data = c.client.recv(self.size)
                print "received:", c, recvd_data
                if recvd_data == "" or recvd_data == "close\n":
                    print "Client " + str(c.address) + (" disconnected...")
                    return
    
                print recvd_data
    
            except socket.error, (value, message):
                if value == 35:
                    continue 
                else:
                    print "Error:", value, message  
    

    备份你的工作,然后扔掉它-部分

    您需要分块实施您的程序,并在运行过程中对每个部分进行测试。首先,处理程序的
    输入部分。不要担心如何广播您收到的输入。相反,请担心您能够通过套接字成功地反复接收输入。到目前为止,一切都很好

    现在,我想你会对此做出反应