How to create simple server-client communication? The simple example doesn't communicate and the network example adds a bunch of feature, also I can't find when ListFilesResponse_callback() is called

148 views
Skip to first unread message

gaetan Kelabra

unread,
Apr 28, 2023, 5:01:43 AM4/28/23
to nanopb
Hello (Petteri),

I am trying to make a simple project that sends an int to the server and the server does an operation on that int (ex: the modulo 2 [%2]) and returns the answer as an int.
I am basically trying to make something in between the simple example and the network example.

The issue I am having is that the simple example doesn't have a server and client and the network example add some extra features (looking into directories, getting paths etc) which makes it harder to break down, also I can't find when the ListFilesResponse_callback is called.

Once I manage it I would be happy to publish it (and maybe even add it to your project if you find it useful).

Environment :
Raspberry 3 model B+
OS Raspbian 10.2.1-6+rpi1
Gcc 10.2.1
Cmake 3.18.4
nanopb nanopb-0.4.7

What I have tried :
Create a my own "simple" example sending various types of data. Done
Create my own server-client example (network example). Failed
Break down the functions of the network example. Failed
Figure out when and where is the ListFilesResponse_callback called. Failed. You commented that it is called in the client when receiving a filename from the server and in the server during encoding.
Also, it is defined as NULL in the fileproto.pb.h, defined once in the client.c and defined once again in the server.c
I read the common.h, there is a write_callback function and a read one but I can't see them called in the client or server files either.
I commented out the ListFilesResponse_callback functions and tried to do "make", I got this in the terminal:

