mirror of
https://github.com/BOSWatch/BW3-Core.git
synced 2026-01-11 03:00:08 +01:00
Merge pull request #11 from BOSWatch/broadcast
add broadcasting function
This commit is contained in:
commit
b287e2fda0
173
boswatch/network/broadcast.py
Normal file
173
boswatch/network/broadcast.py
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: broadcast.py
|
||||
@date: 21.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: UDP broadcast server and client class
|
||||
"""
|
||||
import logging
|
||||
import socket
|
||||
import threading
|
||||
|
||||
logging.debug("- %s loaded", __name__)
|
||||
|
||||
|
||||
class BroadcastClient:
|
||||
"""!BroadcastClient class"""
|
||||
|
||||
def __init__(self, port=5000):
|
||||
"""!Create an BroadcastClient instance
|
||||
|
||||
@param port: port to send broadcast packets (5000)"""
|
||||
self._broadcastPort = port
|
||||
|
||||
self._serverIP = ""
|
||||
self._serverPort = 0
|
||||
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self._socket.settimeout(3)
|
||||
|
||||
def getConnInfo(self, retry=0):
|
||||
"""!Get the connection info from server over udp broadcast
|
||||
|
||||
This function will send broadcast package(s)
|
||||
to get connection info from the server.
|
||||
|
||||
- send the magic packet <BW-Request> on broadcast address.
|
||||
- wait for a <BW-Result> magic packet.
|
||||
- extract the connection data from the magic packet and return
|
||||
|
||||
@param retry: Count of retry - 0 is infinite (0)
|
||||
|
||||
@return True or False"""
|
||||
sendPackages = 0
|
||||
while sendPackages < retry or retry == 0:
|
||||
try:
|
||||
logging.debug("send magic <BW3-Request> as broadcast - Try: %d", sendPackages)
|
||||
self._socket.sendto("<BW3-Request>".encode(), ('255.255.255.255', self._broadcastPort))
|
||||
sendPackages += 1
|
||||
payload, address = self._socket.recvfrom(1024)
|
||||
payload = str(payload, "UTF-8")
|
||||
|
||||
if payload.startswith("<BW3-Result>"):
|
||||
logging.debug("received magic <BW3-Result> from: %s", address[0])
|
||||
self._serverIP = address[0]
|
||||
self._serverPort = int(payload.split(";")[1])
|
||||
logging.info("got connection info from server: %s:%d", self._serverIP, self._serverPort)
|
||||
return True
|
||||
except socket.timeout: # nothing received - retry
|
||||
logging.debug("no magic packet received")
|
||||
except: # pragma: no cover
|
||||
logging.exception("error on getting connection info")
|
||||
logging.warning("cannot fetch connection info after %d tries", sendPackages)
|
||||
return False
|
||||
|
||||
@property
|
||||
def serverIP(self):
|
||||
"""!Property to get the server IP after successful broadcast"""
|
||||
return self._serverIP
|
||||
|
||||
@property
|
||||
def serverPort(self):
|
||||
"""!Property to get the server Port after successful broadcast"""
|
||||
return self._serverPort
|
||||
|
||||
|
||||
class BroadcastServer:
|
||||
"""!BroadcastServer class"""
|
||||
|
||||
def __init__(self, servePort=8080, listenPort=5000):
|
||||
"""!Create an BroadcastServer instance
|
||||
|
||||
@param servePort: port to serve as connection info (8080)
|
||||
@param listenPort: port to listen for broadcast packets (5000)"""
|
||||
self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
|
||||
self._socket.settimeout(2)
|
||||
self._socket.bind(('', listenPort))
|
||||
self._serverThread = None
|
||||
self._serverShutdown = False
|
||||
self._servePort = servePort
|
||||
|
||||
def start(self):
|
||||
"""!Start the broadcast server in a new thread
|
||||
|
||||
@return True or False"""
|
||||
try:
|
||||
if not self._serverThread:
|
||||
logging.debug("start udp broadcast server")
|
||||
self._serverThread = threading.Thread(target=self._listen)
|
||||
self._serverThread.name = "BroadServ"
|
||||
self._serverThread.daemon = True
|
||||
self._serverShutdown = False
|
||||
self._serverThread.start()
|
||||
return True
|
||||
else:
|
||||
logging.warning("udp broadcast server always started")
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot start udp broadcast server thread")
|
||||
return False
|
||||
|
||||
def stop(self):
|
||||
"""!Stop the broadcast server
|
||||
|
||||
Due to the timeout of the socket,
|
||||
stopping the thread can be delayed by two seconds.
|
||||
But function returns immediately.
|
||||
|
||||
@return True or False"""
|
||||
try:
|
||||
if self._serverThread:
|
||||
logging.debug("stop udp broadcast server")
|
||||
self._serverShutdown = True
|
||||
return True
|
||||
else:
|
||||
logging.warning("udp broadcast server always stopped")
|
||||
return True
|
||||
except: # pragma: no cover
|
||||
logging.exception("cannot stop udp broadcast server thread")
|
||||
return False
|
||||
|
||||
def _listen(self):
|
||||
"""!Broadcast server worker thread
|
||||
|
||||
This function listen for magic packets on broadcast
|
||||
address and send the connection info to the clients.
|
||||
|
||||
- listen for the magic packet <BW-Request>
|
||||
- send connection info in an <BW-Result> macig packet"""
|
||||
logging.debug("start listening for magic")
|
||||
while not self._serverShutdown:
|
||||
try:
|
||||
payload, address = self._socket.recvfrom(1024)
|
||||
payload = str(payload, "UTF-8")
|
||||
if payload == "<BW3-Request>":
|
||||
logging.debug("received magic <BW3-Request> from: %s", address[0])
|
||||
logging.info("send connection info in magic <BW3-Result> to: %s", address[0])
|
||||
self._socket.sendto("<BW3-Result>;".encode() + str(self._servePort).encode(), address)
|
||||
except socket.timeout:
|
||||
continue # timeout is accepted (not block at recvfrom())
|
||||
except: # pragma: no cover
|
||||
logging.exception("error while listening for clients")
|
||||
self._serverThread = None
|
||||
logging.debug("udp broadcast server stopped")
|
||||
|
||||
@property
|
||||
def isRunning(self):
|
||||
"""!Property of broadcast server running state"""
|
||||
if self._serverThread:
|
||||
return True
|
||||
return False
|
||||
14
bw_server.py
14
bw_server.py
|
|
@ -51,13 +51,27 @@ try:
|
|||
from boswatch.descriptor.descriptor import Descriptor
|
||||
from boswatch.filter.doubeFilter import DoubleFilter
|
||||
from boswatch.utils import header
|
||||
from boswatch.network.broadcast import BroadcastClient
|
||||
from boswatch.network.broadcast import BroadcastServer
|
||||
except Exception as e: # pragma: no cover
|
||||
logging.exception("cannot import modules")
|
||||
print("cannot import modules")
|
||||
print(e)
|
||||
exit(1)
|
||||
|
||||
# Test for the broadcast connection info function
|
||||
server = BroadcastServer()
|
||||
client = BroadcastClient()
|
||||
server.start()
|
||||
print(server.isRunning)
|
||||
client.getConnInfo()
|
||||
print(client.serverIP, client.serverPort)
|
||||
server.stop()
|
||||
print(server.isRunning)
|
||||
time.sleep(2)
|
||||
print(server.isRunning)
|
||||
|
||||
# test for the timer class
|
||||
from boswatch.utils.timer import RepeatedTimer
|
||||
from boswatch.network.netCheck import NetCheck
|
||||
net = NetCheck()
|
||||
|
|
|
|||
70
test/test_broadcast.py
Normal file
70
test/test_broadcast.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#!/usr/bin/python
|
||||
# -*- coding: utf-8 -*-
|
||||
"""!
|
||||
____ ____ ______ __ __ __ _____
|
||||
/ __ )/ __ \/ ___/ | / /___ _/ /______/ /_ |__ /
|
||||
/ __ / / / /\__ \| | /| / / __ `/ __/ ___/ __ \ /_ <
|
||||
/ /_/ / /_/ /___/ /| |/ |/ / /_/ / /_/ /__/ / / / ___/ /
|
||||
/_____/\____//____/ |__/|__/\__,_/\__/\___/_/ /_/ /____/
|
||||
German BOS Information Script
|
||||
by Bastian Schroll
|
||||
|
||||
@file: test_broadcast.py
|
||||
@date: 25.09.2018
|
||||
@author: Bastian Schroll
|
||||
@description: Unittests for BOSWatch. File must be _run as "pytest" unittest
|
||||
"""
|
||||
import logging
|
||||
import time
|
||||
import pytest
|
||||
|
||||
from boswatch.network.broadcast import BroadcastServer
|
||||
from boswatch.network.broadcast import BroadcastClient
|
||||
|
||||
|
||||
class Test_Timer:
|
||||
"""!Unittest for the timer class"""
|
||||
|
||||
def setup_method(self, method):
|
||||
logging.debug("[TEST] %s.%s", type(self).__name__, method.__name__)
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def useBroadcastServer(self):
|
||||
"""!Server a BroadcastServer instance"""
|
||||
self.broadcastServer = BroadcastServer()
|
||||
yield 1 # server the server instance
|
||||
if self.broadcastServer.isRunning:
|
||||
assert self.broadcastServer.stop()
|
||||
while self.broadcastServer.isRunning:
|
||||
pass
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def useBroadcastClient(self):
|
||||
"""!Server a BroadcastClient instance"""
|
||||
self.broadcastClient = BroadcastClient()
|
||||
yield 1 # server the server instance
|
||||
|
||||
# tests start here
|
||||
|
||||
def test_serverStartStop(self, useBroadcastServer):
|
||||
assert self.broadcastServer.start()
|
||||
assert self.broadcastServer.isRunning
|
||||
assert self.broadcastServer.stop()
|
||||
|
||||
def test_serverDoubleStart(self, useBroadcastServer):
|
||||
assert self.broadcastServer.start()
|
||||
assert self.broadcastServer.start()
|
||||
assert self.broadcastServer.stop()
|
||||
|
||||
def test_serverStopNotStarted(self, useBroadcastServer):
|
||||
assert self.broadcastServer.stop()
|
||||
|
||||
def test_clientWithoutServer(self, useBroadcastClient):
|
||||
assert not self.broadcastClient.getConnInfo(1)
|
||||
|
||||
def test_serverClientFetchConnInfo(self, useBroadcastServer, useBroadcastClient):
|
||||
assert self.broadcastServer.start()
|
||||
assert self.broadcastClient.getConnInfo()
|
||||
assert self.broadcastServer.stop()
|
||||
assert self.broadcastClient.serverIP
|
||||
assert self.broadcastClient.serverPort
|
||||
Loading…
Reference in a new issue