UPnP event subscription using blocking Socket on a Thread - python

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()

Related

Simulate a Long Network Request for Python Testing

I need to test a device update function. The function opens a socket on a host and sends a block of text.
The update can take up to 120 seconds. It returns a code for success/failure. To allow continued functioning of the program the update is launched in a thread.
I cannot control the response of the device. The simulation needs to be able to hold an open connection for at least 120 seconds.
It does not need to be safe or scalable since it will only be used for an integration test. The simplest solution is preferred. Pure python is best, but a docker is also acceptable.
I wrote this up based on rdas's pointer.
import json
import logging
import socket
import socketserver
import threading
import time
log = logging.getLogger(__name__)
log.setLevel(logging.INFO)
class LongRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
# Echo the back to the client
data = json.loads(self.request.recv(1024).decode())
t = 0
while t < data['delay']:
time.sleep(1)
print(".", end='')
t += 1
if t % 80 == 0:
print("\n")
print("\n")
self.request.send(b"ok")
class Server():
def __init__(self, host='localhost', port=0):
self.host = host
self.port = port
self.ip = None
self.server = None
def run(self):
address = (self.host, self.port) # let the kernel assign port if port=0
self.server = socketserver.TCPServer(address, LongRequestHandler)
self.ip, self.port = self.server.server_address # what port was assigned?
t = threading.Thread(target=self.server.serve_forever)
t.setDaemon(True) # don't hang on exit
t.start()
return True
def send_request(self, data: dict ):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.ip, self.port))
message = json.dumps(data).encode()
s.send(message)
response = s.recv(1024)
s.close()
return response
def __exit__(self):
self.server.shutdown()
self.server.socket.close()
if __name__ == '__main__':
# For simple testing and config example...
server = Server()
server.run()
# Send the data
d = dict(delay=5) # set delay here to desired
out = server.send_request(d)
print('Received: {!r}'.format(out))

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).

How to do fan-out in ZeroMQ? Forwarding from a set of topics to multiple clients

I need to implement a server that receives a request specifying a set of topics via control channel (req-rep), and then as response sends a URL pointing to a publisher socket that will be opened for this specific client or a rejection message (because of insufficient privileges).
I managed to implement a version that can handle only one client at the time (with two endless loops), but I do not know which pattern to use to handle multiple clients concurrently.
It's important for me that the sockets for different clients stay separate.
Here's simplified code:
import zmq
context = zmq.Context()
upstream_port = 10000
upstream_host = 'localhost'
control_channel_port = 11000
upstream_addr = 'tcp://{}:{}'.format(upstream_host, upstream_port)
def should_grant(request):
'''
Permission checking - irrelevant to the question
'''
return True
def bind_downstream():
downstream = context.socket(zmq.PUB)
addr = 'tcp://*'
port = downstream.bind_to_random_port(addr)
return downstream, port
def bind_control_channel():
control_channel_sock = context.socket(zmq.REP)
control_channel_sock.bind('tcp://*:{}'.format(control_channel_port))
return control_channel_sock
def connect_upstream(topics):
raw_data = context.socket(zmq.SUB)
for t in topics:
raw_data.setsockopt_unicode(zmq.SUBSCRIBE, unicode(t))
raw_data.connect(upstream_addr)
return raw_data
if __name__ == '__main__':
print("Binding control channel socket on {}".format('tcp://*:{}'.format(control_channel_port)))
control_channel = bind_control_channel()
while True:
request = control_channel.recv_json()
print("Received request {}".format(request))
if should_grant(request):
(downstream_sock, downstream_port) = bind_downstream()
print("Downstream socket open on {}".format('tcp://*:{}'.format(downstream_port)))
print("Connecting to upstream on {}".format(upstream_addr))
upstream_sock = connect_upstream(request['topics'])
control_channel.send_json({'status': 'ok', 'port': downstream_port})
while True:
parts = upstream_sock.recv_multipart() # Simple forwarding
downstream_sock.send_multipart(parts)
else:
control_channel.send_json({'status': 'rejected'})
The correct way to do this would be to use threads.
Your main program or thread would handle the control channel loop. As soon as a connection appears, you would create the upstream and downstream sockets but handle the actual transfer in a thread. I am not sure if the code below works as I do not have a client that would work with it, but give it a go and see what happens. You will get the idea nevertheless.
from threading import Thread
....
....
class ClientManager(Thread):
def __init__(self, ups, downs):
super(ClientManager, self).__init__(self)
self.upstream_socket = ups
self.downstream_socket = downs
def run(self):
while True:
_parts = self.upstream_socket.recv_multipart()
self.downstream_socket.send_multipart(_parts)
if __name__ == '__main__':
print("Binding control channel socket on {}".format('tcp://*:{}'.format(control_channel_port)))
control_channel = bind_control_channel()
while True:
request = control_channel.recv_json()
print("Received request {}".format(request))
if should_grant(request):
(downstream_sock, downstream_port) = bind_downstream()
print("Downstream socket open on {}".format('tcp://*:{}'.format(downstream_port)))
print("Connecting to upstream on {}".format(upstream_addr))
upstream_sock = connect_upstream(request['topics'])
control_channel.send_json({'status': 'ok', 'port': downstream_port})
_nct = ClientManager(upstream_sock, downstream_sock)
_nct.daemon = True
_nct.start()
else:
control_channel.send_json({'status': 'rejected'})

