Python 带有中央中继服务器的多线程套接字
我以前曾设法实现了一个客户机-服务器套接字脚本,它在单个客户机和服务器之间传递消息,现在我正在尝试实现一个多客户机系统 更具体地说,我想使用服务器作为两个客户端之间的某种媒介,从一个客户端检索信息并将其转发给另一个客户端。我试图附加并发送接收客户端的端口号,然后从服务器端的消息中提取它。在那之后,我会尝试将它发送到具有该端口号的任何套接字,但我遇到了一些问题(因为端口号是在发送时确定的,我相信?),所以现在我只是尝试将发送的消息中继回所有客户端。但是,问题是消息只发送到服务器,而没有中继到所需的客户端 我以前曾试图实现点对点系统,但我遇到了麻烦,所以我决定退后一步,改为这样做 Server.py:Python 带有中央中继服务器的多线程套接字,python,multithreading,sockets,tkinter,Python,Multithreading,Sockets,Tkinter,我以前曾设法实现了一个客户机-服务器套接字脚本,它在单个客户机和服务器之间传递消息,现在我正在尝试实现一个多客户机系统 更具体地说,我想使用服务器作为两个客户端之间的某种媒介,从一个客户端检索信息并将其转发给另一个客户端。我试图附加并发送接收客户端的端口号,然后从服务器端的消息中提取它。在那之后,我会尝试将它发送到具有该端口号的任何套接字,但我遇到了一些问题(因为端口号是在发送时确定的,我相信?),所以现在我只是尝试将发送的消息中继回所有客户端。但是,问题是消息只发送到服务器,而没有中继到所需的
import socket, _thread, threading
import tkinter as tk
SERVERPORT = 8600
HOST = 'localhost'
class Server():
def __init__(self):
self.Connected = True
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
self.ServerSocket.bind((HOST, SERVERPORT))
self.ServerSocket.listen(2)
self.Clients = []
def Listen(self):
print('Server is now running')
while self.Connected:
ClientSocket, Address = self.ServerSocket.accept()
self.Clients.append(Address)
print('\nNew user connected', Address)
t = threading.Thread(target=self.NewClient, args=(ClientSocket,
Address))
t.daemon = True
t.start()
self.Socket.close()
def NewClient(self, ClientSocket, Address):
while self.Connected:
if ClientSocket:
try:
ReceivedMsg = ClientSocket.recv(4096)
print('Message received from', Address, ':', ReceivedMsg)
self.Acknowledge(ClientSocket, Address)
if ReceivedMsg.decode('utf8').split()[-1] != 'message':
ReceiverPort = self.GetSendPort(ReceivedMsg)
self.SendToClient(ClientSocket,ReceivedMsg,ReceiverPort)
except:
print('Connection closed')
raise Exception
ClientSocket.close()
def Acknowledge(self, Socket, Address):
Socket.sendto(b'The server received your message', Address)
def GetSendPort(self, Msg):
MsgDigest = Msg.decode('utf8').split()
return int(MsgDigest[-1])
def SendToClient(self, Socket, Msg, Port):
Addr = (HOST, Msg)
for Client in self.Clients:
Socket.sendto(Msg, Client)
def NewThread(Func, *args):
if len(args) == 1:
t = threading.Thread(target=Func, args=(args,))
elif len(args) > 1:
t = threading.Thread(target=Func, args=args)
else:
t = threading.Thread(target=Func)
t.daemon = True
t.start()
t.join()
Host = Server()
NewThread(Host.Listen)
和客户端(.py):
我希望在用户按下tkinter窗口中的send按钮时发送一条消息,但同时,它会持续“监听”以查看是否收到任何消息
我之前还尝试在另一个线程中的客户端中运行GetResponse
方法,而不是if self.Connected
我在self.Connected时使用,但仍然无法工作
更新
在发表了一些有用的评论之后,我对这两个文件进行了如下编辑:
服务器现在为首先运行的每个客户端保留两个套接字。服务器文件作为模块导入到客户机文件中。然后运行每个客户端文件,每个客户端在服务器文件中运行一个函数,请求使用套接字。如果请求是允许的(即没有抛出错误),则连接套接字,将其添加到存储在服务器文件中的一组客户端,然后返回到客户端文件。然后,客户端使用此套接字发送和接收消息
Server.py
import socket, _thread, threading
import tkinter as tk
SERVERPORT = 8600
HOST = 'localhost'
class Server():
def __init__(self):
self.Connected = True
self.ServerSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ServerSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
self.ServerSocket.bind((HOST, SERVERPORT))
self.ServerSocket.listen(2)
self.Clients = {}
def ConnectClient(self, Username, Port):
Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.Clients[Username] = [Socket, Port, False]
try:
self.Clients[Username][0].connect((HOST, SERVERPORT))
self.Clients[Username][2] = True
print('Opened port for user', Username)
return Socket
except Exception:
print('Could not open port for user', Username)
raise Exception
def Listen(self):
print('Server is now running')
while self.Connected:
ClientSocket, Address = self.ServerSocket.accept()
print('\nNew user connected', Address)
t = threading.Thread(target=self.NewClient, args=(ClientSocket,
Address))
t.daemon = True
t.start()
self.Socket.close()
def NewClient(self, ClientSocket, Address):
while self.Connected:
if ClientSocket:
try:
ReceivedMsg = ClientSocket.recv(4096)
if b'attempting to connect to the server' in ReceivedMsg:
ClientSocket.send(b'You are now connected to the server')
else:
print('Message received from', Address, ':',ReceivedMsg)
#self.Acknowledge(ClientSocket, Address)
ReceiverPort = self.GetSendPort(ReceivedMsg)
if ReceiverPort != None:
self.SendToClient(ClientSocket,ReceivedMsg,
ReceiverPort)
except:
print('Connection closed')
raise Exception
ClientSocket.close()
def Acknowledge(self, Socket, Address):
Socket.sendto(b'The server received your message', Address)
def GetSendPort(self, Msg):
MsgDigest = Msg.decode('utf8').split()
try:
Port = int(MsgDigest[-1])
except ValueError:
Port = None
return Port
def SendToClient(self, Socket, Msg, Port):
Addr = (HOST, Port)
Receiver = None
for Client, Vars in self.Clients.items():
if Vars[1] == Port:
Receiver = Client
self.Clients[Receiver][0].sendto(Msg, Addr)
def NewThread(Func, *args):
if len(args) == 1:
t = threading.Thread(target=Func, args=(args,))
elif len(args) > 1:
t = threading.Thread(target=Func, args=args)
else:
t = threading.Thread(target=Func)
t.daemon = True
t.start()
t.join()
Host = Server()
if __name__ == '__main__':
NewThread(Host.Listen)
和Client.py
import socket, threading, Server
import tkinter as tk
Username = 'Ernest'
PORT = 8601
OtherPORT = 8602
SERVERPORT = 8600
HOST = '127.0.0.1'
class Client():
def __init__(self, Username):
self.Connected, self.Username = False, Username
def Connect(self):
print('Requesting to connect to server')
try:
self.Socket = Server.Host.ConnectClient(self.Username, PORT)
self.Connected = Server.Host.Clients[self.Username][2]
Msg = '{} is attempting to connect to the server'.format(self.Username)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
ReceivedMsg = self.Socket.recv(4096)
print(ReceivedMsg)
Msg = MsgUI(self.Username)
Msg.Display()
except Exception:
print('Could not connect to server')
raise Exception
def SendMsg(self):
try:
if self.Connected:
Msg = '{} sent you a message {}'.format(self.Username,OtherPORT)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
self.GetResponse()
except Exception:
print('Connection closed')
raise Exception
def GetResponse(self, *args):
AckMsg = '\n{} received the message'.format(self.Username)
NMsg = '\n{} did not receive the message'.format(self.Username)
if self.Connected:
Msg = self.Socket.recv(4096)
print(Msg)
if Msg:
self.Socket.sendall(bytes(AckMsg, encoding='utf8'))
else:
self.Socket.sendall(bytes(NMsg, encoding='utf8'))
class MsgUI():
def __init__(self, Username):
self.Username = Username
self.entry = tk.Entry(win)
self.sendbtn = tk.Button(win, text='send', command=Peer.SendMsg)
def Display(self):
self.entry.grid()
self.sendbtn.grid()
win.mainloop()
win = tk.Tk()
Peer = Client(Username)
Peer.Connect()
现在的问题更多的是python和范围问题。当尝试将消息中继回客户机时,我得到了一个KeyError
,因为Clients
字典仍然是空的。在客户端文件中对服务器进行函数调用时,很明显,对字典的更新发生在客户端文件中,而不是服务器文件中,服务器文件位于不同的实例中。我需要一种方法来更改客户端
字典的内容,该字典由客户端文件调用以执行操作,但在服务器文件中生效。您是否承诺使用多线程?线程在python中不并发运行(由于GIL),虽然它们是处理并发操作的一种方式,但它们不是唯一的方式,通常也不是最好的方式,除非它们是唯一的方式。考虑这个代码,它不能很好地处理失败案例,但似乎是一个起点。p>
import socket, select, Queue
svrsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
svrsock.setblocking(0)
svrsock.bind(('', 17654))
svrsock.listen(16)
client_queues = {}
write_ready=[] # we'll update this for clients only that have things in the queue
while client_queues.keys() + [svrsock] :
readable, writable, exceptional = select.select(client_queues.keys() + [svrsock] , write_ready, [])
for rd in readable:
if rd is svrsock: # reading listening socket == accepting connection
conn, addr = svrsock.accept()
print("Connection from {}".format(addr))
conn.setblocking(0)
client_queues[conn] = Queue.Queue()
else:
data = rd.recv(1024)
if data:
# TODO: send to all queues
print("Message from {}".format(rd.getpeername()))
for sock, q in client_queues.iteritems():
q.put("From {}: {}".format( rd.getpeername(), data))
if sock not in write_ready:
write_ready.append(sock)
for rw in writable:
try:
data = client_queues[rw].get_nowait()
rw.send(data)
except Queue.Empty:
write_ready.remove(rw)
continue
这个概念很简单。服务器接受连接;每个连接(套接字)都与一个挂起消息队列相关联。每个准备读取的套接字都从中读取,其消息被添加到每个客户机的队列中。收件人客户端将被添加到具有挂起数据的客户端的write_ready
列表中(如果尚未在列表中)。然后,每个准备好写入的套接字都会将其下一个排队消息写入其中。如果没有更多的邮件,收件人将从write\u ready
列表中删除
如果不使用多线程,这非常容易协调,因为所有协调都是应用程序顺序中固有的。对于线程来说,这将更加困难,代码也会多得多,但可能不会因为gil而提高性能
在没有多线程的情况下并发处理多个I/O流的秘密是select
。原则上这很容易;我们传递了select()
一个可能用于读取的套接字列表,另一个可能用于写入的套接字列表,以及一个最终列表,对于这个简化的演示,我完全忽略了这个列表。select调用的结果将包括一个或多个实际准备好读取或写入的套接字,这允许我阻止,直到一个或多个套接字准备好进行活动。然后,我处理所有为每次传递活动准备好的套接字(但它们已经被过滤到那些不会阻塞的套接字)
这里还有很多事情要做。我自己不清理,不跟踪关闭的连接,不处理任何异常,等等。但是,不必担心线程和并发性保证,解决这些缺陷非常容易
这里是“行动中”。对于客户端,我使用netcat,它非常适合于没有第4层+协议的第3层测试(换句话说,可以说是原始tcp)。它只需打开一个到给定目标和端口的套接字,通过套接字发送其stdin,并将其套接字数据发送到stdout,这使它非常适合演示此服务器应用程序
我还想指出,在服务器和客户端之间耦合代码是不可取的,因为在不破坏另一个的情况下,您将无法对其中任何一个进行更改。服务器和客户机之间最好有一个“契约”,并对其进行维护。即使在同一代码库中实现服务器和客户端的行为,也应该使用tcp通信契约来驱动实现,而不是代码共享。只有我的2美分,但一旦你开始共享代码,你就会开始以你意想不到的方式耦合服务器/客户端版本
服务器:
$ python ./svr.py
Connection from ('127.0.0.1', 52059)
Connection from ('127.0.0.1', 52061)
Message from ('127.0.0.1', 52061)
Message from ('127.0.0.1', 52059)
Message from ('127.0.0.1', 52059)
第一客户(52059):
第二个客户:
$ nc localhost 17654
From ('127.0.0.1', 52061): hello
hello
From ('127.0.0.1', 52059): hello
hello
From ('127.0.0.1', 52059): hello
如果您需要更详细地理解为什么<代码>选择比并行执行更具吸引力,请考虑:Apache基于线程模型,换句话说,T
$ nc localhost 17654
hello
From ('127.0.0.1', 52061): hello
From ('127.0.0.1', 52059): hello
From ('127.0.0.1', 52059): hello
$ nc localhost 17654
From ('127.0.0.1', 52061): hello
hello
From ('127.0.0.1', 52059): hello
hello
From ('127.0.0.1', 52059): hello