Python Twisted sending large a file across network - python-2.7

I am trying to send a file across the network using Twisted with the LineReceiver protocol. The issue I am seeing is that when I read a binary file and try to send the chunks they simply don't send.
I am reading the file using:
import json
import time
import threading
from twisted.internet import reactor, threads
from twisted.protocols.basic import LineReceiver
from twisted.internet import protocol
MaximumMsgSize = 15500
trySend = True
connectionToServer = None
class ClientInterfaceFactory(protocol.Factory):
def buildProtocol(self, addr):
return WoosterInterfaceProtocol(self._msgProcessor, self._logger)
class ClientInterfaceProtocol(LineReceiver):
def connectionMade(self):
connectionToServer = self
def _DecodeMessage(self, rawMsg):
header, body = json.loads(rawMsg)
return (header, json.loads(body))
def ProcessIncomingMsg(self, rawMsg, connObject):
# Decode raw message.
decodedMsg = self._DecodeMessage(rawMsg)
self.ProccessTransmitJobToNode(decodedMsg, connObject)
def _BuildMessage(self, id, msgBody = {}):
msgs = []
fullMsgBody = json.dumps(msgBody)
msgBodyLength = len(fullMsgBody)
totalParts = 1 if msgBodyLength <= MaximumMsgSize else \
int(math.ceil(msgBodyLength / MaximumMsgSize))
startPoint = 0
msgBodyPos = 0
for partNo in range(totalParts):
msgBodyPos = (partNo + 1) * MaximumMsgSize
header = {'ID' : id, 'MsgParts' : totalParts,
'MsgPart' : partNo }
msg = (header, fullMsgBody[startPoint:msgBodyPos])
jsonMsg = json.dumps(msg)
msgs.append(jsonMsg)
startPoint = msgBodyPos
return (msgs, '')
def ProccessTransmitJobToNode(self, msg, connection):
rootDir = '../documentation/configs/Wooster'
exportedFiles = ['consoleLog.txt', 'blob.dat']
params = {
'Status' : 'buildStatus',
'TaskID' : 'taskID',
'Name' : 'taskName',
'Exports' : len(exportedFiles),
}
msg, statusStr = self._BuildMessage(101, params)
connection.sendLine(msg[0])
for filename in exportedFiles:
with open (filename, "rb") as exportFileHandle:
data = exportFileHandle.read().encode('base64')
params = {
ExportFileToMaster_Tag.TaskID : taskID,
ExportFileToMaster_Tag.FileContents : data,
ExportFileToMaster_Tag.Filename : filename
}
msgs, _ = self._BuildMessage(MsgID.ExportFileToMaster, params)
for m in msgs:
connection.sendLine(m)
def lineReceived(self, data):
threads.deferToThread(self.ProcessIncomingMsg, data, self)
def ConnectFailed(reason):
print 'Connection failed..'
reactor.callLater(20, reactor.callFromThread, ConnectToServer)
def ConnectToServer():
print 'Connecting...'
from twisted.internet.endpoints import TCP4ClientEndpoint
endpoint = TCP4ClientEndpoint(reactor, 'localhost', 8181)
deferItem = endpoint.connect(factory)
deferItem.addErrback(ConnectFailed)
netThread = threading.Thread(target=reactor.run, kwargs={"installSignalHandlers": False})
netThread.start()
reactor.callFromThread(ConnectToServer)
factory = ClientInterfaceFactory()
protocol = ClientInterfaceProtocol()
while 1:
time.sleep(0.01)
if connectionToServer == None: continue
if trySend == True:
protocol.ProccessTransmitJobToNode(None, None)
trySend = False
Is there something I am doing wrong?file is sent, it's when the write is multi part or there are more than one file it struggles.
If a single write occurs then the m
Note: I have updated the question with a crude piece of sample code in the hope it makes sense.

