mailto: blog -at- heyrick -dot- eu

Navi: Previous entry Display calendar Next entry
Switch to desktop version

FYI! Last read at 21:03 on 2024/04/28.

DumbServer - writing a simple server for RISC OS

Following a discussion on the RISC OS Open forums, I thought I'd have a crack at writing a complete simple server for RISC OS.

Presented below is a program that opens port 123. If you connect to it, you will see a menu giving various options, and upon pressing a key, will give humorous responses. Only one connection can be established at a time, other connections will be told the server is busy.

This program compiles with the ROOL DDE. It makes use of the TCPIP libraries, and the standard C libraries. It is a proper multitasking program, though for simplicity it does not place an icon on the iconbar or anything like that. You can quit it from the TaskManager.

The code ought to be fairly self-documenting with the comments. If you think anything needs to be explained better, leave a comment. The exception is the FD_SET/FD_ISSET nonsense in check_listener(). This is because the socket code has its origins in Unix, and Unix is good at doing complicated things, but less good at simple things. You can easily check dozens of sockets at the same time, but you need to do all of this junk to check just one socket. Ho hum. Just copy it into your code, it works... ☺

/* DumbServer v0.01

   by Rick Murray, 2016/01/04
   to demonstrate writing a simple Internet accessible server.
   http://www.heyrick.co.uk/blog/index.php?diary=20160104

   Run this program, then connect to the machine on port 123 using any telnet client.


   © 2016 Richard Murray
   Licensed under the I-Don't-Give-A-Crap licence version 0.
   If you can't handle sarcasm, assume bsd. And don't read the rest of this file.
*/


#include "kernel.h"
#include "swis.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define COMPAT_INET4 1
#include "sys/types.h"       // shorthand types (like u_int and u_long)
#include "sys/socket.h"      // address families (etc) and socket functions
#include "socklib.h"         // more socket functions (and dupes of prior line)
#include "inetlib.h"         // various support functions
#include "netinet/in.h"      // IP/IP definitions
#include "sys/ioctl.h"       // for ioctl function
#include "sys/errno.h"       // error definitions
#include "netdb.h"           // hostent definition

#define TRUE  1
#define FALSE 0


static unsigned int sock = 0;    // listening socket
static unsigned int mysock = -1; // connected socket (is -1 if NOT connected)
static fd_set isready;
static struct timeval timeout;

static unsigned int taskhandle;
union polldatadef
{
   char bytes[256];
   int  words[64];
} data;

static char buffer[256] = "";

static void check_listener(void);
static void check_socket(void);



