Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: Socket client thread design problem

by BrowserUk (Patriarch)
on Mar 20, 2014 at 15:45 UTC ( [id://1079093]=note: print w/replies, xml ) Need Help??


in reply to Socket client thread design problem

So any advise on realizing this behavior without running into all this trouble created by the differences in the threading model of C and Perl?

Advise: stop trying to emulate the C code. There are many programming techniques used in C that are either suboptimal or impossible to realise in Perl. There is always a better Perlish solution.

But you have to step back from the C implementation and look at the required functionality, not how that functionality is achieved in C.

If you would describe the program at a higher level I might be able to suggest something. I'm really intrigued by the idea of a C program running user provided callback code.

(From the description so far, it sounds like your trying to implement a multi-server IRC client with Bot logic; but that's just a guess.)


With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
"Science is about questioning the status quo. Questioning authority".
In the absence of evidence, opinion is indistinguishable from prejudice.
  • Comment on Re: Socket client thread design problem

Replies are listed 'Best First'.
Re^2: Socket client thread design problem
by photron (Novice) on Mar 21, 2014 at 09:37 UTC

    Not IRC related at all. It's hardware control over TCP/IP. Here's a example using the C code. There is an IPConnection object that handles all the TCP/IP stuff. Then there is a Temperature object that represents a temperature sensor. To interact with the temperature sensor the example creates an IPConnection object and a Temperature. The Temperature object will then use the IPConnection for its communication needs.

    Getter Functions

    Now to read the temperature of the sensor you just call temperature_get_temperature. This internally sends a request to the hardware asking for the current temperature. Then the sensor sends a response back containing the current temperature. During this message exchange the temperature_get_temperature blocks while waiting for a response. There is also a timeout mechanism. If the sensor doesn't respond within 2.5 sec then temperature_get_temperature will report an error.

    #include <stdio.h> #include "ip_connection.h" #include "bricklet_temperature.h" #define HOST "localhost" #define PORT 4223 #define UID "XYZ" // Change to your UID int main() { // Create IP connection IPConnection ipcon; ipcon_create(&ipcon); // Create device object Temperature t; temperature_create(&t, UID, &ipcon); // Connect to brickd if(ipcon_connect(&ipcon, HOST, PORT) < 0) { fprintf(stderr, "Could not connect\n"); exit(1); } // Don't use device before ipcon is connected // Get current temperature (unit is °C/100) int16_t temperature; if(temperature_get_temperature(&t, &temperature) < 0) { fprintf(stderr, "Could not get value, probably timeout\n"); exit(1); } printf("Temperature: %f °C\n", temperature/100.0); printf("Press key to exit\n"); getchar(); ipcon_destroy(&ipcon); // Calls ipcon_disconnect internally }

    Just this getter mechanism alone would work without any threads, you only have to receive data during a getter call. If no getter is called no data will arrive from the hardware.

    There seems to be a post size limit, so to be continued ...

Re^2: Socket client thread design problem
by photron (Novice) on Mar 21, 2014 at 09:40 UTC

    Continuation ...

    Callbacks

    But there is more. You can tell the sensor to inform you about temperature changes without you having to call the get function over and over again. For this the callback function cb_temperature is registered using temperature_register_callback. It will automatically be called if a temperature callback from the temperature sensor is received.

    #include <stdio.h> #include "ip_connection.h" #include "bricklet_temperature.h" #define HOST "localhost" #define PORT 4223 #define UID "XYZ" // Change to your UID // Callback function for temperature callback // (parameter has unit °C/100) void cb_temperature(int16_t temperature, void *user_data) { (void)user_data; // avoid unused parameter warning printf("Temperature: %f °C.\n", temperature/100.0); } int main() { // Create IP connection IPConnection ipcon; ipcon_create(&ipcon); // Create device object Temperature t; temperature_create(&t, UID, &ipcon); // Connect to brickd if(ipcon_connect(&ipcon, HOST, PORT) < 0) { fprintf(stderr, "Could not connect\n"); exit(1); } // Don't use device before ipcon is connected // Set Period for temperature callback to 1s (1000ms) // Note: The callback is only called every second if the // temperature has changed since the last call! temperature_set_temperature_callback_period(&t, 1000); // Register temperature callback to function cb_temperature temperature_register_callback(&t, TEMPERATURE_CALLBACK_TEMPERATURE, (void *)cb_temperature, NULL); printf("Press key to exit\n"); getchar(); ipcon_destroy(&ipcon); // Calls ipcon_disconnect internally }

    Now someone has to receive incoming data from the socket even if no getter call is currently in progress. In the C code there is the receive thread of the IPConnection that continuously handles incoming data. If a response for a getter call arrives then the receive thread check if any device object such as the Temperature object is currently wating for a response. If yes then the response is passed to the waiting getter call via a queue. If a callback is received, then it is passed over to a second thread via another queue, the callback thread of the IPConnection.

    The callback thread takes a callback packet from its queue and checks if any of the device objects has a callback function registered for it. If yes then it calls it with the data from the callback packet. The callbacks cannot be called from the receive thread because then the user provided getter function would not be able to call another getter function of the device. As the receive thread is currently blocked by the callback call it cannot receive the incoming response for the getter call.

    But there is even more. If a disconnect of the socket is detected then the receive thread is stopped and the callback thread is told to reconnect. Once the connection is established again the receive thread start receiving incoming data again.

    There seems to be post size limit, so to be continued ...

Re^2: Socket client thread design problem
by photron (Novice) on Mar 21, 2014 at 10:22 UTC

    Continuation ...

    Summary

    So what the C code does is allow to call getter functions and setter functions (just like getters but without a response) to talk to hardware using a TCP/IP connection. The getters can timeout. You can also register for callbacks to be notified by the hardware without having to poll for it. Finally the IPConnection automatically takes care of reestablishing the TCP/IP connection if it gets lost.

    The C code does all this by using two threads and sharing a socket between the receive thread the callback thread and the main thread of the program. Obviously this doesn't work 1:1 in Perl due to the different threading model. And I ran into some trouble there already. But the goal is to get the same features in Perl.

    For comparison here is the current temperature getter example ( https://raw.githubusercontent.com/Tinkerforge/temperature-bricklet/master/software/examples/perl/example_simple.pl ) and the temperature callback example ( https://raw.githubusercontent.com/Tinkerforge/temperature-bricklet/master/software/examples/perl/example_callback.pl ) in Perl.

    Here's the full C code ( http://download.tinkerforge.com/bindings/c/tinkerforge_c_bindings_2_0_13.zip ) and the current Perl code ( http://download.tinkerforge.com/bindings/perl/tinkerforge_perl_bindings_2_0_1.zip ). Note that I'm currently working one Perl code and that the provided version has a bit of code duplication it it that makes the internal logic a bit hard to follow.

    Edit: Okay, I cannot use a tags here, contradictory the documentation. I thought the post would have been to long and splitted it into three posts.

      I downloaded the C zip you linked only to find it only contains the same trivial, single-threaded examples you already posted (amongst lots of other unrelated examples). But no C code that uses 3 threads and shared sockets and queues and stuff to implement something you claim to be trying to emulate in Perl.

      Nothing for me to use as a basis for producing a threaded Perl code to meet the sketchy and confused specs you outline in words.

      So, then I start to think about the examples you have posted, and it is my conclusion that you are trying to use threads to mix together two different APIs either of which can serve the purpose on its own.

      • Either: your server queries the current value from the device on demand -- ie. when a client connects -- synchronously reads the response and returns it to the connecting client.

        No need for a callback here. Each client gets the latest value on demand.

      • Or: your server registers a callback that, once per second or whatever frequency you choose, gets called back (if the temp changes), and updates a (global) cache variable with the latest temperature value.

        And whenever your clients connect, you simply return the current value of that cache variable.

        No threads -- unless you choose to use threads for your client connects -- no need or use for queues.

        Simplicity personified.

      So, unless you can provide a reason for your 3 threads + queues design -- and the C code that implements it -- I can see no reason at all to try and create something that matches your earlier descriptions. Especially from scratch and without reference.


      With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

        Regarding the C zip: the relevant files for my example here are: bindings/ip_connection.(c|h) and bindings/bricklet_temperature.(c|h). ip_connection.(c|h) implements all the thread and socket logic. bricklet_temperature.(c|h) does the packing and unpacking of TCP/IP packages to function call arguments and return values. The C code forms a binding for a TCP/IP protocol here. The examples I provided are using this binding. All the threading and socket handling is done inside the binding C code.

        Sorry, you're right my description of the spec is long and probably hard to follow. Let me try to summarize the critical points:

        • First of all there are only two parties involved here: the user program is the client asking for the temperature (using the binding), the hardware is the server providing the information. There is no other server-like thing in between them that could cache stuff as you describe it.
        • The C binding provides functions to talk to the hardware using TCP/IP. The C binding takes care of packing function call parameters and send them to the hardware and unpacking received data from the hardware to function call return values. There are other hardware modules beside the temperature sensor. For example, there is a relay module. It has a set_state function. The C binding takes care of creating the correct TCP/IP packet to set the state of the relay and sends it. all the user hast to do is calling the set_state function.

          You're right: if all I wanted to do was setting a relay state or getting the temperature then this could all be done synchronous. The get_temperature function would just create and send request and then directly receive incoming data from the socket to get the response and return it to the user.

          Okay, until here everything is fine, I just need a socket, that's it, no threads, no queues, no problems.

        • But there is also a button module. A program that wants to react on a button press could just call the is_pressed function over and over again to do that. Or it can tell the button module to send a message each time the button state changes. This is a callback, a spontaneous response send by the hardware without a corresponding request from the user program. There is no point in caching this information, as suggested.

        And that'a what you called "mixing together two different APIs". There is the getter/setter style stuff and the callback stuff. One could argue that all information that callbacks can provide can also be polled for by getter calls. That's basically correct from a general point of view. But from a hardware point of view polling wastes a lot of bandwidth especially for events that done occur that often such as the press of a button. Also the hardware might not be directly connected to TCP/IP, there might be low bandwidth connections such as USB and RS485 in between. From an API and protocol point of view the actual electrical connection is not visible to the user program. There is also the problem that most communication links are designed for high throughput, but this protocol requires low latency due to the request/response nature. So callback are useful and have advantages compared to polling.

        The callbacks is what makes the implementation of the protocol difficult here, but the hardware and the TCP/IP protocol is there and fixed, the task is to implement a Perl module that provides these features to the user.

        To receive callbacks there has to be someone receiving incoming data from the socket all the time. This cannot be done within the simple model of only receiving data from the socket if a response for a getter call is expected. The C code does this by using a receive thread (thread 1: ip_connection.c:1284 ipcon_receive_loop) that constantly receives incoming data. Then the callbacks have to be delivered to the user code. This is done by the callback thread (thread 2: ip_connection.c:1141 ipcon_callback_loop). As explained in the earlier post, this cannot be done from the receive thread if the user should be able to call getters from the callback functions. The last thread (thread 3) is the main-thread of the program or any other user-created thread that calls getter/setter function of the bindings. As getter/setter calls directly write their request to the socket. Communication between all this threads is done by queues.

        Instead of using threads for callback handling the binding code could include a blocking handle_callbacks function that does the work of the receive and callback threads. Then the user has to call it in order to use callbacks. This is how this is realized in PHP, that lacked thread support at the time the PHP binding was implemented. But I'd like to have the Perl binding work as all the other bindings, and keep PHP as an exception.

        This got quite long again. It seems that I fail at stating my problems in a short fashion :-)

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1079093]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others drinking their drinks and smoking their pipes about the Monastery: (6)
As of 2024-04-20 00:16 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found