Skip to content
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

feature/server-client-protocol-version-2 #1072

Merged
merged 104 commits into from
Oct 13, 2023

Conversation

rex-schilasky
Copy link
Contributor

@rex-schilasky rex-schilasky commented Apr 18, 2023

Pull request type

Please check the type of change your PR introduces:

  • Bugfix

What is the current behavior?
See issue #907 and pull request #918.

Fixes #905
Fixes #907

What is the new behavior?
Different client/server protocol versions introduced based on the work of @floroeske.

  1. Client and server exchange their protocol version via registration layer.
  2. Server will not start before any client registration is received.
  3. Server starts with clients protocol version (currently 0 or 1).
  4. A server can handle multiple clients with different protocol version.
  5. A client can be connected to multiple server with different protocol versions.

Does this introduce a breaking change?

  • Yes

Public API changed /include/ecal/ecal_callback.h.

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

std::string request_;
std::string response_;
std::vector<char> packed_response_;

enum { max_length = 64 * 1024 };
char data_[max_length];
char data_[max_length];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not declare C-style arrays, use std::array<> instead [cppcoreguidelines-avoid-c-arrays]

  char                   data_[max_length];
  ^

std::string request_;
std::string response_;
std::vector<char> packed_response_;

enum { max_length = 64 * 1024 };
char data_[max_length];
char data_[max_length];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use default member initializer for 'data_' [modernize-use-default-member-init]

ecal/core/src/service/asio_server.h:44:

-     : socket_(io_service), data_{}
+     : socket_(io_service), 
Suggested change
char data_[max_length];
char data_[max_length]{};

@@ -236,7 +357,7 @@ class CAsioServer
delete new_session;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead [cppcoreguidelines-owning-memory]

      delete new_session;
      ^
Additional context

ecal/core/src/service/asio_server.h:338: variable declared here

  void handle_accept(CAsioSession* new_session, unsigned int version,
                     ^

return true;
}