int main(void)
{
   // Set up a socket for receiving incoming connections
   int  args[2];
   int  event = 0;
   struct sockaddr_in saddr;
   union polldatadef polldata;


   // ## PART ONE
   // ## Setting up the socket

   // Create the socket
   sock = socket(AF_INET, SOCK_STREAM, PF_UNSPEC);

   // Allow address reuse - *very* *important*. If you don't do this, then you'll need to
   // wait for the socket to "expire" in between running this program, and running it again.
   args[0] = 1; // non-zero = enable
   setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &args, sizeof(int));

   // Set linger time to 10 seconds maximum
   args[0] = 1;  // wait for data to drain
   args[1] = 10; // 10 seconds manimum
   setsockopt(sock, SOL_SOCKET, SO_LINGER, &args, (sizeof(int) * 2));

   // Bind to port 123
   memset(&saddr, 0, sizeof(saddr));   // zero everything before use
   saddr.sin_family = AF_INET;         // accept internet connection...
   saddr.sin_port = htons(123);        // ...on port 123...
   saddr.sin_addr.s_addr = INADDR_ANY; // ...from anybody.
   if ( bind(sock, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
   {
      socketclose(sock);
      printf("Failed to bind socket - is DumbServer already running?\n");
      exit(EXIT_FAILURE);
   }

   // Make it non-blocking; so the machine won't hang up waiting for something to happen.
   args[0] = 1;
   socketioctl(sock, FIONBIO, &args[0]);

   // Now place an ear against the wire
   if ( listen(sock, 1) != 0 )
   {
      socketclose(sock);
      printf("Unable to listen for incoming connections.\n");
      exit(EXIT_FAILURE);
   }


   // ## PART TWO
   // ## Initialising as a Wimp task and polling

   // Makes extensive use of embedded assembler here. Sad pathetic people will hate me for
   // this (see if I care...look, this is me caring, see?), however the idea is to paste in
   // some boilerplate Wimp code without specifying any particular library and all that that
   // may involve...
   //
   // The SWI instruction:
   //   SWI  <swinum>, {<input>}, {<output>}, {<corrupted>}
   // in/out/corr is a register list. Can also specify LR (in module code) and PSR if flags
   // are expected to be corrupted (they usually are).
   // The register lists control how cc generates code, so do NOT forget anything, and
   // specify ALL used or corrupted registers.

   // Basic minimal Wimp initialise
   __asm
   {
      MOV   R0, 200
      MOV   R1, 0x4B534154   // "TASK"
      MOV   R2, "DumbServer"
      MOV   R3, 0
      SWI   (Wimp_Initialise + XOS_Bit), {R0-R3}, {R1}, {R0, PSR}
      MOV   taskhandle, R1
   }

   // Polling loop
   while ( TRUE )
   {
      __asm
      {
         // Get the current ticker value
         SWI   OS_ReadMonotonicTime, {}, {R0}, {PSR}

         // Put it into R2 with 50 (half a second) added
         ADD   R2, R0, 50

         // The poll block buffer
         MOV   R1, &polldata

         // And finally the poll mask
         MOV   R0, #0  // lazy - just return everything...

         // Yield!
         SWI   (Wimp_PollIdle + XOS_Bit), {R0-R2}, {R0, PSR}, {R1, R2}

         // Remember the event code
         MOVVC event, R0

         // else fake enough upon an error to get the program to shut down
         MOVVS event, 17             // User_Message
         MOVVS polldata.words[4], 0  // Message_Quit
      }

      // Now deal with the poll events. Will likely be either NULL or USER_MESSAGE.
      // We'll ignore anything else.

      switch (event)
      {
         case  0: // Null_Event - anything happen?
                  check_listener();
                  if ( mysock != -1 )
                     check_socket();
                  break;

         case 17: // User_Message, check +16 for message code
                  switch (polldata.words[4])
                  {
                     case 0x00000 : // Message_Quit
                                    // Close task
                                    __asm
                                    {
                                       MOV   R0, taskhandle
                                       MOV   R1, 0x4B534154   // "TASK"
                                       SWI   (Wimp_CloseDown + XOS_Bit), {R0-R1}, {}, {R0, PSR}
                                    }
                                    // Close socket(s)
                                    if ( mysock != -1 )
                                       socketclose(mysock);
                                    socketclose(sock);
                                    // Bye.
                                    exit(EXIT_SUCCESS);
                                    break;
                  }
                  break;

      } // end of event switch
   }; // end of "while (TRUE)" polling loop

   return 0; // shouldn't ever be executed
}



static void check_listener(void)
{
   // Check to see if there's an incoming connection
   struct sockaddr_in saddr;
   int namelen = 0;

   FD_ZERO(&isready);
   FD_SET(sock, &isready);
   timeout.tv_sec = 0;
   timeout.tv_usec = 0;
   select((sock+1), &isready, 0, 0, &timeout);
   if (FD_ISSET(sock, &isready))
   {
      // There is a connection pending.
      if (mysock == -1)
      {
         // We are not connected, so accept this connection.
         mysock = accept(sock, (struct sockaddr *)&saddr, &namelen);
         if (mysock == -1)
         {
            // Something went wrong. Just get out of here, the listener is still active...
            return;
         }
      }
      else
      {
         // We are ALREADY connected, so accept and DISCARD this connection.
         int  pickupsock;

         pickupsock = accept(sock, (struct sockaddr *)0, (int *)0);
         if (pickupsock != -1)
         {
            strcpy(buffer, "Sorry, the server is busy.\r\n");
            socketwrite(pickupsock, &buffer, strlen(buffer));
            shutdown(pickupsock, 0);
            socketclose(pickupsock);
         }
      }

      // The IP address of the remote user is saddr.sin_addr.s_addr, if you need it.

      // At this point, "mysock" will be != -1 so it will be treated as a live socket and any
      // other connections will result in busy message followed by a disconnect.

      // Right-o, let's welcome the user.
      strcpy(buffer, "** ACCESS GRANTED **\r\n\r\n\
Please select:\r\n\
   [A]ccess All Files\r\n\
   [B]reak into system\r\n\
   [C]orrupt system\r\n\
   [Q]uit.\r\n\
>");
      // Can anybody say CHEESY! (^_^)
      socketwrite(mysock, &buffer, strlen(buffer));
   }

   return;
}



static void check_socket(void)
{
   // Check to see if connected socket has done something.
   int  howmuch = 0;

   // Peek a byte to see if connection is still active
   howmuch = recv(mysock, &buffer, 1, MSG_PEEK);
   if (howmuch == 0)
   {
      // Does NOT return zero bytes as a result. As we're non-blocking, it would return -1
      // with errno set to 35 (EWOULDBLOCK).
      // Therefore, a result of zero means the remote end has thrown in the towel.
      // Dunno why we need to do this rubbish instead of socketread() returning -1 with the
      // error EBUGGEREDOFF or somesuch...
      shutdown(mysock, 0);
      socketclose(mysock);
      mysock = -1; // so we know nothing is connected now
      return;
   }

   // Read up to 250 bytes.
   howmuch = socketread(mysock, &buffer, 250);
   if (howmuch > 0)
   {
      // Our menu provides single keypress options, so we'll only look at  the first character
      // that is received. If the user types more, well, that's their problem isn't it?
      switch( buffer[0] )
      {
         case 'A' : // falls through
         case 'a' : strcpy(buffer, "\r\nWhat the hell? This isn't a movie!\r\n\
No self-respecting system would EVER have an option like that.\r\n>");
                    break;

         case 'B' : // falls through
         case 'b' : strcpy(buffer, "\r\nYeah... and I suppose you want inch tall \
characters as well, right?\r\n>");
                    break;

         case 'C' : // falls through
         case 'c' : strcpy(buffer, "\r\nThis one is easy, just install Windows 10.\r\n>");
                    // And with Win10 IoT for the Pi, this even makes sense on a machine
                    // running RISC OS.......god help us...
                    break;

         case 'Q' : // falls through
         case 'q' : strcpy(buffer, "\r\nBye!\r\n");
                    socketwrite(mysock, &buffer, strlen(buffer));
                    shutdown(mysock, 0);
                    socketclose(mysock);
                    mysock = -1; // so we know nothing is connected now
                    return;
                    break; // never executed, but looks weird without it

         default  : strcpy(buffer, "\r\nCan't you read? Enter A, B, C, or Q.\r\n>");
                    break;
      }

      socketwrite(mysock, &buffer, strlen(buffer));
   }

   return;
}

 

Ideas for you, now:

 

And now, the bit you were waiting for. Click the picture for it...

 

 

Your comments:

Colin, 5th January 2016, 16:32
A recv return value of 0 means the the remote end of the socket wont send any more data ie that it has shutdown the 'write' side of its socket - it isn't an error condition. The socket may still be open for you to write to it. It will return zero whether the socket is nonblocking or not. 
 
Love the lengths you go to to avoid using _swix 
 
Could simplify checklistener to just accept instead of select and accept. nonblocking accept returns ewouldblock if there are no queued connections

Add a comment (v0.11) [help?]
Your name:

 
Your email (optional):

 
Validation:
Please type 43352 backwards.

 
Your comment:

 

Navi: Previous entry Display calendar Next entry
Switch to desktop version

Search:

See the rest of HeyRick :-)