fstream/fstream-server.py

364 lines
9.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
import sys
oldexcepthook = sys.excepthook
def newexcepthook(type,value,traceback):
oldexcepthook(type,value,traceback)
#input("Press ENTER to quit.")
sys.excepthook = newexcepthook
import os
p = os.path.join
pUp = os.path.dirname
s = False
if getattr(sys, 'frozen', False) and hasattr(sys, '_MEIPASS'):
s = os.path.realpath(sys.executable)
else:
s = os.path.realpath(__file__)
sp = pUp(s)
# script start
2021-04-13 18:33:16 +00:00
import threading
import queue
import socket
import subprocess
import configparser
import time
2021-04-13 18:33:16 +00:00
2021-04-14 16:28:54 +00:00
connections = {}
2021-04-22 02:29:12 +00:00
connectionsId = 0
2021-04-14 16:28:54 +00:00
connectionsLock = threading.Lock()
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
threadCount = 0
threadCountLock = threading.Lock()
fileLock = threading.Lock()
2021-04-15 18:19:45 +00:00
serverAddr = ("127.0.0.1",61920)
2021-04-13 18:33:16 +00:00
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2021-04-20 14:32:56 +00:00
bufferSize = 10000 # Buffer size in bytes
2021-04-14 16:52:47 +00:00
timeout = 15 # How long to wait for a connection to respond before timing out?
maxClients = 20 # How many clients can be connected at maximum?
maxClientsPerIP = 3 # How many clients can be connected at maximum, per IP?
maxAccumulatedData = 50*1000*1000 # How much data can be in an outbound thread's queue at maximum before the connection is closed? The maximum amount of queue data is maxAccumulatedData * maxClients bytes.
maxInboundTransferRate = 0 # Maximum upload speed for broadcasters. This sucks right now. Set to 0 to disable.
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
def addThread():
global threadCount
with threadCountLock:
threadCount += 1
print("Thread opened. Threads: " +str(threadCount)+ " (Actual: " +str(threading.active_count())+ ")")
def removeThread():
global threadCount
with threadCountLock:
threadCount -= 1
print("Thread closed. Threads: " +str(threadCount)+ " (Actual: " +str(threading.active_count())+ ")")
def commandToList(cmd):
args = []
cArg = ""
escape = False
quoted = False
for letter in cmd:
if escape == True:
cArg += letter
escape = False
continue
if letter == "\\":
escape = True
continue
#if quoted == False and letter == ",":
if letter == ",":
if cArg == "": continue
args.append(cArg)
cArg = ""
continue
#if letter == '"':
# quoted = not quoted
# continue
cArg += letter
args.append(cArg)
return args
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
def closeConnection(connectionId):
if connectionId in connections:
2021-04-22 03:34:11 +00:00
try:
connections[connectionId]["connection"].close()
except Exception as e:
print("Failed to close connection: " +str(e))
pass
2021-04-22 02:29:12 +00:00
del connections[connectionId]
2021-04-13 18:33:16 +00:00
class outThread(threading.Thread):
2021-04-22 02:29:12 +00:00
def __init__(self,connectionId):
2021-04-13 18:33:16 +00:00
threading.Thread.__init__(self)
self.queue = queue.Queue()
2021-04-22 02:29:12 +00:00
self.connectionId = connectionId
2021-04-14 16:28:54 +00:00
def getConnection(self):
with connectionsLock:
2021-04-22 02:29:12 +00:00
if self.connectionId in connections:
return connections[self.connectionId]["connection"]
return False
2021-04-13 18:33:16 +00:00
def run(self):
try:
while True:
2021-04-16 02:29:28 +00:00
data = self.queue.get(timeout=15)
2021-04-22 02:29:12 +00:00
connection = self.getConnection()
if not connection:
removeThread()
return
connection.sendall(data)
with connectionsLock: closeConnection(self.connectionId)
removeThread()
return
2021-04-13 18:33:16 +00:00
except:
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
removeThread()
2021-04-21 12:02:53 +00:00
print("Thread closed - Exception:")
2021-04-13 18:33:16 +00:00
raise
class inThread(threading.Thread):
2021-04-22 02:29:12 +00:00
def __init__(self,connectionId):
2021-04-13 18:33:16 +00:00
threading.Thread.__init__(self)
2021-04-22 02:29:12 +00:00
self.connectionId = connectionId
2021-04-14 16:28:54 +00:00
def getConnection(self):
with connectionsLock:
2021-04-22 02:29:12 +00:00
if self.connectionId in connections:
return connections[self.connectionId]["connection"]
return False
2021-04-13 18:33:16 +00:00
def run(self):
try:
2021-04-22 02:29:12 +00:00
connection = self.getConnection()
if not connection:
removeThread()
return
data = connection.recv(1000)
2021-04-14 03:12:35 +00:00
if data == b"":
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
2021-04-21 12:02:53 +00:00
print("Thread closed - Client disconnected.")
2021-04-22 02:29:12 +00:00
removeThread()
2021-04-14 03:12:35 +00:00
return
data = data.decode("utf-8")
while data[-1] == " ": data = data[:-1]
args = commandToList(data)
cmd = args.pop(0)
user = args[0]
self.user = user
password = False
if len(args) > 1:
password = args[1]
userPath = p(sp,"users",user+ ".ini")
with fileLock:
if not os.path.isfile(userPath):
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
2021-04-21 12:02:53 +00:00
print("Thread closed - Invalid user given: " +user)
2021-04-22 02:29:12 +00:00
removeThread()
return
config = configparser.ConfigParser()
config.read(userPath)
if cmd == "watch":
if cmd in config and "pass" in config[cmd] and config[cmd]["pass"] != "" and config[cmd]["pass"] != password:
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
2021-04-21 12:02:53 +00:00
print("Thread closed - Invalid password given for user: " +user)
2021-04-22 02:29:12 +00:00
removeThread()
return
2021-04-22 02:29:12 +00:00
with connectionsLock:
if not self.connectionId in connections:
removeThread()
return
connections[self.connectionId]["action"] = "watch"
connections[self.connectionId]["user"] = user
thread = outThread(self.connectionId)
connections[self.connectionId]["outThread"] = thread
thread.start()
2021-04-22 02:29:12 +00:00
addThread()
connections[self.connectionId]["inThread"] = False
removeThread()
return
if cmd == "broadcast":
if cmd in config and "pass" in config[cmd] and config[cmd]["pass"] != "" and config[cmd]["pass"] != password:
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
2021-04-21 12:02:53 +00:00
print("Thread closed - Invalid password given for user: " +user)
2021-04-22 02:29:12 +00:00
removeThread()
return
2021-04-22 02:29:12 +00:00
with connectionsLock:
2021-04-22 03:34:55 +00:00
if not self.connectionId in connections:
removeThread()
return
2021-04-14 20:03:26 +00:00
deleteList = []
2021-04-22 02:29:12 +00:00
for connectionId in connections:
if connectionId == self.connectionId: continue
if connections[connectionId]["action"] != "broadcast": continue
if connections[connectionId]["user"] != user: continue
deleteList.append(connectionId)
2021-04-14 20:03:26 +00:00
2021-04-22 02:29:12 +00:00
for connectionId in deleteList:
2021-04-22 03:34:55 +00:00
closeConnection(connectionId)
2021-04-22 02:29:12 +00:00
2021-04-22 03:34:55 +00:00
connections[self.connectionId]["action"] = "broadcast"
connections[self.connectionId]["user"] = user
2021-04-14 20:03:26 +00:00
if maxInboundTransferRate > 0: startTime = time.time()
while True:
2021-04-22 02:29:12 +00:00
connection = self.getConnection()
if not connection:
removeThread()
return
data = connection.recv(bufferSize)
2021-04-14 03:12:35 +00:00
if data == b"":
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
2021-04-21 12:02:53 +00:00
print("Thread closed - Client disconnected.")
2021-04-22 02:29:12 +00:00
removeThread()
2021-04-14 03:12:35 +00:00
return
2021-04-22 02:29:12 +00:00
with connectionsLock:
for connectionId in connections:
connectionData = connections[connectionId]
thread = connectionData["outThread"]
if not thread: continue
if connectionData["user"] != user: continue
accumulatedData = thread.queue.qsize() * bufferSize
if accumulatedData > maxAccumulatedData:
print("Thread closed - Too much accumulated data.")
closeConnection(connectionId)
removeThread()
return
thread.queue.put(data)
if maxInboundTransferRate > 0:
endTime = time.time()
timeTaken = endTime - startTime
if timeTaken <= 0: timeTaken = 0.00001 # this isn't good enough
transferSpeed = bufferSize / timeTaken
if transferSpeed > maxInboundTransferRate:
maxSleep = (bufferSize/maxInboundTransferRate) - timeTaken
if maxSleep > 0:
sleepTime = (transferSpeed / maxInboundTransferRate) - 1
if sleepTime > maxSleep: sleepTime = maxSleep
time.sleep(sleepTime)
startTime = endTime
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
removeThread()
return
except:
2021-04-22 02:29:12 +00:00
with connectionsLock: closeConnection(self.connectionId)
removeThread()
2021-04-21 12:02:53 +00:00
print("Thread closed - Exception:")
raise
2021-04-13 18:33:16 +00:00
2021-04-14 03:12:49 +00:00
class debugThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
while True:
2021-04-22 02:29:12 +00:00
with connectionsLock:
2021-04-14 16:28:54 +00:00
print("\n---\n")
2021-04-14 03:12:49 +00:00
print("Threads - IN: " +str(len(inThreads)))
print("Threads - OUT: " +str(len(outThreads)))
2021-04-14 16:28:54 +00:00
print("\nACCUMULATED DATA:")
2021-04-14 15:03:25 +00:00
for threadId in outThreads:
thread = outThreads[threadId]
print(threadId + ": " + str(thread.queue.qsize() * bufferSize))
2021-04-14 16:28:54 +00:00
print("\nCONNECTIONS:")
connCount = 0
connCountIp = {}
with connectionsLock:
for connId in connections:
conn = connections[connId]
ip = conn[1][0]
if not ip in connCountIp:
connCountIp[ip] = 0
connCountIp[ip] += 1
connCount += 1
for ip in connCountIp:
print(ip+ ": " +str(connCountIp[ip]))
print("Overall: " +str(connCount))
2021-04-14 15:03:25 +00:00
time.sleep(1)
2021-04-14 03:12:49 +00:00
2021-04-15 18:54:38 +00:00
def readConfig():
config = configparser.ConfigParser()
config.read(p(os.path.splitext(s)[0] + ".ini"))
global serverAddr
serverAddrSplit = config["default"]["serverAddr"].rsplit(":",1)
serverAddrSplit[1] = int(serverAddrSplit[1])
serverAddr = tuple(serverAddrSplit)
2021-04-13 18:33:16 +00:00
def main():
2021-04-22 02:29:12 +00:00
global connectionsId
2021-04-15 18:54:38 +00:00
readConfig()
serverSocket.bind(serverAddr)
serverSocket.listen(1024)
2021-04-14 03:12:49 +00:00
# DEBUG
2021-04-22 02:29:12 +00:00
#debug = debugThread()
#debug.start()
2021-04-14 03:12:49 +00:00
# DEBUG END
2021-04-13 18:33:16 +00:00
while True:
connection, address = serverSocket.accept()
2021-04-14 16:52:47 +00:00
connection.settimeout(timeout)
2021-04-14 16:28:54 +00:00
with connectionsLock:
clientCount = 0
ipClientCount = 0
2021-04-22 02:29:12 +00:00
for connectionId in connections:
2021-04-14 16:28:54 +00:00
clientCount += 1
2021-04-22 02:29:12 +00:00
if connections[connectionId]["address"][0] == address[0]:
2021-04-14 16:28:54 +00:00
ipClientCount += 1
2021-04-22 02:29:12 +00:00
if clientCount + 1 > maxClients:
print("Connection closed - too many clients.")
closeConnection(connectionId)
continue
if ipClientCount + 1 > maxClientsPerIP:
2021-04-21 12:02:53 +00:00
print("Connection closed - same IP connected too many times.")
2021-04-22 02:29:12 +00:00
closeConnection(connectionId)
2021-04-14 16:28:54 +00:00
continue
2021-04-13 18:33:16 +00:00
2021-04-22 02:29:12 +00:00
connectionsId += 1
threadIn = inThread(str(connectionsId))
connections[str(connectionsId)] = {
"connection": connection,
"address": address,
"inThread": threadIn,
"outThread": False,
"action": False,
"user": False
}
threadIn.start()
addThread()
2021-04-13 18:33:16 +00:00
if __name__ == '__main__':
main()