-
Notifications
You must be signed in to change notification settings - Fork 1k
Check received data length #288
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
If an incomplete response is received strange things happened (e.g. receiving 0xFF raise an IndexError, receiving 0x011012 raise a struct.error). Now if an incomplete response is received an error is logged. Related to discussion in pymodbus-dev#188 , Fix pymodbus-dev#211
socket.recv(size) waits until it gets some data from the host but not necessarily the entire response that can be fragmented in many packets. To avoid the splitted responses to be recognized as invalid messages and to be discarded, loops socket.recv until full data is received, or timeout is expired.
|
@ccatterina thanks for the PR, I will take a look . |
| h_size = self.client.framer._hsize | ||
| length = struct.unpack(">H", read_min[4:6])[0] - 1 | ||
| expected_response_length = h_size + length | ||
| expected_response_length -= min_size |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would throw TypeError (None - min_size) for messages with out the get_response_pdu_size method defined (for e.g. all mei_messages and diagnostic messages). You can run synchronous_client_ext.py under examples\common to check this behaviour.
| "to 'WAITING FOR REPLY'") | ||
| self.client.state = ModbusTransactionState.WAITING_FOR_REPLY | ||
| result = self._recv(response_length or 1024, full) | ||
| result = self._recv(response_length, full) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can not pass None as response_length, in cases where a transaction returns no response, the argument full would be set to true and subsequent transactions would try to read the full length (None in some cases) throwing an error. Can we have 1024 back here ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have made a wrong assumption, that is all messages have an expected_response_length.
If i get the 1024 back, the client._recv function will wait until it receives 1024 bytes or the timeout expires, so probably it always waits until the timeout expires, making the communication very slow.
Another solution should be to make the client._recv(size) to return the data as soon as at least 1 byte is received, without waits that timeout expires when size=None is passed.
What do you think about it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In most of the normal cases (read/write registers/coils) expected_length is always calculated so it should not be an issue, for some diagnostic or mei requests the response length is dynamic and hence we could choose to read 1024 bytes till time out ,which is still OK with me.
|
@ccatterina I see that your changes are applicable only for Tcp client , do you plan to have these changes for serial client as well ? |
Are you referring to the changes of ModbusTcpClient and ModbusUdpClient _recv method? Anyway, as you already noticed i've not considered the messages for which there isn't an expected size. |
|
@ccatterina Thanks for the clarification. Looking forward to merge this. |
|
@ccatterina I did some work based on your branch here, please give a look when you get time. For UDP, there are high possibility that the data is lost if we try to read data in a loop, so I am sticking to 1024 size (default), for serial interaface ,I am now checking for the bytes available in the read buffer and reading. I am sure that needs further tuning in cases where the slave responds a bit slow. |
Thank you for your effort, now i give a look to the branch.
I'm missing something, what is the difference between TCP and UDP that cause this problem?
I think not, the |
|
|
@ccatterina thanks for the review comment. |
|
@ccatterina re. UDP, UDP protocol does not handle error checks , the packets are just sent to the recipients and will not wait for the recipient actually received the packet, if we miss the packet its gone where as with tcp, the packets sent are tracked and ensures that recipient actually receives those packets. So, with UDP if try to read in loop, there are high possibility that the sender sends all but those packets are not lost in the next iterations. |
Yes, sorry my bad, but if we receive, for example, 1 byte, it will raise a |
Ok, now i get it, maybe the best solution is still to wait for 1024 characters if there isn't an expected response length. |
|
@ccatterina I have modified the logic a little to handle the slow reception of message. Let me know if that looks ok. |
|
@dhoomakethu yeah, it looks fine to me, waiting until there is something in the buffer should solve the problem. |
|
Maybe you can also replace if size is not None:
 while len(data) < size:
 try:
 data += self.socket.recv(size - len(data))
 except socket.error:
 time.sleep(0.01)
 if not self.timeout or (time.time() - begin > self.timeout):
 break |
|
@dhoomakethu Anyway i'm testing your branch in one of our PV plants and it seems to work, now is running for an hour without crash. (With the v1.5.0 and previous it crashed after few minutes) |
|
@ccatterina thanks for the confirmation. I did pushed some more changes to better handle some corner cases. I am testing locally as well and will release an official version by end of this week |
|
sorry @dhoomakethu, have you seen my comment?
What you think about it? Am i wrong? |
|
@ccatterina I think it is already taken care here, if we are not getting the minimum data , we raising exception. |
|
@dhoomakethu yes, but there isn't that code in your branch. I thought that your branch replace my pull request, isn'it? |
|
That's correct, I will have a look.
Thanks
ಗುರು, ಏಪ್ರಿ 26, 2018 9:28 ಅಪರಾಹ್ನ ದಿನಾಂಕದಂದು Claudio Catterina <
notifications@github.com> ಅವರು ಬರೆದಿದ್ದಾರೆ:
… @dhoomakethu <https://github.com/dhoomakethu> yes, but there isn't that
code in your branch <https://github.com/riptideio/pymodbus/tree/patch-1>.
I thought that your branch replace my pull request, isn'it?
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#288 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AI2OLhP9lQT4tqr60BRo9XuDkH3dynFEks5tse65gaJpZM4TZv2_>
.
|
|
👍 I close the pull request. |
Hi @dhoomakethu ,
I made this PR mostly to solve the problem discussed in #188, this would fix that issue.
In the
_recvfunction, theTransactionManagercould receive an incomplete message like0xFF,0x10or0x040101causing the raise of several exceptions because the actual length of the received message wasn't checked.In addition i change the way that a message is received through a socket connection, so that
recv(size)waits until full data is received, or timeout is expired. (That is consistent withread(size)function of serial client)Let me know what you think.
Thanks