I want to create a server and client that sends and receives UDP packets from the network using Twisted. I've already written this with sockets in Python, but want to take advantage of Twisted's callback and threading features. However, I need help though with the design of Twisted.
I have multiple types of packets I want to receive, but let's pretend there is just one:
class Packet(object):
def __init__(self, data=None):
self.packet_type = 1
self.payload = ''
self.structure = '!H6s'
if data == None:
return
self.packet_type, self.payload = struct.unpack(self.structure, data)
def pack(self):
return struct.pack(self.structure, self.packet_type, self.payload)
def __str__(self):
return "Type: {0}\nPayload {1}\n\n".format(self.packet_type, self.payload)
I made a protocol class (almost direct copy of the examples), which seems to work when I send data from another program:
class MyProtocol(DatagramProtocol):
def datagramReceived(self, data, (host, port)):
p = Packet(data)
print p
reactor.listenUDP(3000, MyProtocol())
reactor.run()
What I don't know is how do I create a client which can send arbitrary packets on the network, which get picked up by the reactor:
# Something like this:
s = Sender()
p = Packet()
p.packet_type = 3
s.send(p.pack())
p.packet_type = 99
s.send(p.pack())
I also need to make sure to set the reuse address flag on the client and servers so I can run multiple instances of each at the same time on the same device (e.g. one script is sending heartbeats, another responds to heartbeats, etc).
Can someone show me how this could be done with Twisted?
Update:
This is how I do it with sockets in Python. I can run multiple listeners and senders at the same time and they all hear each other. How do I get this result with Twisted? (The listening portion need not be a separate process.)
class Listener(Process):
def __init__(self, ip='127.0.0.1', port=3000):
Process.__init__(self)
self.ip = ip
self.port = port
def run(self):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind((self.ip, self.port))
data, from_ip = sock.recvfrom(4096)
p = Packet(data)
print p
class Sender(object):
def __init__(self, ip='127.255.255.255', port=3000):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.ip = (ip, port)
def send(self, data):
self.sock.sendto(data, self.ip)
if __name__ == "__main__":
l = Listener()
l.start()
s = Sender()
p = Packet()
p.packet_type = 4
p.payload = 'jake'
s.send(p.pack())
Working solution:
class MySender(DatagramProtocol):
def __init__(self, packet, host='127.255.255.255', port=3000):
self.packet = packet.pack()
self.host = host
self.port = port
def startProtocol(self):
self.transport.write(self.packet, (self.host, self.port))
if __name__ == "__main__":
packet = Packet()
packet.packet_type = 1
packet.payload = 'jake'
s = MySender(packet)
reactor.listenMulticast(3000, MyProtocol(), listenMultiple=True)
reactor.listenMulticast(3000, s, listenMultiple=True)
reactor.callLater(4, reactor.stop)
reactor.run()
Just like the server example above, there is a client example to.
This should help you get started:
https://twistedmatrix.com/documents/current/core/howto/udp.html
https://github.com/twisted/twisted/blob/trunk/docs/core/examples/echoclient_udp.py
Ok, here is a simple heart beat sender and receiver using datagram protocol.
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
import sys, time
class HeartbeatSender(DatagramProtocol):
def __init__(self, name, host, port):
self.name = name
self.loopObj = None
self.host = host
self.port = port
def startProtocol(self):
# Called when transport is connected
# I am ready to send heart beats
self.loopObj = LoopingCall(self.sendHeartBeat)
self.loopObj.start(2, now=False)
def stopProtocol(self):
"Called after all transport is teared down"
pass
def datagramReceived(self, data, (host, port)):
print "received %r from %s:%d" % (data, host, port)
def sendHeartBeat(self):
self.transport.write(self.name, (self.host, self.port))
class HeartbeatReciever(DatagramProtocol):
def __init__(self):
pass
def startProtocol(self):
"Called when transport is connected"
pass
def stopProtocol(self):
"Called after all transport is teared down"
def datagramReceived(self, data, (host, port)):
now = time.localtime(time.time())
timeStr = str(time.strftime("%y/%m/%d %H:%M:%S",now))
print "received %r from %s:%d at %s" % (data, host, port, timeStr)
heartBeatSenderObj = HeartbeatSender("sender", "127.0.0.1", 8005)
reactor.listenMulticast(8005, HeartbeatReciever(), listenMultiple=True)
reactor.listenMulticast(8005, heartBeatSenderObj, listenMultiple=True)
reactor.run()
The broadcast example simply modifies the above approach:
from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor
from twisted.internet.task import LoopingCall
import sys, time
class HeartbeatSender(DatagramProtocol):
def __init__(self, name, host, port):
self.name = name
self.loopObj = None
self.host = host
self.port = port
def startProtocol(self):
# Called when transport is connected
# I am ready to send heart beats
self.transport.joinGroup('224.0.0.1')
self.loopObj = LoopingCall(self.sendHeartBeat)
self.loopObj.start(2, now=False)
def stopProtocol(self):
"Called after all transport is teared down"
pass
def datagramReceived(self, data, (host, port)):
print "received %r from %s:%d" % (data, host, port)
def sendHeartBeat(self):
self.transport.write(self.name, (self.host, self.port))
class HeartbeatReciever(DatagramProtocol):
def __init__(self, name):
self.name = name
def startProtocol(self):
"Called when transport is connected"
self.transport.joinGroup('224.0.0.1')
pass
def stopProtocol(self):
"Called after all transport is teared down"
def datagramReceived(self, data, (host, port)):
now = time.localtime(time.time())
timeStr = str(time.strftime("%y/%m/%d %H:%M:%S",now))
print "%s received %r from %s:%d at %s" % (self.name, data, host, port, timeStr)
heartBeatSenderObj = HeartbeatSender("sender", "224.0.0.1", 8005)
reactor.listenMulticast(8005, HeartbeatReciever("listner1"), listenMultiple=True)
reactor.listenMulticast(8005, HeartbeatReciever("listner2"), listenMultiple=True)
reactor.listenMulticast(8005, heartBeatSenderObj, listenMultiple=True)
reactor.run()
Check out the echoclient_udp.py example.
Since UDP is pretty much symmetrical between client and server, you just want to run reactor.listenUDP there too, connect to the server (which really just sets the default destination for sent packets), then transport.write to send your packets.
Related
I am trying to build a Python program that will pass a message between a Client and Server. The idea is to pass one message from the Server and have the Client modify it and pass it back to the Server.
Right now I am suck on trying to get the Client's message back to the Server; the message 'Congrats! You have connected' is converted to uppercase,
Server
import socket
class Server:
def __init__(self, host, port):
self.serverSocket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.host = host
self.port = port
def bind(self):
self.serverSocket.bind((self.host, self.port))
self.serverSocket.listen(5)
def run(self):
while True:
print ('Waiting for a connection')
(clientSocket, addr) = self.serverSocket.accept()
print ('Got a connection from {}'.format(str(addr)))
message = 'Congrats! You have connected'
self.sendMessage(message, clientSocket)
self.recieveMessage()
clientSocket.close()
def sendMessage(self, message, clientSocket):
clientSocket.send(message.encode('ascii'))
def recieveMessage(self):
(clientSocket, addr) = self.serverSocket.accept()
message = self.serverSocket.recv(1024).decode('ascii')
print(message)
def closeSocket(self):
self.serverSocket.close()
if __name__ == '__main__':
myServer = Server('127.0.0.1', 5555)
myServer.bind()
myServer.run()
myServer.recieveMessage()
myServer.closeSocket()
Client
import socket
class Client:
def __init__(self, host, port):
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.host = host
self.port = port
def connect(self):
self.serverSocket.connect((self.host, self.port))
def getMessage(self):
return self.serverSocket.recv(1024).decode('ascii')
def modifyMessage(self):
return message.upper()
def sendMessage(self, upperCaseMessage, server):
(server, addr) = self.serverSocket.accept()
serverSocket.send(upperCaseMessage.encode('ascii'))
def closeConnection(self):
self.serverSocket.close()
if __name__ == '__main__':
myClient = Client('127.0.0.1', 5555)
myClient.connect()
message = myClient.getMessage()
upperCaseMessage = myClient.modifyMessage()
myClient.sendMessage(upperCaseMessage, serverSocket)
myClient.closeConnection()
I'm trying to build a socket and I want to print an object of clients, but for some reason whenever I connect it just returns empty {}
I'm new to Python and would like some insight
import socket
from threading import Thread
from multiprocessing import Process
import time as t
previousTime = t.time()
clients = {}
hostAddr = "127.0.0.1"
hostPort = 80
class sClient(Thread):
def __init__(self, socket, address):
Thread.__init__(self)
self.sock = socket
self.addr = address
self.start()
def run(self):
print("\nClient Connected from {}!".format(self.addr[0]))
self.sock.sendall("Welcome master".encode())
class sHost():
def __init__(self, host, port, clients):
self.sHost = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sHost.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sHost.bind((host, port))
self.sHost.listen()
self.start_listening()
def start_listening(self):
while 1:
clientSocket, clientAddr = self.sHost.accept()
clients[clientSocket.fileno()] = clientSocket
sClient(clientSocket, clientAddr)
def SendMsgToAllClients(msg):
print(clients) # this is empty
for client in clients.values():
try:
client.sendall(msg.encode())
except Exception as e:
print("Client probably disconnected, removing...")
finally:
del clients[client.fileno()]
if __name__ == '__main__':
Process(target=sHost, args=(hostAddr, hostPort, clients)).start()
print("Server is running")
while 1:
if previousTime + 3 <= t.time():
SendMsgToAllClients("Test")
previousTime = t.time()
I have following code for a Publisher, which instantiates a few class instances and publishes some messages.
However, I don't receive anything at Subscriber side.
Publisher
import zmq
import time
from multiprocessing import Process
class SendData:
def __init__(self, msg, port):
self.msg = msg
self.port = port
ctx = zmq.Context()
self.sock = ctx.socket(zmq.PUB)
self.sock.bind('tcp://127.0.0.1:'+str(self.port))
time.sleep(1)
def sender(self):
self.sock.send_json(self.msg)
def main():
for device, port in zip(['2.2.2.2', '5.5.5.5'],[5001, 5002]):
msg = {device:'Some random message'}
instance = SendData(device, port)
Process(target=instance.sender).start()
if __name__ == "__main__":
main()
Subscriber
import zmq
ctx = zmq.Context()
recv_sock1 = ctx.socket(zmq.SUB)
recv_sock1.connect('tcp://127.0.0.1:5001')
recv_sock1.setsockopt(zmq.SUBSCRIBE, '')
recv_sock2 = ctx.socket(zmq.SUB)
recv_sock2.connect('tcp://127.0.0.1:5002')
recv_sock2.setsockopt(zmq.SUBSCRIBE, '')
while True:
if recv_sock1.poll(10):
msg = recv_sock1.recv_json()
print msg
if recv_sock2.poll(10):
msg = recv_sock2.recv_json()
print msg
I had subscribers started before publishers could publish anything. Also, I can see TCP connections are in Established so connections are made.
pyzmq version 16.0.0
python version 2.7
Q1: Are 0mq publishers supported from class instances?
Q2: What am I missing?
As was said before, trying to share the ZeroMQ context between processes is the problem here and the solution by user3666197 will work.
However, I would suggest subclassing multiprocessing.Process in this case. That way, it is much clearer what part of the code is executed in which process. It also makes your code more readable and reusable.
The following code creates one sender process per device. The sender processes can be reused during the runtime of your program to send more data:
import multiprocessing
import queue
import zmq
import time
class Sender(multiprocessing.Process):
def __init__(self, port):
super(Sender, self).__init__()
self._port = port
self._messages = multiprocessing.Queue()
self._do_stop = multiprocesing.Event()
def run(self):
"""
This code is executed in a new process.
"""
ctx = zmq.Context()
sock = ctx.socket(zmq.PUB)
sock.bind("tcp://127.0.0.1:" + str(self._port))
while not self._do_stop.is_set():
try:
msg = self._message.get_nowait()
sock.send_json(msg)
except queue.Empty:
time.sleep(0.01)
def stop(self):
self._do_stop.set()
def send_message(self, msg):
self._messages.put(msg)
def main():
data = zip(['2.2.2.2', '5.5.5.5'], [5001, 5002])
# create senders
senders = {device: Sender(port) for device, port in data}
# start senders
for device in senders:
senders[device].start()
# send messages
for device, port in zip(['2.2.2.2', '5.5.5.5'],[5001, 5002]):
msg = {device: 'Some random message'}
senders[device].send_message(msg)
# do more stuff here....
# ... e.g. send more messages
# ...
# once you are finished, stop the subprocesses
for device in senders:
senders[device].stop()
I hope this helps solving your problem.
A1: Yes, they are.
A2: Conflicts of scope-of-use v/s Zero-sharing, one of ZeroMQ maxims
Once your original Publisher code is being executed in main(), the class instantiation process creates ( i.e. inside the main()-process scope-of-use ), via the .__init__() constructor-method, it's own Context() -instance, which thus belongs ( incl. all of it's derived child-objects ( sockets et al ) ) to this main()-process.
Next, the call to the Process(...) initiates another few processes, that receive the class-instances ( the pity is that these have already created ZeroMQ non-shareable toys ) from the main()-scope-of-use.
Solution?
A possible dirty quick hack could be to defer the ZeroMQ Context() instantiation -- yes, just move it from .__init__() to some .aDeferredSETUP() that will be executed specifically under different scope-of-use inside each of the spinned-of Process()-process, different from main()-process and you ought be done, as Zero-sharing is safely obeyed.
class SendData:
def __init__(self, msg, port):
self.msg = msg
self.port = port
self.NotSETUP = True
self.ctx = None
self.sock = None
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ L8R
# ctx = zmq.Context()
# self.sock = ctx.socket( zmq.PUB )
# self.sock.bind( 'tcp://127.0.0.1:' + str( self.port ) )
# time.sleep( 1 )
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ L8R
def sender( self ):
if self.NotSETUP:
self.aDeferredSETUP()
self.sock.send_json( self.msg )
def aDeferredSETUP( self ): # create I/O-threads in Process(), not main()
self.ctx = zmq.Context()
self.sock = self.ctx.socket( zmq.PUB )
self.sock.bind( 'tcp://127.0.0.1:' + str( self.port ) )
time.sleep( 1 )
Here is a simple client which connects and sends a text message:
class Client(asyncore.dispatcher):
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.create_socket()
self.connect( (host, port) )
self.buffer = bytes("hello world", 'ascii')
def handle_connect(self):
pass
def handle_close(self):
self.close()
def handle_read(self):
print(self.recv(8192))
def writable(self):
return (len(self.buffer) > 0)
def writable(self):
return True
def handle_write(self):
sent = self.send(self.buffer)
print('Sent:', sent)
self.buffer = self.buffer[sent:]
client = Client('localhost', 8080)
asyncore.loop()
And here is the server which has to receive the message and echo it back:
class Server(asyncore.dispatcher):
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.create_socket()
self.set_reuse_addr()
self.bind((host, port))
self.listen(5)
def handle_read(self):
self.buffer = self.recv(4096)
while True:
partial = self.recv(4096)
print('Partial', partial)
if not partial:
break
self.buffer += partial
def readable(self):
return True
def handle_write(self):
pass
def handle_accepted(self, sock, addr):
print('Incoming connection from %s' % repr(addr))
self.handle_read()
print(self.buffer)
if __name__ == "__main__":
server = Server("localhost", 8080)
asyncore.loop()
The problem is that server isn't reading anything. When I print self.buffer the output is:
b''
What am I doing wrong?
First of all, you need two handlers: One for the server socket (where you expect only accept), and one for the actual communication sockets. In addition, you can only call read once in handle_read; if you call it twice, the second call may block, and that's not allowed in asyncore programming. Don't worry though; if your read did not get everything, you'll immediately be notified again once your read handler returns.
import asyncore
class Handler(asyncore.dispatcher):
def __init__(self, sock):
self.buffer = b''
super().__init__(sock)
def handle_read(self):
self.buffer += self.recv(4096)
print('current buffer: %r' % self.buffer)
class Server(asyncore.dispatcher):
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.create_socket()
self.set_reuse_addr()
self.bind((host, port))
self.listen(5)
def handle_accepted(self, sock, addr):
print('Incoming connection from %s' % repr(addr))
Handler(sock)
if __name__ == "__main__":
server = Server("localhost", 1234)
asyncore.loop()
As we can see, send method is not overloaded.
from socket import socket
class PolySocket(socket):
def __init__(self,*p):
print "PolySocket init"
socket.__init__(self,*p)
def sendall(self,*p):
print "PolySocket sendall"
return socket.sendall(self,*p)
def send(self,*p):
print "PolySocket send"
return socket.send(self,*p)
def connect(self,*p):
print "connecting..."
socket.connect(self,*p)
print "connected"
HOST="stackoverflow.com"
PORT=80
readbuffer=""
s=PolySocket()
s.connect((HOST, PORT))
s.send("a")
s.sendall("a")
Output:
PolySocket init
connecting...
connected
PolySocket sendall
I am sure you don't actually need it and there are other ways to solve your task (not subclassing but the real task).
If you really need to mock object, go with proxy object:
from socket import socket
class PolySocket(object):
def __init__(self, *p):
print "PolySocket init"
self._sock = socket(*p)
def __getattr__(self, name):
return getattr(self._sock, name)
def sendall(self, *p):
print "PolySocket sendall"
return self._sock.sendall(*p)
def send(self, *p):
print "PolySocket send"
return self._sock.send(*p)
def connect(self, *p):
print "connecting..."
self._sock.connect(*p)
print "connected"
HOST = "stackoverflow.com"
PORT = 80
readbuffer = ""
s = PolySocket()
s.connect((HOST, PORT))
s.send("a")
s.sendall("a")
Here's the output:
% python foo.py
PolySocket init
connecting...
connected
PolySocket send
PolySocket sendall