Python: Store and delete Threads to/from List - python

I want to implement a streaming server which sends and endless stream of data to all connected clients. Multiple clients should be able to connect and disconnect from the server in order to process the data in different ways.
Each client is served by a dedicated ClientThread, which sub-classes Thread and contains a queue of the data to be sent to the client (necessary, since clients might process data at different speeds and because bursts of data can occur which the clients might be unable to handle).
The program listens to incoming client connections via a seperate ClientHandlerThread. Whenever a client connects, the ClientHandlerThread spawns a ClientThread and adds it to a list.
As a dummy example, the main Thread increments an integer each second and pushes it to all ClientThread queues through ClientHandlerThread.push_item().
Every 10 increments, the number of items in the client queues is printed.
Now to my questions:
When a client disconnects, the Thread terminates and no more data is send, however, the ClientThread object remains in the ClientHandlerThreads list of clients and items are continuously pushed to its queue.
I'm therefore looking for either (1) a way to delete the ClientThread object from the list whenever a client disconnects, (2) a better way to monitor the ClientThreads than a list or (3) a different (better) architecture to archive my goal.
Many thanks!
Server
import socket
import time
from threading import Thread
from queue import Queue
class ClientThread(Thread):
def __init__(self, conn, client_addr):
Thread.__init__(self)
self.queue = Queue()
self.conn = conn
self.client_addr = client_addr
def run(self):
print('Client connected')
while True:
try:
self.conn.sendall(self.queue.get().encode('utf-8'))
time.sleep(1)
except BrokenPipeError:
print('Client disconnected')
break
class ClientHandlerThread(Thread):
def __init__(self):
Thread.__init__(self, daemon = True)
self.clients = list()
def push_item(self, item):
for client in self.clients:
client.queue.put(str(i))
def run(self):
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.bind('./socket')
s.listen()
i = 1
while True:
conn, client_addr = s.accept()
client = ClientThread(conn, client_addr)
client.start()
self.clients.append(client)
i += 1
if __name__ == '__main__':
client_handler = ClientHandlerThread()
client_handler.start()
i = 1
while True:
client_handler.push_item(str(i))
if i % 10 == 0:
print('[' + ', '.join([str(client.queue.qsize()) for client in client_handler.clients]) + ']')
i += 1
time.sleep(1)
Client:
import socket
if __name__ == '__main__':
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect('./socket')
print('Connected to server')
while True:
data = s.recv(1024)
if not data:
print('Disconnected from server')
break
print(data.decode('utf-8'))

Note You should probably read up on things like aiohttp for much more scalable versions to your answer.
For your question, you can make a few changes to achieve this:
First, change ClientThread's constructor:
class ClientThread(Thread):
def __init__(self, client_handler, conn, client_addr):
self.client_handler = client_handler
self.running = True
...
When the handler creates the object, it should pass self to it as client_handler.
In the run method, use
def run(self):
while True:
...
self.running = False
self.client_handler.purge()
That is, it marks itself as not running, and calls the purge method of handler. This can be written as
class ClientHandlerThread(Thread):
...
def purge(self):
self.clients = [c for c in self.clients if c.running]

Related

Multithreading sockets with a central relay-like server