std::vector<char> CTcpClient::PackRequest(const std::string& request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: method 'PackRequest' can be made static [readability-convert-member-functions-to-static]

ecal/core/src/service/ecal_tcpclient.h:106:

-     std::vector<char> PackRequest(const std::string &request);
+     static std::vector<char> PackRequest(const std::string &request);

@rex-schilasky rex-schilasky mentioned this pull request Apr 19, 2023
9 tasks
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

@@ -43,7 +43,7 @@ namespace eCAL
{
public:
CMonitoringImpl();
~CMonitoringImpl();
~CMonitoringImpl() = default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: annotate this function with 'override' or (rarely) 'final' [cppcoreguidelines-explicit-virtual-functions]

Suggested change
~CMonitoringImpl() = default;
~CMonitoringImpl() override = default;

std::string request_;
std::string response_;
std::vector<char> packed_response_;

enum { max_length = 64 * 1024 };
char data_[max_length];
char data_[max_length];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use default member initializer for 'data_' [modernize-use-default-member-init]

ecal/core/src/service/asio_server.h:45:

-     : socket_(io_service), data_{}
+     : socket_(io_service), 
Suggested change
char data_[max_length];
char data_[max_length]{};

@@ -236,7 +358,7 @@ class CAsioServer
delete new_session;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead [cppcoreguidelines-owning-memory]

      delete new_session;
      ^
Additional context

ecal/core/src/service/asio_server.h:339: variable declared here

  void handle_accept(CAsioSession* new_session, unsigned int version,
                     ^

{
request_ += data.substr(bytes_used, bytes_transferred - bytes_used);
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider a relatively rare case, suppose pack_1 and pack_2 are sent in rapid succession on the tcpclient side. If pack_1 is internally split for some reason, and then resend part of the internally split packets (tcp automatic retransmission), then the new packet actually contains the second half of pack_1 and the first half of pack_2, the current code implementation does not do anything to the first half of pack_2.
Of course, due to the nodelay socket option enabled on the sender side, this makes it difficult to reproduce the situation, but there is still a low probability of this problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume by pack_1 and pack_2 you mean two separate requests sent by the same client using the same TCP connection. I guess you are right it could happen, but I think only when you start two requests in parallel using two threads. I think if you send two requests from the same thread using the same client it would be processed sequentially. The first client call would block until it received the response from the server.

I think a more likely problem could be that for some reason not all data comes through and the server will wait forever to receive a complete payload. Then a new request might complete the payload receiving and corrupt the data.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thank you for your review - GREAT. I will check all in detail later.

Copy link
Contributor

@floroeske floroeske left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, I'll try it out in the next few days.

{
request_ += data.substr(bytes_used, bytes_transferred - bytes_used);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume by pack_1 and pack_2 you mean two separate requests sent by the same client using the same TCP connection. I guess you are right it could happen, but I think only when you start two requests in parallel using two threads. I think if you send two requests from the same thread using the same client it would be processed sequentially. The first client call would block until it received the response from the server.

I think a more likely problem could be that for some reason not all data comes through and the server will wait forever to receive a complete payload. Then a new request might complete the payload receiving and corrupt the data.

if (header_.size() == sizeof(eCAL::STcpHeader) && header_request_size_ == 0)
{
eCAL::STcpHeader tcp_header;
memcpy(&tcp_header, header_.data(), sizeof(eCAL::STcpHeader));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should add a method to decode and check if the header is valid. e.g. version field in the header and maybe a checksum. If it's not valid cancel here and restart. The is a possibility that we can receive a rubbish header resulting in a wrong or very large payload size.

}
else
{
if ((ec == asio::error::eof) ||
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One thing I couldn't get working successfully was adding timeouts to asio. As far as I understand this just handles connection loss, but not timeouts. Theoretically the server could hang there forever trying to receive a full payload. CTcpClient::ReceiveResponse has a timeout handler though I haven't tested it.

@@ -241,7 +316,7 @@ namespace eCAL
// read stream header (sync)
else
{
size_t bytes_read = m_socket->read_some(asio::buffer(&tcp_header, sizeof(tcp_header)));
size_t const bytes_read = m_socket->read_some(asio::buffer(&tcp_header, sizeof(tcp_header)));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe same thing here. Validate header and check if psize_n makes sense.

return true;
}

std::vector<char> CTcpClient::PackRequest(const std::string& request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if PackRequest and PackResponse should live in core/src/service/ecal_tcpheader.h. Maybe one PackHeader() function would be enough.

@KerstinKeller KerstinKeller added this to the eCAL 5.12 milestone Apr 21, 2023
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

ecal/core/src/ecal_def.h Outdated Show resolved Hide resolved
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
@floroeske
Copy link
Contributor

I've been running this branch for the last week on some of our devices and I haven't found any issues so far. I haven't tested the protocol backwards compatibility.

2 info logs changed to severity level debug2
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

std::string request_;
std::string response_;
std::vector<char> packed_response_;

enum { max_length = 64 * 1024 };
char data_[max_length];
char data_[max_length]{};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: do not declare C-style arrays, use std::array<> instead [cppcoreguidelines-avoid-c-arrays]

  char                   data_[max_length]{};
  ^

@@ -236,7 +359,7 @@ class CAsioServer
delete new_session;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: deleting a pointer through a type that is not marked 'gsl::owner<>'; consider using a smart pointer instead [cppcoreguidelines-owning-memory]

      delete new_session;
      ^
Additional context

ecal/core/src/service/asio_server.h:340: variable declared here

  void handle_accept(CAsioSession* new_session, unsigned int version,
                     ^

@FlorianReimold FlorianReimold marked this pull request as draft May 5, 2023 06:15
@FlorianReimold FlorianReimold marked this pull request as ready for review September 20, 2023 06:03
@FlorianReimold
Copy link
Member

I am finally handing this over to anybody who would like to review the changes 😉

@FlorianReimold FlorianReimold merged commit 5ca0c8a into master Oct 13, 2023
14 checks passed
@FlorianReimold FlorianReimold deleted the feature/server-client-protocol-version-2 branch November 8, 2023 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unreliable RPC calls with large messages Q: How is reading from a Service TCP Socket handled in eCAL?
6 participants