Listening for socket input in multiple threads

I'm using python sockets.
Here's the problem. I've 2 threads:
One thread listens for socket input from remote and reply to it
One thread polls file and if something is present in file then send
to socket and expect a response.
Now the problem is in case of second thread when I send something, the response doesn't come to this thread. Rather it comes to thread mentioned in (1) point.
This is thread (1)
def client_handler(client):
global client_name_to_sock_mapping
client.send(first_response + server_name[:-1] + ", Press ^C to exit")
user_name = None
while True:
request = client.recv(RECV_BUFFER_LIMIT)
if not user_name:
user_name = process_input(client, request.decode('utf-8'))
user_name = user_name.rstrip()
if user_name not in client_name_to_sock_mapping.keys():
client_name_to_sock_mapping[user_name] = client
else:
msg = "Username not available".encode('ascii')
client.send(msg)
else:
process_input(client, request.decode('utf-8'), user_name)
This is run from thread (2)
def send_compute_to_client():
time.sleep(20)
print("Sleep over")
for _, client_sock in client_name_to_sock_mapping.iteritems():
print("client = {}".format(client_sock))
client_sock.sendall("COMPUTE 1,2,3")
print("send completed = {}".format(client_sock))
data = client_sock.recv(1024)
print("Computed results from client {}".format(data))
Can someone please explain this behaviour?
I have faced similar problems in the past. That happens when in one thread you start a blocking action listening for a connection while in the other thread you send through the same socket.
If I understand it well, you always want to receive the response from the previously send data. So in order to solve it I would use locks to force that behaviour, so just create a class:
from threading import Lock
class ConnectionSession:
def __init__(self, address, conn):
self.ip = address[0] # Optional info
self.port = address[1] # Optional info
self.conn = conn
self.lock = Lock()
Here it goes how to create a ConnectionSession object properly when a listening socket is created:
address = ('127.0.0.1', 46140)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(address)
conn, addr = s.accept()
session = ConnectionSession(addr, conn)
And here it goes when a 'sending' connection is created:
address = ('127.0.0.1', 46140)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(address)
session = ConnectionSession(address, s)
Keep in mind that the created session instance is the one that needs to be shared among threads.
Afterwards, to send information through the shared socket you could do in each thread something like:
# Previous code
try:
session.lock.acquire()
session.conn.sendall("Hi there buddy!")
# Do something if needed
message = session.conn.recv(1024)
except Exception as e:
print "Exception e=%s should be handled properly" % e
finally:
if session.lock.locked():
session.lock.release()
# Other code
Note that the finally block is important as it will free the locked connection whether if the action succeeded or not.
You can also wrap the previous code in a class, e.g: SocketManager with the following code in order to avoid having to explicitly acquire and release locks.
I hope it helps

Close socket when game ends

I wrote a script in blender game and I use sockets, I have a Server.blend and a client.blend.
this is my Server's Constructor:
class Server:
def __init__(self, host="127.0.0.1", port= 9238):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setblocking(False)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.bind((host, port))
and this is my client's:
class Client:
def __init__(self, server_ip="127.0.0.1", server_port= 9238):
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.socket.setblocking(False)
self.serv_addr = (server_ip, server_port)
The problem is that I don't know when the client is going to exit the game, so I can't close his socket- what keeps the used port open so i can't use the current port again.
I have a dictionary which contains all the addresses of all the clients, so I tried to send a message to all the addresses and in case that the client disconnected, i won't be able to send the message and use and exception to remove the address from the list (and it's avatar etc..):
def Check_For_Disconnect(self):
for addr in self.addr_user:
try:
self.socket.sendto(b"You are connected!" , addr)
except socket.error:
scene = logic.getCurrentScene()
for obj in scene.objects:
if str(obj) == "Text" and obj == self.addr_user[addr].name:
obj.delete()
del self.addr_user[addr]
I suppose that I don't reach the exception because the client's socket is still open so the message arrives properly.
Does anyone have any idea how I can around this problem?
The client should send some info about exiting the game, thus the server knows exactly when to close the socket. So the process is triggered by the client side.
I found a solution: I don't know when the client is going to exit the game, so I can't close his socket, what I do know is that just when the client runs his game- he can send messages to the server. so as long as the server on air he asks from the client for "connected" message. Every time that the server doesn't get a message from the client, he counts it. Now it's up to you how many counts to do until you sure that the client disconnected.
This is my receive method:
def receive(self):
while True:
for k in self.addr_connected:
if self.addr_connected[k] > 50:
self.Remove_Client(k)
break
try:
data, addr = self.socket.recvfrom(1024)
if not addr in self.addr_user:
user= User(data.decode())
scene = logic.getCurrentScene()
spawner = scene.objects['Spawner']
avatar = scene.addObject("Avatar", spawner)
avatar.children[0]['Text'] = user.name
avatar['user']= user
self.addr_user[addr] = user
self.addr_connected[addr] = 0
else:
user= self.addr_user[addr]
try:
user.keyboard.updateState(pickle.loads(data))
except:
data = data.decode()
if data == "I am connected":
self.addr_connected[addr] = 0
for k in self.addr_connected:
if k != addr:
self.addr_connected[k] += 1
except socket.error:
for k in self.addr_connected:
self.addr_connected[k] += 1
break

Categories

Resources