I have previously managed to implement a client-server socket script which relays messages between a single client and the server and I'm now trying to implement a multiple-client system.
More specifically, I would like to use the server as some sort of medium between two clients which retrieves information from one client and relays it to the other. I had tried to attach and send the port number of the receiving client and then extract it from the message on the server side. After that, I would try and send it to whatever socket with that port number but I ran into some trouble (as port numbers are determined at the point of sending I believe?) so now I am simply just trying to relay the sent message back to all clients. However, the problem is that the message is only being sent to the server and not being relayed to the desired client.
I had previously tried to implement a peer-to-peer system but I ran into trouble so I decided to take a step back and do this instead.
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 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)
And the Client(.py):
import socket, threading
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
self.Socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def Connect(self):
print('Trying to connect')
try:
self.Socket.connect((HOST, SERVERPORT))
self.Connected = True
print(self.Username, 'connected to server')
Msg = MsgUI(self.Username)
Msg.Display()
except Exception:
print('Could not connect to server')
raise Exception
def SendMsg(self):
if self.Connected:
Msg = '{} sent you a message {}'.format(self.Username, OtherPORT)
self.Socket.sendall(bytes(Msg, encoding='utf8'))
self.GetResponse()
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()
I want a message to be sent whenever the user presses the send button in the tkinter window, but at the same time, it is continually 'listening' to see if it received any messages.
I also previously tried to run the GetResponse method in the Client in another thread and instead of if self.Connected I used while self.Connected and it still didn't work.
UPDATE
After some helpful comments, I have edited the two files as such:
The server now holds the two sockets for each client which is run first. The server file is imported into the client file as a module. Each client file is then run and each client runs a function in the server file, requesting to use the socket. If the request is allowed (i.e. no error was thrown), the socket is connected, added to a set of clients stored in the server file and then returned to the client file. The client then uses this socket to send and receive messages.
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)
And 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()
Now the problem is more of a python and scope problem. When trying to relay the message back to the client, I was getting a KeyError as the Clients dictionary was still empty. When making the function call to the server in the client file, it's clear that the update to the dictionary happens in the client file rather than the server file - which is in a different instance. I need a method of changing the contents of the Clients dictionary that is called to action by the client file but takes effect in the server file.
Are you committed to multithreading? Threads don't run concurrently in python ( due to the GIL), and while they are one way to handle concurrent operations, they aren't the only way and usually they're not the best way, unless they're the only way. Consider this code, which doesn't handle failure cases well, but seems to work as a starting point.
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
The concept is pretty simple. The server accepts connections; each connection (socket) is associated with a queue of pending messages. Each socket that's ready for reading is read from, and its message is added to each client's queue. The recipient client is added into the write_ready list of clients with data pending, if it's not already in there. Then each socket that's ready for writing has its next queued message written to it. If there are no more messages, the recipient is removed from the write_ready list.
This is very easy to orchestrate if you don't use multithreading because all coordination is inherent in the order of the application. With threads it would be more difficult and a lot more code, but probably not more performance due to the gil.
The secret to handling multiple I/O streams concurrently without multithreading is select. In principle it's pretty easy; we pass select() a list of possible sockets for reading, another list of possible sockets for writing, and a final list that for this simplified demo I completely ignore . The results of the select call will include one or more sockets that are actually ready for reading or writing, which allows me to block until one or more sockets are ready for activity. I then process all the sockets ready for activity every pass ( but they've already been filtered down to just those which wouldn't block).
There's a ton still to be done here. I don't cleanup after myself, don't track closed connections, don't handle any exceptions, and so on. but without having to worry about threading and concurrency guarantees, it's pretty easy to start addressing these deficiencies.
Here it is "in action". Here for the client side I use netcat, which is perfect for layer 3 testing without layer 4+ protocols ( in other words, raw tcp so to speak). It simply opens a socket to the given destination and port and sends its stdin through the socket and sends its socket data to stdout, which makes it perfect for demoing this server app!
I also wanted to point out, coupling code between server and client is inadvisable because you won't be able to roll out changes to either without breaking the other. It's ideal to have a "contract" so to speak between server and client and maintain it. Even if you implement the behavior of server and client in the same code base, you should use the tcp communications contract to drive your implementation, not code sharing. Just my 2 cents, but once you start sharing code you often start coupling server/client versions in ways you didn't anticipate.
the server:
$ 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)
First client ( 52059):
$ 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
Second client:
$ 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
If you need more convincing on why select is way more compelling than concurrent execution, consider this: Apache is based on a threading model, in other words, the connections each get a worker thread . nginx is based on a select model, so you can see how much faster that can potentially be. Not to say that nginx is inherently better, as Apache benefits from the threading model because of its heavy use of modules to extend capabilities ( mod_php for example), whereas nginx doesn't have this limitation and can handle all requests from any thread. But the raw performance of nginx is typically considered far higher and far more efficient, and a big reason for this is that it avoids almost all the cpu context switches inherent in apache. It's a valid approach!
A word on scaling. Obviously, this wouldn't scale forever. Neither would a threading model; eventually you run out of threads. A more distributed and high throughput system would likely use a Pub/Sub mechanism of some kind, offloading the client connection tracking and message queueing from the server to a pub/sub data tier and allowing connections to be restored and queued data to be sent, as well as adding multiple servers behind a load balancer. Just throwing it out there. You might be pleasantly surprised how well select can scale ( cpu is so much faster than network anyway that it's likely not the bottleneck).

