Dynamic Server Threads

From Nutwiki
Revision as of 17:02, 27 October 2016 by Harald (Talk | contribs) (1 revision imported)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Overview

This example demonstrates use of dynamic threads to allow for an unknown number of users to connect to a Nut/OS node at the same time. It dynamically creates a new thread whenever one has been occupied by a connecting client and kills threads as they are no longer needed.

In order to test this example you will want to use a telnet client and connect to the IP the board outputs to the terminal.

On Windows you can do this: Start -> Run... -> "telnet"

Then type "open <IP_OF_YOUR_BOARD>" where <IP_OF_YOUR_BOARD> is the IP the board prints to the terminal (without the <>).

If you are using Linux you probably already know what to do.

Source Code

<source lang="c">

  1. include <dev/board.h>
  2. include <stdio.h>
  3. include <io.h>
  1. include <sys/thread.h>
  2. include <sys/timer.h>
  3. include <sys/confnet.h>
  4. include <sys/socket.h>
  1. include <arpa/inet.h>
  2. include <net/route.h>
  3. include <netinet/tcp.h>
  1. include <pro/dhcp.h>

THREAD(TelnetThread, arg) {

   TCPSOCKET *my_sock = NULL;
   char *text = "Hello user! If this wasn't just an example you could now enter commands!";
   my_sock = NutTcpCreateSocket();
   NutTcpAccept(my_sock, 23);
   NutThreadCreate("telnetd", TelnetThread, NULL, 512);
   NutTcpSend(my_sock, text, strlen(text));
   NutTcpCloseSocket(my_sock);
   NutThreadExit();
   for (;;) {
   }

}

int main(void) {

   unsigned long baud = 115200;
   NutRegisterDevice(&DEV_DEBUG, 0, 0);
   freopen(DEV_DEBUG_NAME, "w", stdout);
   _ioctl(_fileno(stdout), UART_SETSPEED, &baud);
   puts("\nNut/OS Dynamic Server Threads Demo");
   if (NutRegisterDevice(&DEV_ETHER, 0, 0)) {
       puts("Registering " DEV_ETHER_NAME " failed.");
   }
   else if (NutDhcpIfConfig(DEV_ETHER_NAME, NULL, 0)) {
       puts("Configuring " DEV_ETHER_NAME " failed.");
   }
   printf("Network configured. Listening on IP %s, port 23.", inet_ntoa(confnet.cdn_ip_addr));
   NutThreadCreate("telnetd", TelnetThread, NULL, 512);
   for (;;) {
       NutThreadYield();
   }
   return 0;

} </source>

Details

Let's first discuss the main function to see what's going on and save the thread for later.


<source lang="c"> int main(void) {

   unsigned long baud = 115200;
   NutRegisterDevice(&DEV_DEBUG, 0, 0);
   freopen(DEV_DEBUG_NAME, "w", stdout);
   _ioctl(_fileno(stdout), UART_SETSPEED, &baud);
   puts("\nNut/OS Dynamic Server Threads Demo");

</source>

As always we prepare the debug device first so we can output things to the terminal.

<source lang="c"> if (NutRegisterDevice(&DEV_ETHER, 0, 0)) {

   puts("Registering " DEV_ETHER_NAME " failed.");

}

else if (NutDhcpIfConfig(DEV_ETHER_NAME, NULL, 0)) {

   puts("Configuring " DEV_ETHER_NAME " failed.");

}

printf("Network configured. Listening on IP %s, port 23.", inet_ntoa(confnet.cdn_ip_addr)); </source>

Now that we got that done, it is time to prepare the network connection. This is going to be a networked example after all, right? The first function call registers the ethernet controller, the second function call lets it find its settings via DHCP. This is normally present in most networks, if it is not you should instead use a hardcoded IP.

Since we have to connect to the board and DHCP may have given it an IP we don't know, we output the IP to the terminal.

<source lang="c"> NutThreadCreate("telnetd", TelnetThread, NULL, 512);

for (;;) {

   NutThreadYield();

} return 0; </source>

The final part of the main function: We create a new thread, call it telnetd (like daemon) and point it to the thread function. The rest of this is just an infinite loop that keeps yielding the processor. Normally you may want to serve the watchdog here but since this is an example we'll keep it simple.

Alright, let's take a look at that thread function. It's the more interesting part here, anyway.

<source lang="c"> THREAD(TelnetThread, arg) {

   TCPSOCKET *my_sock = NULL;
   char *text = "Hello user! If this wasn't just an example you could now enter commands!";
   my_sock = NutTcpCreateSocket();

</source>

First thing we do in this thread is declare a socket pointer and a message we want the user to see when he connects. Since this is just an example we will just close the connection after the user has received the message. A real server would of course require some kind of command line and interpreter but that is way out of the scope of this example.

Second thing we do here is create a socket. We want to accept connections so that probably makes sense.

<source lang="c"> NutTcpAccept(my_sock, 23);

NutThreadCreate("telnetd", TelnetThread, NULL, 512); </source>

Now it gets a little more interesting. We wait for an incoming connection using NutTcpAccept and as soon as we get one we create a new telnet thread.

Wait, what?

The reason we are creating another thread at this point is this: We want to ensure we always have a thread able to accept the next connection. And we start out with only one thread, remember? So if every thread creates another instance of the telnet at the same time it is accepting the connection, there will always be a thread able to handle the next connection.

Originally, when writing the first version of this example, it had the thread creation in the main loop and would pass the socket to the newly created thread. While this is correct, hierarchically speaking, this version is way easier to use and understand.

<source lang="c"> NutTcpSend(my_sock, text, strlen(text));

NutTcpCloseSocket(my_sock); NutThreadExit();

for (;;) { } } </source>

Since we accepted the connection and then created a new thread, all we have left to do is handle the client requests. Since in our example there is only one thing you can do with the server we send the message we prepared to the client and then close the connection.

After that, the thread is no longer needed (since we created another thread to take care of followup requests) we just tell it to exit. This makes the thread "selfdestruct". No it won't blow up (unless you build some special hardware for that ;p) but it will stop running and the resources it used will be released for the system to use again.

Output

The example will show the following on the terminal: <source lang="text">

Nut/OS Thread Argument Demo Network configured. Listening on IP 192.168.192.121, port 23. </source> When you connect via a telnet client, you will get this message: <source lang="text"> Hello user! If this wasn't just an example you could now enter commands! </source>