_BuildMessage returns a two-tuple: (msgs, '').
Your network code iterates over this:
msgs = self._BuildMessage(MsgID.ExportFileToMaster, params)
for m in msgs:
So your network code first tries to send a list of json encoded data and then tries to send the empty string. It most likely raises an exception because you cannot send a list of anything using sendLine. If you aren't seeing the exception, you've forgotten to enable logging. You should always enable logging so you can see any exceptions that occur.
Also, you're using time.sleep and you shouldn't do this in a Twisted-based program. If you're doing this to try to avoid overloading the receiver, you should use TCP's native backpressure instead by registering a producer which can receive pause and resume notifications. Regardless, time.sleep (and your loop over all the data) will block the entire reactor thread and prevent any progress from being made. The consequence is that most of the data will be buffered locally before being sent.
Also, your code calls LineReceiver.sendLine from a non-reactor thread. This has undefined results but you can probably count on it to not work.
This loop runs in the main thread:
while 1:
time.sleep(0.01)
if connectionToServer == None: continue
if trySend == True:
protocol.ProccessTransmitJobToNode(None, None)
trySend = False
while the reactor runs in another thread:
netThread = threading.Thread(target=reactor.run, kwargs={"installSignalHandlers": False})
netThread.start()
ProcessTransmitJobToNode simply calls self.sendLine:
def ProccessTransmitJobToNode(self, msg, connection):
rootDir = '../documentation/configs/Wooster'
exportedFiles = ['consoleLog.txt', 'blob.dat']
params = {
'Status' : 'buildStatus',
'TaskID' : 'taskID',
'Name' : 'taskName',
'Exports' : len(exportedFiles),
}
msg, statusStr = self._BuildMessage(101, params)
connection.sendLine(msg[0])
You should probably remove the use of threading entirely from the application. Time-based events are better managed using reactor.callLater (your main-thread loop effectively generates a call to ProcessTransmitJobToNode once hundred times a second (modulo effects of the trySend flag)).
You may also want to take a look at https://github.com/twisted/tubes as a better way to manage large amounts of data with Twisted.

Related

Determine which descriptor ID belongs to which client - QTcpSocket