using multiprocessing to handle socket

I have a server with main process acepting socket connections and put them in Queue stack and another process monitoring this stack and applying it to pool processes handling connections. All works fine except for one thing:
last connection allways at stuck until another connection appears, it's look like last connection can't be closed, but why?
from multiprocessing import Queue, Process, Pool, Manager
import datetime
import socket
def get_date():
return datetime.datetime.now().strftime('%H:%M:%S')
class Server:
def __init__(self, host, port):
self.server_address = host, port
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
def run(self):
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind(self.server_address)
self.server_socket.listen(1)
print('listen at: %s:%s' % self.server_address)
q = Manager().Queue()
Process(target=self.handle_request, args=(q,)).start()
while True:
client_socket, adress = self.server_socket.accept()
print('\n[%s] request from: %s:%s' % (get_date(), *adress))
q.put(client_socket)
client_socket.close()
del client_socket # client_socket.close() not working
def help(self, client_socket):
data = client_socket.recv(512)
client_socket.send(data)
client_socket.close()
print(data[:50])
def handle_request(self, q):
with Pool(processes=2) as pool:
while True:
pool.apply_async(self.help, (q.get(),))
Server('localhost', 8000).run()
close doesn't realy close connection unless no other process holding a reference, but shutdown will affect all processes. you could call client_socket.shutdown(socket.SHUT_WR) before client_socket.close().
Update:
the reason close doesn't fully close connection is there is a process started by Manager() is holding a reference. Use Queue instead would make close works as you expected.

UPnP event subscription using blocking Socket on a Thread