../../generator/protoc  --nanopb_out=. fileproto.proto
cc -ansi -Wall -Werror -g -O0 -I../.. -o server server.c common.c fileproto.pb.c ../../pb_encode.c ../../pb_decode.c ../../pb_common.c
/usr/bin/ld: /tmp/ccD7pSxp.o:(.rodata+0x84): undefined reference to `ListFilesResponse_callback'
collect2: error: ld returned 1 exit status
make: *** [Makefile:16: server] Error 1
rm fileproto.pb.c


I get /usr/bin/ld: /tmp/ccD7pSxp.o:(.rodata+0x84): undefined reference to ListFilesResponse_callback' which I think means my linker doesn't find the definition for the function (can't it just be defined as NULL like in the .pb.c file).
The second interesting thing is make: *** [Makefile:16: server] Error 1 and line 16 of the makefile is $(CC) $(CFLAGS) -o $@ $^ $(NANOPB_CORE)

l have tried to find other examples or projects in nanopb but got no luck.

Questions / How to help me :
Where is called the ListFilesResponse_callback function ?
How can I make a simple server-client project sending just an int ?
Do you know online projects that I could use to learn ?
Is there issues on github or questions in this group that could help me ? (I don't want to take too much of your time.)


Thanks for your work.

Regards

Petteri Aimonen

unread,
Apr 28, 2023, 1:29:02 PM4/28/23
to nan...@googlegroups.com
Hi,

> The issue I am having is that the simple example doesn't have a server and
> client and the network example add some extra features (looking into
> directories, getting paths etc) which makes it harder to break down, also I
> can't find when the ListFilesResponse_callback is called.

The callback is name-bound, i.e. it is called directly by nanopb when encoding
or decoding.
https://jpa.kapsi.fi/nanopb/docs/whats_new.html#callbacks-bound-by-function-name
https://jpa.kapsi.fi/nanopb/docs/concepts.html#function-name-bound-callbacks

> Also, it is defined as NULL in the fileproto.pb.h, defined once in the
> client.c and defined once again in the server.c

The fileproto.pb.h should have definition such as:

extern bool ListFilesResponse_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
#define ListFilesResponse_CALLBACK ListFilesResponse_callback

The first line is a function declaration, meaning the body should be in your own code.
The second line is a macro used to find the callback function by the generated code.

--

Internal details that you probably don't need to know:

The actual call happens here:
https://github.com/nanopb/nanopb/blob/master/pb_decode.c#L767

The function is called through a function pointer defined in struct pb_msgdesc_t:
https://github.com/nanopb/nanopb/blob/master/pb.h#L324

The horrible/fancy macro construction in PB_BIND puts the function pointer in the struct:
https://github.com/nanopb/nanopb/blob/master/pb.h#L516

--
Petteri

gaetan Kelabra

unread,
Apr 30, 2023, 12:27:39 PM4/30/23
to nanopb
Thank you for your answer Petteri.

I will give you a better answer once I spend more time on it.

I did find those 2 lines in the fileproto.pb.c:
extern bool ListFilesResponse_callback(pb_istream_t *istream, pb_ostream_t *ostream, const pb_field_t *field);
#define ListFilesResponse_CALLBACK ListFilesResponse_callback

I will take a look at the links.

Thank you.

Regards

gaetan Kelabra

unread,
May 3, 2023, 5:05:43 AM5/3/23
to nanopb
Hello Petteri,

Thanks to your answer I was able to understand what callbacks are used for, basiocally they are mandatory whenwe don't know the size of the message to be sent (ex: strings, bytes or continuous stream of data).

What I have done :
Since I just want to send ints, I used the main from the server and the client of the network example but I used to same principle as in the simple example (allocate space, create and init stream, set value, encode).
I was able to make a client send an int to a server, I attached the code bellow.
I am now trying to send back to the client the modulo 2 of that number.

Questions regarding the project I am trying to make :
1) Making a sendAnswer function that is called at then end of the handle_connection() function in the server file is easy. I follow the same principle : allocate space, create stream, set vale, encode).
However, if I create a function that decodes the answer from the server (handleAnswer() ) how is the client supposed know when that function is called? Is it supposed to listen to the port as the server does in its main inside an infinite loop?

2) Furthermore, if I want to make a second request type (ex: send 2 ints to receive the modulo 2 of the multiplication of those 2 numbers as an answer), it is easy to do the proper request on the client side, but how is the server supposed to know which one he will receive? Since we allocate space such as : "requestType2 request = requestType1_init_zero" whens ending,  hwo will server know to allocate space to receive such as : "answerType2 answer = {}" instead of "answerType1 answer = {}"?

General questions about nanopb :
I went throught the common.f and common.c file in the network example.
3) The simple exmaple uses buffer while the network example doesn't and just uses pb_ostream_from_socket. Is a buffer just used when we don't went to send over the network since we need to send through the socket?
4) Why isn't common.c and .h included in the pb_common? It seems to me that pb_ostream_from_socket is mandatory the send through the network since this function uses send() that is (I think) native to C and allow to send through TCP (or something similar), therefore, it is mandatory to use pb_ostream_from_socket to send through the network and pb_ostream_from_buffer is just used to store data inside a buffer.
5) Why does it works to call pb_ostream_from_socket() and only then calling pb_encode()? Since the pb_ostream_from_socket() function call send() the information should be sent even before encoding happens.

Thanks again for taking the time to help people interested in your project!

Regards
common.h
fileproto.pb.h
serverCopy
clientCopy.c
serverCopy.c
Makefile
fileproto.proto
clientCopy
common.c

Petteri Aimonen

unread,
May 3, 2023, 5:21:33 AM5/3/23
to nan...@googlegroups.com
Hi,

That's a lot of questions, I'll answer sparsely.
You'll need to research more yourself.

> 1) Making a sendAnswer function that is called at then end of the
> Is it supposed to listen to the port as the server does in its main
> inside an infinite loop?

Depends totally on what kind of TCP server/client design you want.

> 2) Furthermore, if I want to make a second request type (ex: send 2 ints to
> ending, hwo will server know to allocate space to receive such as :
> "answerType2 answer = {}" instead of "answerType1 answer = {}"?

Typical solution is to put all different message types inside a one common
message with "oneof". Alternative way is to first send an integer that identifies
message type.

> 3) The simple exmaple uses buffer while the network example doesn't and
> just uses pb_ostream_from_socket. Is a buffer just used when we don't went
> to send over the network since we need to send through the socket?

The socket callbacks are used in the example to demonstrate that with callbacks,
space for whole message doesn't have to be allocated. A buffer can be used instead
if you have enough RAM.

> 4) Why isn't common.c and .h included in the pb_common? It seems to me that
> pb_ostream_from_socket is mandatory the send through the network since this
> function uses send() that is (I think) native to C and allow to send

send() is only available on unix-based platforms, not all platforms nanopb targets.

> 5) Why does it works to call pb_ostream_from_socket() and only then calling
> pb_encode()? Since the pb_ostream_from_socket() function call send() the
> information should be sent even before encoding happens.

send() is called from the callback.

--
Petteri

gaetan Kelabra

unread,
May 3, 2023, 5:34:00 AM5/3/23
to nanopb
Thank you for your answers Petteri, I will look into that!

Regards,

Gaëtan.
Reply all
Reply to author
Forward
0 new messages