I am creating an app where the server and clients run on the same machine, see picture.
I want the user to be able to send data from the server to a specific client (= specific window). For this, the user needs to know which ID belongs to which client (for example the corresponding ID could be displayed in each window's title).
Is it possible to get the corresponding descriptor ID on the client side? If not, how could I achieve the same result anyway?
I expect something like this as a result:
Here is an example code in pyside2 but I don't mind if the solution is using C++ qt.
QTCPServer:
import sys
from typing import List
from PySide2.QtCore import *
from PySide2.QtNetwork import *
from PySide2.QtWidgets import *
class MainWindow(QMainWindow):
new_message = Signal(bytes)
_connection_set: List[QTcpSocket] = []
def __init__(self):
super().__init__()
self.server = QTcpServer()
# layout
self.setWindowTitle("QTCPServer")
self._central_widget = QWidget()
self._main_layout = QVBoxLayout()
self.status_bar = QStatusBar()
self.text_browser_received_messages = QTextBrowser()
self._controller_layout = QHBoxLayout()
self.combobox_receiver = QComboBox()
self.lineEdit_message = QLineEdit()
self._controller_layout.addWidget(self.combobox_receiver)
self._controller_layout.addWidget(self.lineEdit_message)
self._buttons_layout = QHBoxLayout()
self.send_message_button = QPushButton("Send Message")
self.send_message_button.clicked.connect(self.send_message_button_clicked)
self._buttons_layout.addWidget(self.send_message_button)
# end layout
if self.server.listen(QHostAddress.Any, 8080):
self.new_message.connect(self.display_message)
self.server.newConnection.connect(self.new_connection)
self.status_bar.showMessage("Server is listening...")
else:
QMessageBox.critical(self, "QTCPServer", f"Unable to start the server: {self.server.errorString()}.")
self.server.close()
self.server.deleteLater()
sys.exit()
# set layout
self.setStatusBar(self.status_bar)
self.setCentralWidget(self._central_widget)
self._central_widget.setLayout(self._main_layout)
self._main_layout.addWidget(self.text_browser_received_messages)
self._main_layout.addLayout(self._controller_layout)
self._main_layout.addLayout(self._buttons_layout)
def new_connection(self) -> None:
while self.server.hasPendingConnections():
self.append_to_socket_list(self.server.nextPendingConnection())
def append_to_socket_list(self, socket: QTcpSocket):
self._connection_set.insert(len(self._connection_set), socket)
self.connect(socket, SIGNAL("readyRead()"), self.read_socket)
self.connect(socket, SIGNAL("disconnected()"), self.discard_socket)
self.combobox_receiver.addItem(str(socket.socketDescriptor()))
self.display_message(f"INFO :: Client with socket:{socket.socketDescriptor()} has just entered the room")
def read_socket(self):
socket: QTcpSocket = self.sender()
buffer = QByteArray()
socket_stream = QDataStream(socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
socket_stream.startTransaction()
socket_stream >> buffer
if not socket_stream.commitTransaction():
message = f"{socket.socketDescriptor()} :: Waiting for more data to come.."
self.new_message.emit(message)
return
header = buffer.mid(0, 128)
file_type = header.split(",")[0].split(":")[1]
buffer = buffer.mid(128)
if file_type == "message":
message = f"{socket.socketDescriptor()} :: {str(buffer, 'utf-8')}"
self.new_message.emit(message)
def discard_socket(self):
socket: QTcpSocket = self.sender()
it = self._connection_set.index(socket)
if it != len(self._connection_set):
self.display_message(f"INFO :: A client has just left the room")
del self._connection_set[it]
socket.deleteLater()
self.refresh_combobox()
def send_message_button_clicked(self):
receiver = self.combobox_receiver.currentText()
if receiver == "Broadcast":
for socket in self._connection_set:
self.send_message(socket)
else:
for socket in self._connection_set:
if socket.socketDescriptor() == int(receiver):
self.send_message(socket)
return
self.lineEdit_message.clear()
def send_message(self, socket: QTcpSocket):
if not socket:
QMessageBox.critical(self, "QTCPServer", "Not connected")
return
if not socket.isOpen():
QMessageBox.critical(self, "QTCPServer", "Socket doesn't seem to be opened")
return
string = self.lineEdit_message.text()
socket_stream = QDataStream(socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
header = QByteArray()
string_size = len(string.encode('utf-8'))
fstring = f"fileType:message,fileName:null,fileSize:{string_size}"
header.prepend(fstring.encode())
header.resize(128)
byte_array = QByteArray(string.encode())
byte_array.prepend(header)
socket_stream.setVersion(QDataStream.Qt_5_15)
socket_stream << byte_array
def display_message(self, string):
self.text_browser_received_messages.append(string)
def refresh_combobox(self):
self.combobox_receiver.clear()
self.combobox_receiver.addItem("Broadcast")
for socket in self._connection_set:
self.combobox_receiver.addItem(str(socket.socketDescriptor()))
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
QTCPClient
import sys
from PySide2.QtCore import *
from PySide2.QtNetwork import *
from PySide2.QtWidgets import *
class MainWindow(QMainWindow):
new_message = Signal(bytes)
def __init__(self):
super().__init__()
self.socket = QTcpSocket(self)
# layout
self.setWindowTitle("QTCPClient")
self._central_widget = QWidget()
self._main_layout = QVBoxLayout()
self.status_bar = QStatusBar()
self.text_browser_received_messages = QTextBrowser()
self._controller_layout = QHBoxLayout()
self.lineEdit_message = QLineEdit()
self._controller_layout.addWidget(self.lineEdit_message)
self._buttons_layout = QHBoxLayout()
self.send_message_button = QPushButton("Send Message")
self.send_message_button.clicked.connect(self.on_send_message_button_clicked)
self._buttons_layout.addWidget(self.send_message_button)
# end layout
self.new_message.connect(self.display_message)
self.connect(self.socket, SIGNAL("readyRead()"), self.read_socket)
self.connect(self.socket, SIGNAL("disconnected()"), self.discard_socket)
# set layout
self.setStatusBar(self.status_bar)
self.setCentralWidget(self._central_widget)
self._central_widget.setLayout(self._main_layout)
self._main_layout.addWidget(self.text_browser_received_messages)
self._main_layout.addLayout(self._controller_layout)
self._main_layout.addLayout(self._buttons_layout)
self.socket.connectToHost(QHostAddress.LocalHost, 8080)
if self.socket.waitForConnected():
self.status_bar.showMessage("Connected to Server")
else:
QMessageBox.critical(self, "QTCPClient", f"The following error occurred: {self.socket.errorString()}.")
if self.socket.isOpen():
self.socket.close()
sys.exit()
def discard_socket(self):
self.socket.deleteLater()
self.socket = None
self.status_bar.showMessage("Disconnected!")
def read_socket(self):
buffer = QByteArray()
socket_stream = QDataStream(self.socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
socket_stream.startTransaction()
socket_stream >> buffer
if not socket_stream.commitTransaction():
message = f"{self.socket.socketDescriptor()} :: Waiting for more data to come.."
self.new_message.emit(message)
return
header = buffer.mid(0, 128)
file_type = header.split(",")[0].split(":")[1]
buffer = buffer.mid(128)
if file_type == "message":
message = f"{self.socket.socketDescriptor()} :: {str(buffer, 'utf-8')}"
self.new_message.emit(message)
def on_send_message_button_clicked(self):
if not self.socket:
QMessageBox.critical(self, "QTCPServer", "Not connected")
return
if not self.socket.isOpen():
QMessageBox.critical(self, "QTCPServer", "Socket doesn't seem to be opened")
return
string = self.lineEdit_message.text()
socket_stream = QDataStream(self.socket)
socket_stream.setVersion(QDataStream.Qt_5_15)
header = QByteArray()
string_size = len(string.encode('utf-8'))
fstring = f"fileType:message,fileName:null,fileSize:{string_size}"
header.prepend(fstring.encode())
header.resize(128)
byte_array = QByteArray(string.encode())
byte_array.prepend(header)
socket_stream << byte_array
self.lineEdit_message.clear()
def display_message(self, string: str):
self.text_browser_received_messages.append(string)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
The socket descriptors are only valid for the constructor and they do not match on both sides.
One possibility is to automatically send a first "handshake" message to the client as soon as it's connected, the client will identify that message as a "descriptor id" type, and eventually set its window title.
In the following changes to your code, I'm using a simple fileType:descriptor header, and the descriptor id is actually sent as an integer value into the datastream. You can obviously use a string there, if you want to send any other value.
# server
def append_to_socket_list(self, socket: QTcpSocket):
# ...
descriptor = int(socket.socketDescriptor())
socket_stream = QDataStream(socket)
fstring = 'fileType:descriptor,fileName:null,fileSize:{},'.format(descriptor.bit_length())
header = QByteArray()
header.prepend(fstring.encode())
header.resize(128)
socket_stream << header
socket_stream.writeInt32(descriptor)
# client
def read_socket(self):
# ...
header = buffer.mid(0, 128)
fields = header.split(",")
file_type = fields[0].split(":")[1]
buffer = buffer.mid(128)
if file_type == "descriptor":
self.id = socket_stream.readInt32()
self.setWindowTitle("QTCPClient - id {}".format(self.id))
Some suggestions:
both signals have a bytes signature, but this is wrong as you're emitting those signals as str types; if you're not sure, you can use the basic object type;
the self.connect syntax is considered obsolete, use the "new" (well, not so new anymore) style one: object.signal.connect(slot); for instance:
self.socket.readyRead.connect(self.read_socket)
use QApplication.quit() instead of sys.exit(), so that the application properly does everything it needs before actually quitting the python interpreter;
instead of using the text value of the combo, you should use the user data:
descriptor = socket.socketDescriptor()
self.combobox_receiver.addItem(str(descriptor), descriptor)
then you can access it by using self.combobox_receiver.currentData() (you can add the "broadcast" item with a -1 value); you could even add the socket itself as user data;
to properly split the header without getting garbled results for the last field, you must add a final comma, otherwise split() will return the whole rest of the string;
Note for PyQt users: socketDescriptor() returns a sip.voidptr, to obtain the actual value use int(socket.socketDescriptor()).

Calling asynchronous function from dash

Once a single function needs to be awaited, it seems like a cascading of functions that need to be awaited, until you reach a point where adding await breaks stuff:
I have dash code that intermittently gets called:
def get_data(symbolStr):
print("Getting AR for", symbolStr)
df = amp.get_data_job(symbolStr) #calls async code and need to be awaited
return df
...
html.Div(
dcc.Interval(id='interval1', interval = 900 * 1000, n_intervals=0),
),
html.Div(
dash_table.DataTable(
id='table',
columns=[{"name": i, "id": i} for i in magic_columns],
editable=True,
row_deletable=True
),
)
#app.callback(
[dash.dependencies.Output('table', 'data')],
[dash.dependencies.Input('interval1', 'n_intervals')],
[dash.dependencies.State('table', 'data')]
)
def update_interval(n, data):
allSymbolsDf = pd.DataFrame()
for symbolStr in symbolsStr:
df = get_data(symbolStr)
allSymbolsDf = allSymbolsDf.append(df)
time.sleep(3)
return [allSymbolsDf.to_dict('rows')]
Thing is, get_data is a function that calls asynchronous code
df = amp.get_data_job(symbolStr)
I can't turn the callback update_interval into an asynchronous function (AFAIK). What is the recommended way out of this. I mean, still keeping dash.
Ran into the same situation using FastApi and serving dash on the side. Use asyncio.run()
import asyncio
async def asyn_fun(foo):
return True if foo == "bar" else False
def sync_fun(foo):
return asyncio.run(asyn_fun(foo))
print(sync_fun("bar"))

Unable to mqtt_client publish inside a class

I'm trying to use Paho MQTT Client and Multiprocessing to send temperature with defined interval. However, publish command is not working inside class. I've checked self.mqtt_client inside scheduler it has the Client object.
Is there anyone that can address problem for me?
Everything inside class is working except Scheduler.
def scheduler(self, topic, interval):
if interval != 0:
while True:
if topic == "temp":
print("Temperature published " + interval) #It's working.
self.mqtt_client.publish(topic, interval , 0 , False) #There is no error/output about this line
time.sleep(int(interval))
else:
pass
Class:
class Switcher:
config = None
mqtt_client = None
mqtt_connected = False
switches = {}
stages = {}
def __init__(self, config):
self.config = config
for switch_cfg in self.config['switches']:
self.switches[switch_cfg['topic_set']] = Switch(int(switch_cfg['gpio']), switch_cfg['topic_status'], switch_cfg['initial'])
def scheduler(self, topic, interval):
if interval != 0:
while True:
if topic == "temp":
print("Temperature published " + interval) #It's working.
self.mqtt_client.publish(topic, interval , 0 , False) #There is no error/output about this line
time.sleep(int(interval))
else:
pass
def mqtt_connect(self):
if self.mqtt_broker_reachable():
self.verbose('Connecting to ' + self.config['mqtt_host'] + ':' + self.config['mqtt_port'])
self.mqtt_client = mqtt.Client(self.config['mqtt_client_id'])
if 'mqtt_user' in self.config and 'mqtt_password' in self.config:
self.mqtt_client.username_pw_set(self.config['mqtt_user'], self.config['mqtt_password'])
self.mqtt_client.on_connect = self.mqtt_on_connect
self.mqtt_client.on_disconnect = self.mqtt_on_disconnect
self.mqtt_client.on_message = self.mqtt_on_message
try:
self.mqtt_client.connect(self.config['mqtt_host'], int(self.config['mqtt_port']), 10)
for switch_cfg in self.config['switches']:
self.mqtt_client.subscribe(switch_cfg['topic_set'], 0)
self.mqtt_client.loop_forever()
except:
self.error(traceback.format_exc())
self.mqtt_client = None
else:
self.error(self.config['mqtt_host'] + ':' + self.config['mqtt_port'] + ' not reachable!')
def mqtt_on_connect(self, mqtt_client, userdata, flags, rc):
self.mqtt_connected = True
for switch_ios in self.config['switches']:
self.mqtt_client.publish(self.config['station_status'], "available", 0, False)
self.mqtt_client.publish(switch_ios['topic_status'], self.switches[switch_ios['topic_set']].get_state(), 0, False)
temp_interval = 1
temp_process = multiprocessing.Process(target=self.scheduler, args=("temp",str(temp_interval),))
temp_process.start()
self.verbose('...mqtt_connected!')
def mqtt_broker_reachable(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.settimeout(5)
try:
s.connect((self.config['mqtt_host'], int(self.config['mqtt_port'])))
s.close()
return True
except socket.error:
return False
def start(self):
self.mqtt_connect()
You mqtt_connect function will never return.
self.mqtt_client.loop_forever() Will block until self.mqtt_client.disconnect() is called.
You should probably be using self.mqtt_client.loop_start() which will run the client loop on it's own thread in the background. You can call self.mqtt_client.loop_stop() when you want to shut the client down.

Error while closing the python serial port

" i am trying to read data from the serial connection and doing some stuff if it matches my string but its giving me errors when i close the serial connection port"
" for some reason i do not see this error if i use the serial.readline() method "
import time
import serial
from Queue import Queue
from threading import Thread
class NonBlocking:
def __init__(self, serial_connection, radio_serial_connection):
self._s = serial_connection
self._q = Queue()
self.buf = bytearray()
def _populateQueue(serial_connection, queue):
if type(serial_connection) == str:
return
self.s = serial_connection
while True:
i = self.buf.find(b"\n")
if i >= 0:
r = self.buf[:i + 1]
self.buf = self.buf[i + 1:]
queue.put(r)
while True:
i = max(1, min(2048, self.s.in_waiting))
data = self.s.read(i)
i = data.find(b"\n")
if i >= 0:
r = self.buf + data[:i + 1]
self.buf[0:] = data[i + 1:]
a = r.split('\r\n')
for item in a:
if item:
queue.put(item)
else:
self.buf.extend(data)
self._t = Thread(target=_populateQueue, args=(self._s, self._q))
self._t.daemon = True
self._t.start()
def read_all(self, timeout=None):
data = list()
if self._q.empty():
pass
while not self._q.empty():
data.append(self._q.get(block=timeout is not None, timeout=timeout))
return data
class SerialCommands:
def __init__(self, port, baudrate):
self.serial_connection = serial.Serial(port, baudrate)
self.queue_data = NonBlocking(self.serial_connection, '')
def read_data(self):
returned_info = self.queue_data.read_all()
return returned_info
def close_q(self):
self.serial_connection.close()
class qLibrary:
def __init__(self):
self.q = None
self.port = None
def close_q_connection(self):
self.q.close_q()
def establish_connection_to_q(self, port, baudrate=115200, delay=2):
self.delay = int(delay)
self.port = port
try:
if not self.q:
self.q = SerialCommands(self.port, int(baudrate))
except IOError:
raise AssertionError('Unable to open {0}'.format(port))
def verify_event(self, data, timeout=5):
timeout = int(timeout)
data = str(data)
# print data
while timeout:
try:
to_analyze = self.q.read_data()
for item in to_analyze:
print "item: ", item
if str(item).find(str(data)) > -1:
print "Found data: '{0}' in string: '{1}'".format(data, item)
except:
pass
time.sleep(1)
timeout -= 1
if __name__ == '__main__':
q1 = qLibrary()
q1.establish_connection_to_q('COM5')
q1.verify_event("ATE")
q1.close_q_connection()
" i expect the code to close the serial connection without any exceptions or errors "
the output is
Exception in thread Thread-1:
Traceback (most recent call last):
File "C:\Python27\Lib\threading.py", line 801, in __bootstrap_inner
self.run()
File "C:\Python27\Lib\threading.py", line 754, in run
self.__target(*self.__args, **self.__kwargs)
File "C:/Program Files (x86)/serialtest1.py", >line 27, in _populateQueue
data = self.s.read(i)
File "C:\Program Files (x86)\venv\lib\site->packages\serial\serialwin32.py", line 283, in read
ctypes.byref(self._overlapped_read))
TypeError: byref() argument must be a ctypes instance, not 'NoneType'
If you define your serial port with no timeout it will get the default setting timeout=None which means when you call serial.read(x) the code will block until you read x bytes.
If you never get those x bytes your code will get stuck in there waiting forever, or at least until you receive more data on the buffer to get the total number of bytes received equal to x.
If you mix that up with threading, I'm afraid you are quite likely closing the port while you are trying to read.
You can probably fix this issue just defining a sensible read timeout on your port or changing the way you read. The general advice is to set a timeout that works for your application and read at least the maximum number of bytes you expect. Reading your code, that seems to be what you wanted to do. If so, you forgot to set the timeout.
If you have a reason not to set a timeout or you want to keep your reading routine as it is, you can make your code work if you cancel reading before closing. You can do that with serial.cancel_read()

Can`t receive GRPC stream

I am trying to receive stream from some GRPC server, code of which is not accessible to me.
Here is the code of my GRPC client:
class Client():
def __init__ (self, grpcIP, grpcPort, params):
self.channel = grpc.insecure_channel('%s:%d' % (grpcIP, grpcPort))
grpc.channel_ready_future(self.channel).result()
self.stub = pb2_grpc.DataCloneStub(self.channel)
self.params = params
self.host = grpcIP
def StartSender(self):
params = pb2.StartParameters(**self.params)
try:
res = self.stub.Start(params)
print(type(res))
for pr in res:
print(pr.current_progress)
except grpc.RpcError as e:
print(e)
Here are snippets from proto file that is used.
Method:
rpc Start (StartParameters) returns (stream Progress) {}
Message in stream:
message Progress {
double current_progress = 1;
uint64 total_sent = 2;
uint64 total_size = 3;
string file_name = 4;
Error error = 5;
}
As I understand self.stub.Start(params) should return iterator with objects of type Progress. The problem is that it returns something with type grpc._channel._Rendezvous. I can`t iterate through the response. It doesn`t catch any exceptions either.
Did someone experience such behavior? Is it possible that issue comes not from client side?
Class grpc._channel._Rendezvous is also an iterator, you can check the streaming RPC example here.
The code you posted is correct implementation. If the program blocked at the for pr in res line, it is possible that the server is not sending any response (or taking a long time), hence the client side is blocked.