Update client widget from server with rpyc callback - python

I want to edit all client QTextBrowser logs when any client sends some message to server.
my intended procedure is this:
[client]enter -> [client]chatUI.handleEnter -> (RFC)[server]exposed_send -> [server]broadcast -> (RFC)[clients]update.emit() -> [clients]listen -> log changed
When I run this code, other clients logs are not changed and only the client that give input to server has an updated log.
How I can solve this to update all clients properly?
chat_server.py
import rpyc
import random
import string
from threading import RLock
users = dict()
callbacks = dict()
user_num = 0
lock = RLock()
buf = dict()
class chatService(rpyc.Service):
def on_connect(self):
global user_num
with lock:
user_num = user_num+1
print ("connect user: %d" % user_num)
def on_disconnect(self):
global user_num
with lock:
user_num = user_num-1
print ("disconnect user: %d" % user_num)
def exposed_accept(self, idt, callback):
with lock:
global users
global callbacks
if not isinstance(idt, str) or len(idt) != 6:
return False
elif idt in users:
return -1
else:
pw = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(32))
users[idt] = pw
callbacks[idt] = rpyc.async(callback)
return pw
def exposed_send(self, target, msg, idt, pw):
print ('here')
name = self.identify(idt, pw)
if name == False:
print ('here2')
return False
else:
print ('here3')
global callbacks
if target == None:
self.broadcast("[%s] %s" % (name, msg))
elif msg.target in callbacks:
self.send("[%s] %s" %(name, msg), target)
else:
return False
def exposed_order(self, msg, idt, pw):
pass
def identify(self, idt, pw):
global users
if users[idt] == pw:
return idt
else:
return False
def broadcast(self, msg):
with lock:
print("bloadcast calls")
global callbacks
global buf
for user, callback in callbacks.items():
if user not in buf or buf[user] == None:
buf[user] = (msg,)
else:
buf[user] = buf[user] + (msg,)
callback()
def send(self, msg, target):
global callbacks
global buf
if user not in buf or buf[user] == None:
buf[target] = (msg,)
else:
buf[target] = buf[target] + (msg,)
callbacks[target]()
def exposed_get_buf(self, user):
global buf
temp = buf[user]
buf[user] = None
return temp
if __name__ == '__main__':
from rpyc.utils.server import ThreadedServer
t = ThreadedServer(chatService, port = 3743)
t.start()
chat_client.py
from chatUI import *
import rpyc
import random
import string
if __name__ == '__main__':
service = rpyc.connect('floating.tk', 3743)
app, chat = UIReady(service)
while True:
idt = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
pw = service.root.accept(idt, chat.update.update.emit)
if pw != False and pw != -1:
break
chat.idt = idt
chat.pw = pw
sys.exit(app.exec_())
chatUI.py
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
class Updater(QObject):
update = pyqtSignal()
class Chat(QWidget):
log = None
enter = None
def __init__(self, service) :
super().__init__()
self.service = service
self.idt = None
self.pw = None
self.initUI()
self.update = Updater()
self.update.update.connect(self.listen)
def initUI(self):
logLabel = QLabel('chat log')
enterLabel = QLabel('enter')
self.log = QTextBrowser()
self.enter = QLineEdit()
self.enter.returnPressed.connect(self.handleEnter)
layout = QGridLayout()
layout.addWidget(logLabel, 0, 0)
layout.addWidget(self.log, 0, 1, 5, 1)
layout.addWidget(enterLabel, 6, 0)
layout.addWidget(self.enter, 6, 1)
self.setLayout(layout)
self.setWindowTitle('chat')
self.resize(600, 600)
self.show()
def handleEnter(self):
msg = self.enter.text()
self.enter.setText("")
self.service.root.send(None, msg, self.idt, self.pw)
print('get enter')
def listen(self):
msg = self.service.root.get_buf(self.idt)
for m in msg:
self.log.append(m)
def UIReady(service):
app = QApplication(sys.argv)
chat = Chat(service)
return app, chat

Related

Chatroom app only updates messages when send button is pressed