I am tyring to subsribe to an event on a UPnP device (the WeMo motion sensor). I first send an HTTP subscribe request to the device, and the device should start sending me event notification on the designated address. That part is working fine (except that I am getting too many notifications; even when the status is not changing, but it is a different problem for a different thread)
If I run the keepListening Function on a separate python process, everything is working fine . However, when I run the function as a thread, it doesn't work;
import socket
import requests
from threading import Thread
def keepListening(): #running this function on a separate process works
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.settimeout(600)
sock.bind(('192.168.10.231',1234))
sock.listen(5)
while 1:
notification = ''
try:
conn, addr = sock.accept()
conn.setblocking(1)
notification= conn.recv(1024)
conn.sendall(r'''HTTP/1.1 200 OK
Content-Type: text/plain
''')
except Exception as er:
print er
print notification
x = Thread(target=keepListening)
x.start()
message = {
'CALLBACK': '<http://192.168.10.231:1234>',
'NT': 'upnp:event',
'TIMEOUT': 'Second-600',
'HOST': '192.168.10.159:49153'}
k = requests.request('SUBSCRIBE','http://192.168.10.159:49153/upnp/event/basicevent1',headers=message)
print k
# keep doing other important works
Each event notification must be replied with a 200 OK reply, otherwise the device won't send further notification; a fact I learned the hard way. A doubt I have, which might be silly, is that, when running in a thread, as opposed to a separate process, the reply message doesn't get sent in timely manner, so the device doesn't send any more notifications.
It is worth mentioning that, even when I run the function in a Thread, I do get the initial notification after the subscription (Devices must mandatorily send an initial notification right after a subscription according to UPnP protocol), but I get no further notification (indicating that my 200 OK reply didn't get through properly; I do see it in wireshark though)
Any idea on what might be the difference in running the function in a thread (as opposed to a separate process) that makes it fail?
Thank you.
I would assume, what is happening is that you end up sending your subscribe request before thread becomes active and starts listening on the interface. So the device can not connect to the socket.
A few day ago I got a wemo motion sensor, switch and RaspberryPi, so I started tinkering.
The script subscribes to the „binaryState“-event of the wemo-device.
Every time the event occurs it prints out an „Alert“ (you can do other things there).
After 250 seconds it renews the subscription.
To adapt the script to your needs, you have to change the IPs:
localIp : your Computer
remoteIp: the ip of the wemo-sensor or switch
I’m new to python (started 3 days ago), so the script might need some revision, but it works.
import socket
import threading
import requests
host = ''
port = 1234
localIp = '<http://192.168.1.32:1234>' # local IP of your computer
remoteIp = '192.168.1.47:49153' # the ip of the wemo device
global uid # stores the uuid of the event
uid = ''
class client(threading.Thread):
def __init__(self, conn):
super(client, self).__init__()
self.conn = conn
self.data = ""
def run(self):
global uid
while True:
self.data = self.data + self.conn.recv(1024)
if self.data.endswith(u"\r\n"):
print self.data # data from the wemo device
uidPos = self.data.find("uuid")
if uidPos != -1: # data contains the uuid of the event
uid = self.data[uidPos+5:uidPos+41]
if "<BinaryState>1</BinaryState>" in self.data:
print "ALERT ------------------------------------------Alert"
# NOTIFICATION !
if "/e:propertyset" in self.data:
self.conn.sendall('HTTP/1.1 200 OK\r\nContent-Type:text/html\r\n\r\n')
return
self.data = ""
def send_msg(self,msg):
self.conn.send(msg)
def close(self):
self.conn.close()
class connectionThread(threading.Thread):
def __init__(self, host, port):
super(connectionThread, self).__init__()
try:
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.s.bind((host,port))
self.s.listen(5)
except socket.error:
print 'Failed to create socket'
sys.exit()
self.clients = []
def run(self):
while True:
print uid
conn, address = self.s.accept()
c = client(conn)
c.start()
print '[+] Client connected: {0}'.format(address[0])
def main():
get_conns = connectionThread(host, port)
get_conns.start()
print get_conns.clients
while True:
try:
response = raw_input()
except KeyboardInterrupt:
sys.exit()
def setCalback():
global uid
threading.Timer(250.0, setCalback).start()
if uid == "": # no uuid set so we subscribe to the event
eventSubscribe()
else: # uuid is set, so we renew the subsciption
eventRefresh()
def eventSubscribe(): # subscribe to the wemo-event
message = {
'CALLBACK': localIp,
'NT': 'upnp:event',
'TIMEOUT': 'Second-300',
'HOST': remoteIp}
k = requests.request('SUBSCRIBE', "http://"+remoteIp+'/upnp/event/basicevent1',headers=message)
print k
def eventRefresh() # refresh the subscription with the known uuid
myuid = "uuid:"+uid
message = {
'SID': myuid,
'TIMEOUT': 'Second-300',
'HOST': remoteIp }
k = requests.request('SUBSCRIBE',"http://"+remoteIp+'/upnp/event/basicevent1',headers=message)
print k
if __name__ == '__main__':
threading.Timer(2.0, setCalback).start() # wait 2 sec. then subscribe to the service
main()

How to create channel with socket in Python

I've started Python a few times ago and now, I'm currently creating a socket server. I already have the server functioning with multiple threads with multiple clients (Hurray !) But I'm looking for functionality I can't call (i don't even know if it exists) I would like to create a kind of channel where client can send different type of message.
An example I create a channel INFO and if the server received this type of socket it just does a print
I create another channel DEBUG where I can send custom command which the server will execute
etc
In a non-programming language it will do this:
def socketDebug(command):
run command
def socketInfo(input):
print input
if socket == socketDebug:
socketDebug(socket.rcv)
else:
if socket == socketInfo:
socketInfo(socket.rcv)
I hope I'm clear.
Here is a quite simple implementation of a Channel class. It creates a socket, to accept
connections from clients and to send messages. It is also a client itself,
receiving messages from other Channel instances (in separate processes for example).
The communication is done in two threads, which is pretty bad (I would use async io). when
a message is received, it calls the registered function in the receiving thread which
can cause some threading issues.
Each Channel instance creates its own sockets, but it would be much more scalable to
have channel "topics" multiplexed by a single instance.
Some existing libraries provide a "channel" functionality, like nanomsg.
The code here is for educational purposes, if it can help...
import socket
import threading
class ChannelThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.clients = []
self.chan_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.chan_sock.bind(('',0))
_, self.port = self.chan_sock.getsockname()
self.chan_sock.listen(5)
self.daemon=True
self.start()
def run(self):
while True:
new_client = self.chan_sock.accept()
if not new_client:
break
self.clients.append(new_client)
def sendall(self, msg):
for client in self.clients:
client[0].sendall(msg)
class Channel(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.daemon = True
self.channel_thread = ChannelThread()
def public_address(self):
return "tcp://%s:%d" % (socket.gethostname(), self.channel_thread.port)
def register(self, channel_address, update_callback):
host, s_port = channel_address.split("//")[-1].split(":")
port = int(s_port)
self.peer_chan_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.peer_chan_sock.connect((host, port))
self._callback = update_callback
self.start()
def deal_with_message(self, msg):
self._callback(msg)
def run(self):
data = ""
while True:
new_data = self.peer_chan_sock.recv(1024)
if not new_data:
# connection reset by peer
break
data += new_data
msgs = data.split("\n\n")
if msgs[-1]:
data = msgs.pop()
for msg in msgs:
self.deal_with_message(msg)
def send_value(self, channel_value):
self.channel_thread.sendall("%s\n\n" % channel_value)
Usage:
In process A:
c = Channel()
c.public_address()
In process B:
def msg_received(msg):
print "received:", msg
c = Channel()
c.register("public_address_string_returned_in_process_A", msg_received)
In process A:
c.send_value("HELLO")
In process B:
received: HELLO

Python Multithreading, run two functions at the same time

So I'm learning about socket programming and have wrote a nifty little chat server. The problem I am having is that my client cannot read and write at the same time. I'm not too sure how to set this up.
This is what I have so far, I want read() and write() to be running concurrently (It isn't so much about reading and writing at the same time - it's about being able to receive messages while input() hangs waiting for user input.):
import socket
import threading
class Client(threading.Thread):
def __init__(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(('127.0.0.1', 1234))
print('Client connected to server')
self.readThread = threading.Thread.__init__(self)
self.writeThread = threading.Thread.__init__(self)
def read(self):
data = self.socket.recv(1024)
if data:
print('Received:', data)
def write(self):
message = input()
self.socket.send(bytes(message, 'utf-8'))
client = Client()
while True:
#do both
You're really close. Try something like this:
import socket
import threading
class Client(threading.Thread):
def __init__(self):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(('127.0.0.1', 1234))
print('Client connected to server')
t = threading.Thread(target = self.read)
t.daemon = True # helpful if you want it to die automatically
t.start()
t2 = threading.thread(target = self.write)
t2.daemon = True
t2.start()
def read(self):
while True:
data = self.socket.recv(1024)
if data:
print('Received:', data)
def write(self):
while True:
message = input()
self.socket.send(bytes(message, 'utf-8'))
client = Client()
It's worth pointing out that if you're reading and writing from a single terminal this way your prompt could get a little out of hand. I imagine though that you're starting with print statements, but will eventually collect data into other containers in your app.

Categories

Resources