/*---------------------------------------------------------------------+
| Copyright (c) 1995, SAS Institute Inc. |
| Unpublished - All Rights Reserved |
| S A S / C S A M P L E |
| |
| NAME: TCPLISTN |
| LANGUAGE: C |
| PURPOSE: Demonstrate a TCP listener application that "attaches" |
| a TCP Server, TCPTSERV, on MVS. TCPLISTN details the |
| following socket calls: |
| socket(), bind(), listen(), accept(), read(), |
| givesocket(), takesocket(), select() and close(). |
| MVS - |
| COMPILE, LINK: SUBMIT prefix.SAMPLE.AUX(TCPLISTN) |
| EXECUTE: prefix.SAMPLE.AUX(TCPLSGO) |
| where "prefix" is the installation defined high-level- |
| qualifier for the SAS/C product. |
| NOTES: TCPLISTN assumes a working TCP/IP environment on both |
| the local and remote hosts, refer to MISC NOTES. |
| TSO - |
| COMPILE: LC370 CLIST - options ENXREF EXTNAME RENT |
| LINK: CLK370 CLIST |
| EXECUTE: call 'your.local.load(TCPLISTN)' |
| NOTES: TCPLISTN assumes a working TCP/IP environment on both |
| the local and remote hosts. Refer to MISC NOTES: |
| CMS - N/A - Multi-tasking is NOT supported w/in CMS |
| MISC NOTES: TCPLISTN needs to be customized for the local TCP/IP |
| environment. The local information required by |
| TCPLISTN is: |
| LISTENPORT - defines the port TCPLISTN will do a |
| listen(). Default is port 13. |
| QUITSTRING - used as a method to terminate TCPLISTN. |
| TCPLISTN will read the message from the |
| client application, if the message is: |
| QUITSTRING, then TCPLISTN terminates. |
| MAXSOCKETS - defines the maximum number of sockets |
| for the select() socket array. |
| MISC NOTES: Use TCPCLNT to test the TCPLISTN listener. |
| MISC NOTES: TCPLISTN uses the following environment variables: |
| TCP_SOCK - Socket descriptor passed to TCPTSERV |
| TCP_DOMAIN - AF_INET = 2, passed to TCPTSERV |
| TCP_NAME - Listener Jobname, passed to TCPTSERV |
| TCP_SUBTASKNAME - taskname from getclientid() passed |
| to TCPTSERV. On the givesocket() call, |
| sub-taskname is set to: ' ' |
| MISC NOTES: Telnet may be used to terminate the TCPLISTN. Telnet |
| to the HOST and PORT for TCPLISTN, and then enter the |
| QUITSTRING as defined below. |
| |
+---------------------------------------------------------------------*/
#define LISTENPORT 13 /* Set default listen port */
#define QUITSTRING "LSNRQUIT" /* If the recv() message is QUITSTRING */
/* terminate TCPLISTN */
#define MAXSOCKETS 50 /* max sockets opened at once */
/*-------------------------------------------------------------------+
| Includes for socket calls. |
+-------------------------------------------------------------------*/
#include <sys/types.h>
#include <ctype.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#if __SASC__ == 550
#include <fcntl.h>
#else
#include <unistd.h>
#endif
/*-------------------------------------------------------------------+
| Include for ATTACH. |
+-------------------------------------------------------------------*/
#include <ostask.h>
/*-------------------------------------------------------------------+
| Miscellaneous includes. |
+-------------------------------------------------------------------*/
#include <lclib.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <lcstring.h>
#define TRUE 1
#define FALSE 0
#define MAXREQLEN 256 /* max length of request, in chars */
/*-------------------------------------------------------------------+
| Macros for converting EBCDIC <-> ASCII. |
+-------------------------------------------------------------------*/
#define convFromAscii(str) {cp=(str); while(*cp = ntohcs(*cp)) cp++;}
#define convToAscii(str) {cp=(str); while(*cp = htoncs(*cp)) cp++;}
int setupSocket(void);
int acceptRequest(void);
int closeSockets(int);
void Quit(void);
struct Argument {
short len;
char parms[1000];
};
struct socklist { /* linked list of socket file descriptors */
int sock;
int domain; /* so that it can issue takesocket() */
char name[8]; /* so that it can issue takesocket() */
char subtaskname[8];
char pgmname[8];
void *subtcb; /* Handle for ATTACHed program */
void *plist[1]; /* pointer to argument structure for ATTACH */
struct socklist *next;
} *servlist;
char *cp; /* char pointer for EBCDIC<->ASCII macros */
int listensock, /* socket on which we listen for requests */
numsockets, /* number of sockets opened by listener */
highsocket, /* highest socket number */
holdrequests; /* flag: listener cannot handle any more */
/* requests right now (all sockets used) */
fd_set pending, /* list of sockets waiting to be taken */
taken; /* list of sockets which have been taken */
struct clientid myid, /* clientid of listener for argument struct */
giveid; /* generic clientid used for givesocket() */
void main()
{
int ns; /* number of sockets with action */
fd_set incoming; /* bound socket description for select() */
struct timeval timeout;
/*----------------------------------------------------------------+
| Set up a socket, initialize list of pending servers to none, |
| and set timeout for select() call. |
+----------------------------------------------------------------*/
if (setupSocket() == -1)
exit(EXIT_FAILURE);
FD_ZERO(&pending);
holdrequests = FALSE;
timeout.tv_sec = 60;
timeout.tv_usec = 0;
while (TRUE)
{
do {
/*----------------------------------------------------------+
| Check for action on sockets given to ATTACHed programs, |
| as well as incoming socket (unless all sockets are used). |
+----------------------------------------------------------*/
FD_ZERO(&incoming);
if ((!holdrequests) && (numsockets < MAXSOCKETS))
FD_SET(listensock, &incoming);
taken = pending;
ns = select(highsocket+1, &incoming, NULL, &taken, &timeout);
} while (ns == 0);
if (ns == -1)
{
perror("select() call failed");
Quit();
}
/*-------------------------------------------------------------+
| If there was action on listensock, then handle the incoming |
| request. |
+-------------------------------------------------------------*/
if (FD_ISSET(listensock, &incoming))
{
ns--;
acceptRequest();
}
/*-------------------------------------------------------------+
| If there was action on any other sockets, then release the |
| sockets of those programs which have taken them. |
+-------------------------------------------------------------*/
if (ns > 0)
closeSockets(ns);
}
}
/*-------------------------------------------------------------------+
| Open a socket and set it up to accept requests. Return -1 if error.|
+-------------------------------------------------------------------*/
int setupSocket()
{
struct sockaddr_in sa_serv; /* socket address for server */
int sa_len = sizeof(sa_serv);
char hostname[72];
/*----------------------------------------------------------------+
| Get a socket of the proper type. |
+----------------------------------------------------------------*/
listensock = socket(AF_INET, SOCK_STREAM, 0);
if (listensock == -1)
{
perror("socket() call failed in setupSocket()");
return -1;
}
numsockets = 1;
/*----------------------------------------------------------------+
| Get information about listener to pass to ATTACHed servers. |
| Clientid used for givesocket() should be the same but with a |
| subtaskname of all blanks. |
+----------------------------------------------------------------*/
if (getclientid(AF_INET, &myid) == -1)
{
perror("getclientid() call failed in setupSocket()");
close(listensock);
return -1;
}
giveid = myid;
memset(giveid.subtaskname, ' ', 8);
/*----------------------------------------------------------------+
| Prepare a socket address for the server. We will request the |
| port number LISTENPORT, unless LISTENPORT is 0, in which case |
| we are assigned the next available port number. |
+----------------------------------------------------------------*/
memset(&sa_serv, '\0', sizeof(sa_serv));
sa_serv.sin_family = AF_INET;
sa_serv.sin_addr.s_addr = htonl(INADDR_ANY);
sa_serv.sin_port = htons(LISTENPORT);
/*----------------------------------------------------------------+
| Bind our socket to the desired address so clients may reach us. |
+----------------------------------------------------------------*/
if (bind(listensock, &sa_serv, sizeof(sa_serv)) == -1)
{
perror("bind() call failed in setupSocket()");
close(listensock);
return -1;
}
/*----------------------------------------------------------------+
| Print listener information. |
+----------------------------------------------------------------*/
if (getsockname(listensock, &sa_serv, &sa_len) == -1)
{
perror("getsockname() failed in setupSocket()");
close(listensock);
return -1;
}
if (gethostname(hostname, sizeof(hostname)))
{
perror("gethostname() failed in setupSocket()");
close(listensock);
return -1;
}
printf("Listener is on Host: %.64s Port#: %d.\n",
hostname, (int) ntohs(sa_serv.sin_port));
printf("Request server name '" QUITSTRING "' to quit.\n");
/*----------------------------------------------------------------+
| Set up a queue for incoming requests. SOMAXCONN is maximum |
| size of queue. |
+----------------------------------------------------------------*/
listen(listensock, SOMAXCONN);
highsocket = listensock;
return 0;
}
/*-------------------------------------------------------------------+
| A request has been detected--accept it, givesocket(), and ATTACH |
| program. Return -1 if error. |
+-------------------------------------------------------------------*/
int acceptRequest()
{
int numchars, /* number of chars read from socket */
rc, /* rc from MVS ATTACH function */
pos, /* index variable */
reqsock, /* request socket */
i; /* index variable */
char req[MAXREQLEN+1], /* buffer for reading in request */
errstring[MAXREQLEN+80];/* error message sent to client */
struct socklist *new; /* new node in list of open sockets */
struct sockaddr_in sa_client;/* socket address of client */
int sa_len = sizeof(sa_client);
char Env_buff[256];
struct Argument *args;
char *valid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"abcdefghijklmnopqrstuvwxyz" \
"0123456789@$#";
/*----------------------------------------------------------------+
| Allocate space for a new addtion to the socklist |
+----------------------------------------------------------------*/
new = (struct socklist *)malloc(sizeof(struct socklist));
if (new == NULL)
{
perror("malloc() for new failed in acceptRequest()");
return -1;
}
memset(new, 0, sizeof(struct socklist));
args = (struct Argument *)malloc(sizeof(struct Argument));
if (args == NULL)
{
perror("malloc() for args failed in acceptRequest()");
return -1;
}
memset(args, 0, sizeof(struct Argument));
/*----------------------------------------------------------------+
| Divert incoming request to its own socket. If accept() call |
| fails, then all sockets are being used, so hold off requests. |
+----------------------------------------------------------------*/
reqsock = accept(listensock, &sa_client, &sa_len);
if (reqsock == -1)
{
perror("accept() failed in acceptRequest()");
holdrequests = TRUE;
return -1;
}
/*----------------------------------------------------------------+
| Read request from socket and convert from ASCII to EBCDIC. |
+----------------------------------------------------------------*/
numchars = read(reqsock, req, MAXREQLEN);
if (numchars < 0)
{
perror("socket read() failed in acceptRequest()");
close(reqsock);
return -1;
}
req[numchars] = '\0';
convFromAscii(req);
/*----------------------------------------------------------------+
| Check request for validity (a pretty lax check, admittedly). |
+----------------------------------------------------------------*/
if (req[0] == '\0')
{
strcpy(errstring, "MVS listener: empty request received.\n");
convToAscii(errstring);
write(reqsock, errstring, strlen(errstring));
close(reqsock);
return -1;
}
/*----------------------------------------------------------------+
| Set up the Env vars string for subtaskname, name, sock, etc. |
+----------------------------------------------------------------*/
new->domain = AF_INET;
memcpy(new->name, myid.name, 8);
memcpy(new->subtaskname, myid.subtaskname, 8);
sprintf(Env_buff, " =TCP_SOCK=%d =TCP_DOMAIN=%d " \
"=TCP_NAME=%s =TCP_SUBTASKNAME=%s",
reqsock, new->domain, new->name, new->subtaskname);
/*----------------------------------------------------------------+
| Separate the command from the parmameters |
+----------------------------------------------------------------*/
pos = strspn(req, valid);
req[pos] = '\0';
strcpy(new->pgmname, req);
strcpy(args->parms, (req+pos+1));
/*----------------------------------------------------------------+
| Clean up the Program name. |
+----------------------------------------------------------------*/
for (i = 0; i < sizeof(new->pgmname); i++)
{
if (new->pgmname[i] < ' ')
new->pgmname[i] = ' ';
else new->pgmname[i] = toupper(new->pgmname[i]);
}
/*----------------------------------------------------------------+
| Check for QUIT request. |
+----------------------------------------------------------------*/
if (strcmp(new->pgmname, QUITSTRING) == 0)
{
close(reqsock);
Quit();
}
/*----------------------------------------------------------------+
| Combine the Program parms and the environment vars and assign |
| to the plist pointer in new. |
+----------------------------------------------------------------*/
strcat(args->parms, Env_buff);
args->len = strlen(args->parms);
for (i = 0; i < args->len; i++)
{
if (args->parms[i] < ' ')
args->parms[i] = ' ';
}
new->plist [0] = (void *) (0x80000000 | (unsigned) args);
/*----------------------------------------------------------------+
| Give the socket and then attach the new subtask. |
+----------------------------------------------------------------*/
givesocket(reqsock, &giveid);
rc = ATTACH(&(new->subtcb), _Aep, new->pgmname, _Aparam,
new->plist, _Aend);
/*----------------------------------------------------------------+
| Check return code from ATTACH. |
+----------------------------------------------------------------*/
if (rc != 0)
{
fprintf(stderr, "ATTACH for server '%s' failed with rc = %d.\n",
new->pgmname, rc);
close(reqsock);
return -1;
}
/*----------------------------------------------------------------+
| Add this server to lists of pending programs. |
+----------------------------------------------------------------*/
new->sock = reqsock;
new->next = servlist;
servlist = new;
FD_SET(reqsock, &pending);
if (reqsock > highsocket)
highsocket = reqsock;
numsockets++;
return 0;
}
/*-------------------------------------------------------------------+
| Close sockets taken by ATTACHed servers. Return -1 if error. |
+-------------------------------------------------------------------*/
int closeSockets(nt)
int nt; /* number of sockets taken by ATTACHed servers */
{
struct socklist *servptr, /* list of file descriptors of */
*next, *prev; /* sockets given to servers */
int nr = nt; /* number of sockets left to close */
prev = servptr = servlist; /* point to first socket in list */
while ((nr > 0) && (servptr != NULL))
{
/*-------------------------------------------------------------+
| If this socket was taken by an ATTACHed server, then close |
| the socket, remove it from our lists, and signal that another|
| socket is free for use. |
+-------------------------------------------------------------*/
if (FD_ISSET(servptr->sock, &taken))
{
close(servptr->sock);
nr--;
numsockets--;
holdrequests = FALSE;
FD_CLR(servptr->sock, &pending);
next = servptr->next;
if (servptr == servlist)
servlist = next;
else
prev->next = next;
free((char *) servptr->plist[0]);
free((char *) servptr);
servptr = next;
}
else
{
prev = servptr;
servptr = servptr->next;
}
}
return 0;
}
/*-------------------------------------------------------------------+
| Close all sockets and quit. |
+-------------------------------------------------------------------*/
void Quit()
{
struct socklist *servptr, *next;
next = servlist;
while (next != NULL)
{
servptr = next;
next = servptr->next;
close(servptr->sock);
free((char *) servptr->plist[0]);
free((char *) servptr);
}
close(listensock);
exit(EXIT_SUCCESS);
}
|