Rick's b.log - 2016/01/04 |
|
It is the 31st of January 2025 You are 3.147.86.30, pleased to meet you! |
|
mailto:
blog -at- heyrick -dot- eu
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... ☺
Ideas for you, now:
And now, the bit you were waiting for. Click the picture for it...
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.
/* 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;
}
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
© 2016 Rick Murray |
This web page is licenced for your personal, private, non-commercial use only. No automated processing by advertising systems is permitted. RIPA notice: No consent is given for interception of page transmission. |