I've only just started using pyQt6 as of today, as tkinter was giving me some issues, however now that I have the base of the app working, I'm faced with an issue, my chat (self.output) will only update with new messages anytime that the send button is pressed, this of course is not how I would like it to be working, I'd rather it update once a message has been sent from the server, is there any solution to this problem?
Here is the code
from ast import Delete
import socket
import threading
from PyQt6.QtWidgets import *
from PyQt6.QtGui import *
from PyQt6.QtCore import *
import rsa
import sys
app = QApplication(sys.argv)
HOST = '127.0.0.1'
PORT = 9090
shost = '127.0.0.1'
sport = 9090
def loadKeys():
with open('keys/publicKey.pem', 'rb') as p:
publicKey = rsa.PublicKey.load_pkcs1(p.read())
with open('keys/privateKey.pem', 'rb') as p:
privateKey = rsa.PrivateKey.load_pkcs1(p.read())
return privateKey, publicKey
def encrypt(message, key):
return rsa.encrypt(message.encode('utf-8'), key)
def decrypt(ciphertext, key):
try:
return rsa.decrypt(ciphertext, key).decode('utf-8')
except:
return False
def sign(message, key):
return rsa.sign(message.encode('utf-8'), key, 'SHA-1')
def verify(message, signature, key):
try:
return rsa.verify(message.encode('utf-8'), signature, key,) == 'SHA-1'
except:
return False
privateKey, publicKey = loadKeys()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((shost,sport))
gui_done = False
running = True
class Chatroom(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Harmonic")
self.resize(1920,1080)
self.nickname = QInputDialog.getText(self, 'Nickname', 'Enter your nickname:')
layout = QVBoxLayout()
self.setLayout(layout)
QApplication.processEvents()
self.inputField = QLineEdit()
button = QPushButton('&Send')
button.clicked.connect(self.write)
self.output = QTextEdit()
layout.addWidget(self.inputField)
layout.addWidget(button)
layout.addWidget(self.output)
self.Gui_done = True
self.restoremessages()
receive_thread = threading.Thread(target=self.receive)
receive_thread.start()
def restoremessages(self):
with open('chathistory.txt', 'r') as file:
lines = file.readlines()
for line in lines:
self.output.append('{0}'.format(line))
def write(self):
message = (f"{self.nickname}: {self.inputField.text()}\n")
emessage = encrypt(message, publicKey)
sock.send(emessage)
self.inputField.clear()
def stop(self):
running = False
sock.close
sys.exit(app.exec_())
def receive(self):
while running:
try:
message = sock.recv(10000)
dm = decrypt(message,privateKey)
if message == "NICK":
sock.send(self.nickname.encode())
else:
self.output.append('{0}'.format(dm))
QApplication.processEvents
except ConnectionAbortedError:
print("Connection aborted")
sock.close()
break
except:
print("Error")
sock.close()
break
def closeEvent(self,event):
close = QMessageBox.question(self, "QUIT", "Are you sure you want to stop the process?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
if close == QMessageBox.StandardButton.Yes:
event.accept()
self.stop()
else:
event.ignore()
window = Chatroom()
window.show()
app.exec()
There are certain parts of the code that are redundent that I am aware of, same the error with sys.exit(app.exec_()) for some reason the program would not exit without me throwing an error to do so, so this will have to do for now.

clean_session in pahoMqtt , python not working

I'm using mosquitto 1.5.7 to run a server on rpi 0 which takes the logs from something, and publish it to a topic:
class LiveLogServer:
def __init__(self, hostname, port, numPos):
self.broker = hostname
self.port = port
self.numPos = numPos
self.status = True
self.stopThread = True
self.topicPublish = '/logServers/logServer{numPos}/live'
self.topicSubscribe = '/logServers/logServer/serials'
self.client = mqtt.Client()
self.connect_mqtt()
self.client.on_message = self.on_message
self.subscribe(self.topicSubscribe)
self.startListen()
#start a log for a specific tid in a thread
def startStreamingLog(self, tid):
os.system('adb logcat -c')
self.p = Popen(['adb', '-t', str(tid), 'logcat', '-v', 'brief', 'example:V', '*:S'],
stdout=PIPE, bufsize=-1, close_fds=ON_POSIX)
self.q = Queue()
self.t = Thread(target=self.process_logline, args=(self.p.stdout, self.q))
self.t.daemon = True # thread dies with the program
self.t.start()
self.logs()
def process_logline(self, out, queue):
for line in iter(out.readline, b''):
queue.put(line)
out.close()
#read all the logs from a camera
def logs(self):
while self.status:
try:
line = self.q.get_nowait() # or q.get(timeout=.1)
except Empty: #queue is empty here, do nothing
continue
else: # got line from log
print(line)
lineTemp = str(line.decode('utf-8').rstrip())
self.publish(lineTemp)
def connect_mqtt(self):
# Set Connecting Client ID
self.client.connect(self.broker, self.port)
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
return self.client
def on_connect(self, client, userdata, flags, rc):
if rc == 0:
print("Started LiveLogServer for pos [{}]".format(self.numPos))
else:
print("Failed to connect, return code %d\n", rc)
#receive the serial from the client, get the tid for that serial abd start streaming the logs
def on_message(self, client, userdata, msg):
print(msg.payload.decode('utf-8'))
currentID = CameraID().getLatestTID(msg.payload.decode('utf-8'))
print(currentID)
self.startStreamingLog(currentID)
def subscribe(self, topic):
self.client.subscribe(topic)
def publish(self, msg):
publish.single(self.topicPublish, msg, hostname=self.broker)
def startListen(self):
self.client.loop_start()
And I made a GUI with PyQT5 who subscribe to the topic and take the messages, parse them and apply some rules on each line.
class Ui_logWindow(QtCore.QObject):
messageSignal = pyqtSignal(str)
logWindowSignal = pyqtSignal(str)
output_GUI = ''
def showLogs(self):
arg1 = self.lineEdit.text()
arg2 = int(self.lineEdit_2.text())
arg3 = self.lineEdit_3.text()
arg4 = self.lineEdit_4.text()
arg5 = self.lineEdit_5.text()
arg6 = self.lineEdit_6.text()
self.client = ClientLogParser(arg1, arg2, arg3, arg4, arg5, arg6)
self.client.client.on_message = self.onMsgWin
# self.client.client.on_disconnect = self.on_disconnectHere
self.messageSignal.connect(self.appendToLog)
self.appendLogShortcut = self.messageSignal.emit
self.logWindowSignal.connect(self.appendToLogWindow)
self.appendLogWindowShortcut = self.logWindowSignal.emit
self.t = Thread(target = self.client.startListen, args=[])
self.t.daemon = True
self.t.start()
def submit(self):
self.plainTextEdit_2.clear()
self.showLogs()
def on_disconnectHere(self):
print('on disconnect here')
self.client.client.disconnect()
def appendToLog(self,msg):
self.plainTextEdit.appendPlainText(msg)
self.plainTextEdit.moveCursor(QTextCursor.End)
def appendToLogWindow(self,msg):
self.plainTextEdit_2.appendPlainText(msg)
self.plainTextEdit_2.moveCursor(QTextCursor.End)
def onMsgWin(self, client, userdata, msg):
self.listMsg = msg.payload.decode('utf-8').rstrip()
self.appendLogWindowShortcut(self.listMsg)
for line in self.listMsg.split('\n'):
self.client.lp.parseEvent(line)
self.client.rp.evaluateDict(self.client.lp.dictLogs)
self.output_GUI += "COUNT:\n"
for key, val in self.client.lp.dictLogs.items():
self.output_GUI += f"EVT[{key}] COUNT[{val}]\n"
if val > 0:
self.output_GUI += f'------ the event : {key} has been triggered ------\n'
self.output_GUI += "RULES:\n"
for rule_idx in range(len(self.client.rp.dictRulesList)):
if self.client.rp.resultList[rule_idx] == 'critical':
sys.exit(-1)
if self.client.rp.resultList[rule_idx] == 'warning':
self.output_GUI += '----------succes----------\n'
if self.client.rp.resultList[rule_idx] == 'info':
self.output_GUI += '----------succes----------\n'
if self.client.rp.resultList[rule_idx] == 'succes':
self.output_GUI += '----------succes----------\n'
self.output_GUI += f"RULE:[{rule_idx}][{self.client.rp.dictRulesList[rule_idx]}]\n"
self.output_GUI += f"RESULT[{rule_idx}][{self.client.rp.resultList[rule_idx]}]\n"
self.output_GUI += "----ITERATION-END----\n"
self.appendLogShortcut(self.output_GUI)
class ClientLogParser:
def __init__(self, hostname, port, serial, numPos, lp_path, rp_path):
self.broker = hostname
self.port = port
self.serial = serial
self.numPos = numPos
self.lp = LogParserLive(lp_path)
self.rp = ReportParserLive(rp_path)
self.topicPublish = '/logServers/logServer{numPos}/live'
self.topicSubscribe = '/logServers/logServer/serials'
self.client = mqtt.Client(clean_session=True)
self.connect_mqtt()
# self.client.on_message = self.on_message_local
self.publishSerial(serial)
self.subscribe(self.topicPublish)
self.running = False
# self.startListen()
def connect_mqtt(self):
# Set Connecting Client ID
# self.client.reinitialise(clean_session=True)
self.client.connect(self.broker, self.port)
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
self.client.on_disconnect = self.on_disconnect
return self.client
def on_connect(self, client, userdata, flags, rc):
if rc == 0:
print(f"Started pinServer for pos [{self.numPos}]")
else:
print("Failed to connect, return code %d\n", rc)
def on_disconnect(self, client, userdata, rc):
print('disconnected')
def on_message_local(self, client, userdata, msg):
msg.payload.decode('utf-8')
def subscribe(self, topic):
self.client.subscribe(topic)
def publishSerial(self, msg):
publish.single(self.topicSubscribe, msg, hostname=self.broker)
def startListen(self):
self.running = True
self.client.loop_start()
print('connected')
self.looping()
def looping(self):
while self.running:
sleep(.5)
The thing is the GUI is working as I expect when I click on Submit, after that I click on Stop and it's stopping taking the logs from the server. BUT when I click AGAIN on submit the same log line is doubled, if I do again stop and submit the lines are tripled and so on.
Can anyone help me with this please?
I found the problem, in QT class every time I was instantiate the signals and connect them without disconnect them.
class Ui_logWindow(QtCore.QObject):
messageSignal = pyqtSignal(str)
logWindowSignal = pyqtSignal(str)
So I disconnect them in my disconnect method:
def on_disconnectHere(self):
print('on disconnect here')
self.messageSignal.disconnect()
self.logWindowSignal.disconnect()
self.client.client.disconnect()

Am I using Qthread correctly?

I have a job to create a stm mcu downloader using uart communication.
I used to python and pyqt5.
Results were bad because of the program GUI freezing when starting the download. (download progress is well)
So I changed the code to the way I use Qthread.
But nothing change.
I have 2 Questions.
Am I use Qthread correctly?
How can I fix GUI freezing when start download?
Attach the code below
from PyQt5.QtWidgets import QMainWindow, QApplication, QFileDialog, QTextBrowser
from PyQt5 import uic
from PyQt5.QtCore import *
import sys, serial, time, struct, os
form_class = uic.loadUiType('USART_Downloader.ui')[0]
class Worker(QObject):
ports=['COM%s'%(i+1)for i in range(256)]
check_connect = 0
serial = serial.Serial()
time_instance = 0.00008
finished = pyqtSignal(list)
finished2 = pyqtSignal(str)
finished3 = pyqtSignal(str)
finished4 = pyqtSignal(int)
#pyqtSlot()
def Find_Com(self):
result = []
for i in self.ports:
try:
temp = serial.Serial(i)
temp.close()
result.append(i)
except serial.SerialException:
pass
self.finished.emit(result)
#pyqtSlot(str)
def Apply_Com(self, COM_Port):
self.check_connect=0
self.serial.close()
try :
self.serial = serial.Serial(COM_Port, 115200, timeout = 1)
self.check_connect = 1
self.finished2.emit('Successfully Port Connection')
except serial.SerialException:
self.finished2.emit('Failure Port Connection')
if self.check_connect :
self.serial.write(b'\x7F')
time.sleep(self.time_instance)
res = self.serial.read()
if res == b'\x79' :
self.finished2.emit('Successfully Enter Boot Mode')
else:
self.finished2.emit('Failure Enter Boot Mode')
#pyqtSlot(str)
def Download_Flash(self, fileName):
if self.check_connect:
self.serial.write(b'\x44')
time.sleep(self.time_instance)
self.serial.write(b'\xBB')
time.sleep(self.time_instance)
res = self.serial.read()
if res == b'\x79' :
self.finished3.emit('Erasing')
else:
self.finished3.emit('Failure Enter Erase Flash')
return
self.serial.write(b'\xFF')
time.sleep(self.time_instance)
self.serial.write(b'\xFF')
time.sleep(self.time_instance)
self.serial.write(b'\x00')
time.sleep(self.time_instance)
res = self.serial.read()
if res == b'\x79' :
self.finished3.emit('Successfully Erase Flash')
else:
self.finished3.emit('Failure Enter Erase Flash')
return
else :
self.finished3.emit('Need USART Connection')
Bit_Num = 256
f = open(fileName, 'rb')
download_binary = f.read()
download_binary_len = len(download_binary)
f.close()
start_address = b'\x00\x00\x00\x08'
total_data = []
while(True):
if(download_binary_len > Bit_Num):
total_data.append(Bit_Num)
download_binary_len -= Bit_Num
elif(download_binary_len > 0):
total_data.append(download_binary_len)
download_binary_len = 0
else:
break
self.finished3.emit('Downloading')
for i, k in enumerate(total_data):
self.serial.write(b'\x31')
time.sleep(self.time_instance)
self.serial.write(b'\xCE')
time.sleep(self.time_instance)
res = self.serial.read()
if res == b'\x79' :
pass
else:
self.finished3.emit('Failure Download1')
return
self.serial.write(start_address[3:4])
time.sleep(self.time_instance)
self.serial.write(start_address[2:3])
time.sleep(self.time_instance)
self.serial.write(start_address[1:2])
time.sleep(self.time_instance)
self.serial.write(start_address[0:1])
time.sleep(self.time_instance)
temp2 = struct.unpack('4B', start_address)
check_sum = temp2[0] ^ temp2[1] ^ temp2[2] ^ temp2[3]
check_sum = struct.pack('B', check_sum)
self.serial.write(check_sum)
time.sleep(self.time_instance)
res = self.serial.read()
if res == b'\x79' :
pass
else:
self.finished3.emit('Failure Download2')
return
check_sum = (k-1)
self.serial.write(struct.pack('B', check_sum))
time.sleep(self.time_instance)
for j in range(k):
self.serial.write(download_binary[(i*Bit_Num)+j:(i*Bit_Num)+j+1])
time.sleep(self.time_instance)
for j in download_binary[(i*Bit_Num):(i*Bit_Num)+j+1] :
check_sum = check_sum ^ j
check_sum = struct.pack('B', check_sum)
self.serial.write(check_sum)
time.sleep(self.time_instance)
res = self.serial.read()
if res == b'\x79' :
pass
else:
self.finished3.emit('Failure Download3')
return
temp3 = struct.unpack('i', start_address)[0]
temp3 = temp3 + Bit_Num
start_address = struct.pack('i', temp3)
self.finished4.emit(i/(len(total_data)-1)*100)
self.finished3.emit('Success Download')
class MyWindowClass(QMainWindow, form_class):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.setupUi(self)
self.fileName = ''
self.worker_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.worker_thread)
self.worker_thread.start()
self.worker.finished.connect(self.Refresh_Com)
self.worker.finished2.connect(self.Print_textBrowser)
self.worker.finished3.connect(self.Print_textBrowser_3)
self.worker.finished4.connect(self.Set_ProgressBar)
self.pushButton.clicked.connect(self.Find_Binary)
self.pushButton_2.clicked.connect(lambda:self.worker.Download_Flash(self.fileName))
self.pushButton_3.clicked.connect(lambda:self.worker.Apply_Com(self.comboBox.currentText()))
self.pushButton_4.clicked.connect(self.worker.Find_Com)
self.textBrowser.setAcceptRichText(True)
self.textBrowser.setOpenExternalLinks(True)
self.textBrowser_3.setAcceptRichText(True)
self.textBrowser_3.setOpenExternalLinks(True)
self.progressBar.reset()
self.worker.Find_Com()
#pyqtSlot(list)
def Refresh_Com(self, result):
self.comboBox.clear()
for i in result:
self.comboBox.addItem(i)
self.textBrowser.append('COM Port Refresh Done')
#pyqtSlot(str)
def Print_textBrowser(self, text):
self.textBrowser.append(text)
#pyqtSlot(str)
def Print_textBrowser_3(self, text):
self.textBrowser_3.append(text)
#pyqtSlot(int)
def Set_ProgressBar(self, percent):
self.progressBar.setValue(percent)
def Find_Binary(self):
options = QFileDialog.Options()
self.fileName, _ = QFileDialog.getOpenFileName(self, "QFileDialog.getOpenFileName()", "", "Binary Files (*.bin)", options=options)
self.lineEdit.setText(self.fileName)
if __name__ == "__main__":
app = QApplication(sys.argv)
myWindow = MyWindowClass()
myWindow.show()
app.exec_()
You're using time.sleep() in your code which blocks your GUI. I would recommend you to use QTimer.singleShot() instead. according from this source, you can use time.sleep equivalent like this QtTest.QTest.qWait(msecs)

Why python pickle does not work dos to unix?

Few days ago I coded a mini game to learn socket and threading. When I run my game server and client on windows it just works perfectly but when I moved my server file to my test server it gives me this pickle error:
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
self.run()
File "/usr/lib/python3.8/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
File "server.py", line 46, in handle_client
obj = pickle.loads(conn.recv(obj_length))
_pickle.UnpicklingError: invalid load key, ' '.
What could be the problem?
Whole game files: ---------------------------------------------------------------------------------
Codes that might help:
server.py:
import socket
import threading
import pickle
import time
import random
import ast
#-------------------------------------------------------------------------
class Server():
def __init__(self):
self.HEADER = 2048
self.PORT = 6000
self.SERVER = "ip"
self.ADDR = (self.SERVER, self.PORT)
self.FORMAT = 'utf-8'
self.DISCONNECT_MESSAGE = "!DISCONNECT"
self.ROLES = ["Mafya", "Mafya", "Köylü", "Doktor","Gözcü"]
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.bind(self.ADDR)
#---------------------------------------------------------------------------------------------
self.names = list()
self.addresses = list()
self.lobby_dict = dict()
self.game_dict = dict()
self.ready_list = list()
self.alive_list = list()
self.vote = list()
self.kill_vote = list()
self.who_voted = list()
self.ready_for_day = list()
self.protected = None
def handle_client(self, conn, addr):
try:
if addr[0] not in self.addresses:
print(f"[NEW CONNECTION] {addr[0]} connected.")
self.addresses.append(addr[0])
connected = True
while connected:
obj_length = conn.recv(self.HEADER).decode(self.FORMAT)
if obj_length:
obj_length = int(obj_length)
obj = pickle.loads(conn.recv(obj_length))
if obj == self.DISCONNECT_MESSAGE:
connected = False
print(f"[DISCONNECTED] {addr[0]} disconnected.")
elif "?ONLINE" in obj:
lobby_id = obj.split(":")[1]
conn.send(pickle.dumps(self.lobby_dict[lobby_id]["Players"]))
elif "!NEWLOBBY" in obj:
splitted_obj = obj.split(":")
lobby_id = splitted_obj[1]
admin = splitted_obj[2]
capacity = splitted_obj[3]
self.lobby_dict[lobby_id] = {"Players":[admin],"Capacity":capacity}
elif "!JOINLOBBY" in obj:
splitted_obj = obj.split(":")
lobby_id = splitted_obj[1]
name = splitted_obj[2]
if lobby_id in self.lobby_dict.keys():
self.lobby_dict[lobby_id]["Players"].append(name)
conn.send(pickle.dumps(f"True:{self.lobby_dict[lobby_id]['Capacity']}"))
else:
conn.send(pickle.dumps("False"))
elif "?ALIVE" in obj:
conn.send(pickle.dumps(self.alive_list))
#-----------------------------------------------------------------------------------------------
#Game commands:
elif "!NAME" in obj:
name = obj.split(":")[1]
self.names.append(name)
elif "!ALIVE" in obj:
conn.send(pickle.dumps(self.names))
elif "!READY" in obj:
ready_player = obj.split(":")[1]
self.ready_list.append(ready_player)
elif "!SHUFFLE" in obj:
if len(self.ready_list) == len(self.names):
temp = self.ROLES
if len(self.names) > len(self.ROLES):
for i in range(0,len(self.names) - len(self.ROLES)):
temp.append("Köylü")
random.shuffle(temp)
for i in range(len(self.names)):
self.game_dict[self.names[i]] = temp[i]
conn.send(pickle.dumps(f"True/{self.game_dict}"))
with open("shuffled_roles.txt", "w", encoding="utf-8") as file:
file.write(str(self.game_dict))
print(f"[SHUFFLED LIST] {self.game_dict}")
else:
conn.send(pickle.dumps("False"))
elif "!ROLES" in obj:
if len(self.ready_list) == len(self.names):
with open("shuffled_roles.txt", "r", encoding="utf-8") as file:
line = file.readline()
self.game_dict = ast.literal_eval(line)
conn.send(pickle.dumps(f"True/{self.game_dict}"))
else:
conn.send(pickle.dumps("False"))
elif "!VOTE" in obj:
voted_player = obj.split(":")[1]
who = obj.split(":")[2] + ": " + voted_player
self.who_voted.append(who)
self.vote.append(voted_player)
elif "!VRESULTS" in obj:
conn.send(pickle.dumps(self.vote))
conn.send(pickle.dumps(self.who_voted))
elif "!VCLEAN" in obj:
self.vote = []
self.who_voted = []
elif "!PROTECTED" in obj:
protected = obj.split(":")[1]
self.protected = obj
elif "!NIGHT_KILL" in obj:
kill = obj.split(":")[1]
self.kill_vote.append(kill)
elif "!NKRESULTS" in obj:
nk_results = self.kill_vote
nk_protected = self.protected
if len(nk_results) == 1:
if nk_results[0] != nk_protected:
conn.send(pickle.dumps(nk_results[0]))
elif len(nk_results) == 2:
if nk_results[0] == nk_results[1]:
if nk_results[0] != nk_protected and nk_results[0] != "None":
conn.send(pickle.dumps(nk_results[0]))
elif nk_results[0] == "None" and nk_results[1] != "None":
conn.send(pickle.dumps(nk_results[1]))
elif nk_results[1] == "None" and nk_results[0] != "None":
conn.send(pickle.dumps(nk_results[0]))
else:
conn.send(pickle.dumps(None))
else:
conn.send(pickle.dumps(None))
elif "!NKCLEAN" in obj:
self.protected = "None"
self.kill_vote = []
elif "!RFORDAY" in obj:
rplayer = obj.split(":")[1]
self.ready_for_day.append(rplayer)
elif "!RFDLIST" in obj:
conn.send(pickle.dumps(self.ready_for_day))
elif "!RFDCLEAN" in obj:
self.ready_for_day = list()
else:
print(f"[{addr}] {obj}") #İsimler Buradan -> addr
except ConnectionResetError:
print(f"[CONNECTION] {addr} Connection reset exception has been handled.")
finally:
conn.close()
def start(self):
print("[STARTING] Server is starting...")
self.server.listen()
print("[LISTENING] Server is listening on {}".format(self.SERVER))
while True:
conn, addr = self.server.accept()
thread = threading.Thread(target=self.handle_client, args=(conn, addr))
thread.start()
if __name__ == "__main__":
server = Server()
server.start()
app.py:
import sys
import os
import time
import random
import socket
import threading
import pickle
class Config():
def __init__(self):
self.HEADER = 2048
self.PORT = 6000
self.SERVER = "ip" #socket.gethostbyname(socket.gethostname())
self.ADDR = (self.SERVER, self.PORT)
self.FORMAT = 'utf-8'
self.DISCONNECT_MESSAGE = "!DISCONNECT"
self.client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.menuText = """
░██╗░░░░░░░██╗██╗░██████╗███████╗  ████████╗░█████╗░░██╗░░░░░░░██╗███╗░░██╗
░██║░░██╗░░██║██║██╔════╝██╔════╝  ╚══██╔══╝██╔══██╗░██║░░██╗░░██║████╗░██║
░╚██╗████╗██╔╝██║╚█████╗░█████╗░░  ░░░██║░░░██║░░██║░╚██╗████╗██╔╝██╔██╗██║
░░████╔═████║░██║░╚═══██╗██╔══╝░░  ░░░██║░░░██║░░██║░░████╔═████║░██║╚████║
░░╚██╔╝░╚██╔╝░██║██████╔╝███████╗  ░░░██║░░░╚█████╔╝░░╚██╔╝░╚██╔╝░██║░╚███║
░░░╚═╝░░░╚═╝░░╚═╝╚═════╝░╚══════╝  ░░░╚═╝░░░░╚════╝░░░░╚═╝░░░╚═╝░░╚═╝░░╚══╝
"""
class Client(Config):
def __init__(self):
super().__init__()
self.client.connect(self.ADDR)
self.admin = None
def send(self, obj):
obj = pickle.dumps(obj)
obj_length = len(obj)
send_length = str(obj_length).encode(self.FORMAT)
send_length += b' ' * (self.HEADER - len(send_length))
self.client.send(send_length)
self.client.send(obj)
def messenger(self):
while True:
msg = input("Mesaj: ")
self.send(msg)
class Menu(Client):
def __init__(self):
super().__init__()
class MainMenu(Menu):
def __init__(self, player_name):
super().__init__()
self.player_name = player_name
def printMainMenu(self, game_is_on = False):
print("\n" * 7 + self.menuText)
time.sleep(2)
os.system("cls")
print(self.menuText)
print("""
1. Yeni Oyun Oluştur
2. Oyuna Katıl
""")
class Lobby(Menu):
def __init__(self):
super().__init__()
def lobbyMenu(self):
name_send = f"!NAME:{self.player_name}"
self.send(name_send)
os.system("cls")
print(self.menuText)
print(f"Lobby ID: {self.id}")
self.send(f"?ONLINE:{self.id}")
self.online_list = pickle.loads(self.client.recv(2048))
temp_list = self.online_list
sys.stdout.write("Aktif Oyuncular:| ")
for i in self.online_list:
sys.stdout.write(f"{i} | ")
sys.stdout.flush()
while not self.game_started:
time.sleep(1)
self.send("?ONLINE:" + str(self.id))
self.online_list = pickle.loads(self.client.recv(2048))
if temp_list != self.online_list:
sys.stdout.write("\rAktif Oyuncular:| ")
for i in self.online_list:
sys.stdout.write(f"{i} | ")
sys.stdout.flush()
temp_list = self.online_list
if len(self.online_list) == self.capacity:
self.game_started = True
class CreateLobby(Lobby):
def __init__(self, capacity, player_name, admin):
super().__init__()
self.player_name = player_name
self.admin = admin
self.id = random.randint(100000,999999)
self.capacity = int(capacity)
self.game_started = False
self.send(f"!NEWLOBBY:{self.id}:{self.player_name}:{self.capacity}")
self.lobbyMenu()
class JoinLobby(Lobby):
def __init__(self, id, player_name):
super().__init__()
self.id = id
self.player_name = player_name
self.game_started = False
self.lobby_joiner()
def lobby_joiner(self):
self.send(f"!JOINLOBBY:{self.id}:{self.player_name}")
bool_obj = pickle.loads(self.client.recv(2048))
while bool_obj == "False":
print("Bu ID'ye ait lobby bulunmamaktadır.")
self.id = input(" Lobby ID: ")
self.send(f"!JOINLOBBY:{self.id}:{self.player_name}")
bool_obj = pickle.loads(self.client.recv(2048))
self.capacity = int(bool_obj.split(":")[1])
self.lobbyMenu()
#-------------------------------------------------------------------------------------------------------------
Your code includes
obj = pickle.loads(conn.recv(obj_length))
The problem is that TCP is a streaming protocol and the entire obj_length of data may not have been received when the call is made. When you run client and server on the same machine, you don't have the real network with real segmentation and delays, so you don't see the problem.
The solution is your own receiver that knows to keep asking for data until it sees it all
def recvall(conn, count):
recvlist = []
recvcount = 0
while recvcount < count:
buf = conn.recv(count-recvcount)
if not buf:
# replace with your error handling here
raise OSError("Connection terminated")
recvlist.append(buf)
recvcount += len(buf)
return b"".join(recvlist)
Replace your original line with
obj = pickle.loads(recvall(conn, obj_length))
and it should work

Putting the body back through a python milter

(never mind...solved it)
I've written a milter in Python using pymilter that extracts attachments and saves them to a folder (thanks SO for the recommendation). I've gotten to the point in writing the milter where I need to put the original body of the message back in with the original formatting, but I'm having difficulty seeing exactly how to do this.
FYI - I am a NOVICE python programmer, but have learned much in the past few days.
Here is my current code for examination:
#! /usr/bin/env python
import Milter
import StringIO
import email
import email.Message
import hashlib
import mime
import os
import sys
import tempfile
import time
import rfc822
from Milter.utils import parse_addr
from email import Errors
from email.Message import Message
## == IP Information
from socket import AF_INET, AF_INET6
## ==
## === Define multiprocessing == ##
if True:
from multiprocessing import Process as Thread, Queue
else:
from threading import Thread
from Queue import Queue
logq = Queue(maxsize=4)
def background():
while True:
t = logq.get()
if not t: break
msg,id,ts = t
print "%s [%d]" % (time.strftime('%Y%b%d %H:%M:%S',time.localtime(ts)),id),
# 2005Oct13 02:34:11 [1] msg1 msg2 msg3 ...
for i in msg: print i,
print
## === End Define Multiprocesing === ##
class mltr_SaveAttachments(Milter.Base):
def __init__(self):
self.id = Milter.uniqueID()
def close(self):
# always called, even when abort is called. Clean up
# any external resources here.
return Milter.CONTINUE
def abort(self):
# client disconnected prematurely
return Milter.CONTINUE
def log(self,*msg):
logq.put((msg,self.id,time.time()))
#Milter.noreply
def connect(self, IPname, family, hostaddr):
self.IP = hostaddr[0]
self.port = hostaddr[1]
if family == AF_INET6:
self.flow = hostaddr[2]
self.scope = hostaddr[3]
else:
self.flow = None
self.scope = None
self.IPname = IPname # Name from a reverse IP lookup
self.H = None
self.fp = None
self.receiver = self.getsymval('j')
self.log("connect from %s at %s" % (IPname, hostaddr) )
return Milter.CONTINUE
#Milter.noreply
def header(self, name, hval):
self.fp.write("%s: %s\n" % (name,hval)) # add header to buffer
return Milter.CONTINUE
#Milter.noreply
def body(self, chunk):
self.fp.write(chunk)
return Milter.CONTINUE
#Milter.noreply
def eoh(self):
self.fp.write("\n") # terminate headers
return Milter.CONTINUE
def envfrom(self,mailfrom,*str):
# self.log("envfrom")
self.F = mailfrom
self.R = []
self.fromparms = Milter.dictfromlist(str)
self.user = self.getsymval('{auth_authen}')
self.log("mail from:", mailfrom, *str)
self.fp = StringIO.StringIO()
self.canon_from = '#'.join(parse_addr(mailfrom))
self.fp.write('From %s %s\n' % (self.canon_from,time.ctime()))
return Milter.CONTINUE
## def envrcpt(self, to, *str):
#Milter.noreply
def envrcpt(self, recipient, *str):
rcptinfo = recipient,Milter.dictfromlist(str)
self.R.append(rcptinfo)
return Milter.CONTINUE
def attachment(self):
msg = self._msg
attachDir = attach_dir(msg)
removedParts = []
payload = []
for part in msg.walk():
fname = ""
self.log(part.getparams())
if part.is_multipart():
continue
dtypes = part.get_params(None, 'Content-Disposition')
if not dtypes:
if part.get_content_type() == 'text/plain':
payload.append(part)
continue
ctypes = part.getparams()
if not ctypes:
continue
for key,val in ctypes:
if key.lower() == 'name':
fname = val
else:
for key,val in dtypes:
if key.lower() == 'filename':
fname = val
if fname:
removedParts.append(fname)
data = part.get_payload(decode=1)
extract_attachment(data, attachDir, fname)
part = self.delete_attachments(part, fname)
payload.append(part)
# del msg["content-type"]
# del msg["content-disposition"]
# del msg["content-transfer-encoding"]
# msg.set_payload(payload)
self._msg = msg
out = tempfile.TemporaryFile()
try:
self.log("dumping")
msg.dump(out)
out.seek(0)
msg = rfc822.Message(out)
msg.rewindbody()
while 1:
buf = out.read(8192)
if len(buf) == 0: break
self.replacebody(buf)
finally:
out.close()
self._msg.attach(payload)
return Milter.CONTINUE
def delete_attachments(self, part,fname):
for key,value in part.get_params():
part.del_param(key)
part.set_payload('[DELETED]\n')
del part["content-type"]
del part["content-disposition"]
del part["content-transfer-encoding"]
part["Content-Type"] = "text/html, name="+fname+".html"
return part
def eom(self):
self.fp.seek(0)
msg = mime.message_from_file(self.fp)
self._msg = msg
self.attachment()
# self.log("### MESSAGE ###")
# self.log(self._msg)
# return Milter.ACCEPT
return Milter.TEMPFAIL
## ===
def attach_dir(msg):
tempname = fname = tempfile.mktemp(".tmp")
out = tempfile.TemporaryFile()
msg.dump(out)
out.seek(0)
buf = out.read()
hashDir = hashit(buf)
attachDir = dropDir + hashDir
if not os.path.isdir(hashDir):
os.mkdir(attachDir)
return attachDir
def extract_attachment(data, attachDir, fname):
exdir_file = attachDir + "/" + fname
extracted = open(exdir_file, "wb")
extracted.write(data)
extracted.close()
def hashit(data):
sha1 = hashlib.sha1()
sha1.update(data)
return sha1.hexdigest()
dropDir = "/dropdir/"
def main():
bt = Thread(target=background)
bt.start()
socketname = "/tmp/py_testmilter.sock"
timeout = 600
Milter.factory = mltr_SaveAttachments
flags = Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS
flags += Milter.ADDRCPT
flags += Milter.DELRCPT
Milter.set_flags(flags) # tell Sendmail/Postfix which features we use
print "%s milter startup" % time.strftime('%Y%b%d %H:%M:%S')
sys.stdout.flush()
Milter.runmilter("py_testmilter",socketname,timeout)
logq.put(None)
bt.join()
print "%s milter shutdown" % time.strftime('%Y%b%d %H:%M:%S')
if __name__ == "__main__":
main()

Categories