/* xml_sock_demo.c -- to demostrate an XML socket connection for a GCN site * * USAGE: * xml_sock_demo * * Where "port_num" is the port number for your site. * * DESCRIPTION: * INTRO: This standalone C program shows all the neccessary parts * that a site would use to become a GCN site connected via * the XML_socket method. These parts can be cut and spliced * into your site instrument-control software or * this program can be used as the basis of your instrument's control * program (there is a place noted in the code where to put your * instrument-specific code). * * CLIENT-SERVER: The site (in this case the computer/control_program * for the instrument that will be making the follow-up observations) * is acting as a socket "server". The GCN machine here at GSFC * acts as a socket "client". This is a reversal of what might be * considered the common sense notion of who is "serving" whom, * but it is necessary for the case of multiple sites. The GCN machine * needs to be able to handle the case when one or more of * the GCN sites "goes off-line". To do this GCN needs to be * the "client" end. If your "server" end is running, the GCN "client" * will connect to it and send the various XML_packets (imalives and * burst types). The GCN program makes continuous periodic attempts * to connect to all socket sites listed in the GCN "sites.cfg" file. * If your facility uses firewalls, you may need to ask your IT Security people * to put a hole in the wall for the GCN computer to come in through * to establish the socket connection. * * PACKET TYPES: This version of the program has been set up to handle all * the types of packets that the GCN system produces. However, each site * chooses which sources of GRB and Transient information it wants sent to it. * * SPEED: The XML_socket distribution method is very fast. It takes * 2-5 milliseconds to get the packet from the GCN computer to your computer. * * NO WRITE-BACK: Unlike the regular socket connection method, this xml_socket method * does not require the echoing back to GCN of all packets received. * * ALIVENESS CHECKING: This program reports to the operator via e-mail * when no packets have been received for more than 600 seconds. * It also sends e-mail when the packets resume. And on a finer timescale, * it reports to the logfile when nothing has been received for more than 62 sec * (since the imalives come every 60 sec). * * PORT NUMBERS: The port number is unique for each site and must be chosen & * agreed to by both ends before any activity can take place. A block of acceptable * port numbers was choosen so as to not conflict with any known commercial * Internet socket activity. * * CONFIG SETUP: Also, before any connections are possible, * a small amount of data identifying the site (name, Long, Lat, * filter criteria, IPN, port, etc) must be added to the "sites.cfg" file * at the GCN end. See http://gcn.gsfc.nasa.gov/gcn/invitation.html * * OPERATIONAL CONSIDERATIONS: * RESTARTING TOO QUICKLY: It is a common phenomenon when running * this program that when it is terminated (for whatever reason) * and restarted too quickly, that it will complain about * "server bind()" errors. It varies from site to site, but too quickly * seems to be within ~30 sec. So if you wait at least 60 sec * before restarting this program, then you should avoid this problem. * Basically, it is a result of the operating system on your machine * "remembering" for a few tens of seconds who/what was assigned to each port. * It is possible to adjust this time period, but it is op-sys/machine * dependent and I thought it better to leave it out of this * "general purpose demo program". * * CONNECTION TIME TO GCN: The GCN goes through a series of attempts * to connect to each xml_socket site. The time series is: * T_zero, T+4min, T+8, T+16, T+32, T+64, T+120, +=120, +=120, ... * T_zero is (a) when the GCN program first starts running, or * (b) when a socket site disconnects. So, depending on where GCN is * within this time series, you may have to wait up to 2 hours * for a connection to be established. * * ONLY ONE PROGRAM AT A TIME: Another scenario which produces a situation * where connection is not made comes from having two xml_sock_demo * programs running simultaneously. This can easily happen * when running xml_sock_demo in the background, and then invoking * it a second time. It can also happen in a much more subtle and * irksome way. If the xml_sock_demo gets "wedged", halted, paused, or * some other similar defunct type mode, then it will sometimes not show up * in the process table. Not seeing it present in the process table * will logically cause people to conclude that it is not present, and * then they will restart it. But it is in fact still present -- at least * to the point of still holding on to the socket connection and port number. * Thus it will be competing for packets with the second invokation, and * both will loose. It looks to the user that the second program * can never connect. This can be very perplexing to the user/operator. * The solution to this is making sure you use all the appropriate options * to the system utility that tells you about entries (active AND defunct) * in the process table. Once you see that the defunct copy is still * in the table, then you can use a "kill with extreme prejudice" command * (if using UNIX: kill -9 ) to totally remove it from the process list. * * STANDARDOUT COPY: This program duplicates almost all of its output * to the logfile also to the standardout. This duplication to the stdout * allows the first-time/interactive user to "see" what is going on with the * operations of the program. After a while, you will probably want * to invoke the program with the stdout redirected to /dev/null and * run it in the background: * xml_sock_demo 5000 >& /dev/null & * * NET PACKING FORMAT: The storage/usage of the IP Number and the Port * are also platform-dependent. On your site's platform, you may need to make * calls to htonl() and htons() to handle these dependencies. * * EMAIL ADDRESS CHANGES: Also, don't forget to change the e-mail address * that is hardwired into the e-mail command near the end * of the e_mail_alert() routine from my address to your address(es) * (assuming you still want this feature). * * ERRORS: All the "bad situations" within the operations of this demo program * are marked with print statements with the prefix "ERR:" to the error * messsage. This prefix allows the user quickly search the logfile * for instances of bad activity. * * FILES: * xml_sock_demo.log // A logfile of all packets received & program actions. * // This file is created, opened, and written to * // by the xml_sock_demo program. It provides a * // permanent record of what packets were received. * e_me_xtmp // A temp-file to produce the body of the e-mail. * * * LIST OF ROUTINES: * brokenpipe() // Wrap things up if the socket connection breaks * main() // The main processing routine * server() // Set up the server end of a connection (ie the customer end) * serr() // Specialized error/status reporting routine * chk_imalive() // Check and report an absence of Imalive/any packets rcvd * e_mail_alert() // Send e-mail when no packets * * COMPILATION: * For LINUX platforms: * cc -DLINUXPC xml_sock_demo.c -lm -o xml_sock_demo * or for non-LINUX platforms: * cc xml_sock_demo.c -lm -o xml_sock_demo * It has been a while since this program was compiled and run * on a non-LINUX platform, so buyer beware. * * MISC NOTES: * 1) This file looks best with a tabstop=4 setting. * * 2) This code was purposefully written in a brute-force style * so that it is easier to read no matter what the experience level of the end-user. * Also, by not using the full set of features within the C-language, * it is easier to transliterate to other languages (eg Fortran, whatever). * All the macro defines are directly included in this C source-code * file (instead of using #include statements) so that _all_ the * relavent code is in a _single_ file. This makes public distribution * and installation much easier. * * 3) Inside the e_mail_alert() routine there is a command to send an email * to the "opertor(s)". It is currently set up * with the email addresses for the GCN Operators (myself). * When you set this up for your own testing/operations, * change this hardwired address to your own address(es). * * AUTHOR: * Scott Barthelmy NASA-GSFC Code 661.0 * * MODS: * Cloned from socket_demo. 13Jun09 */ // Please note that most of these include_files are the ones // for SunOS Version 4.1.3 1 and LINUX. // On other machines with different operating systems (and even different // versions of SunOS), these include_files may have different names. // If, when you compile xml_sock_demo.c on your local platform, you find // that one or more of these files are "not found", then you will have // to explore your system's include_files to find the contents that are missing // during the compilation process. Unfortunately, there are too many // combinations of op_sys's for me to incorporate the appropriate include_files. #include // Standard i/o header file #include // File typedefs header file #include // Socket structure header file #include #include #include #ifndef LINUXPC #include #endif #include // For strcpy(), strcat(), etc #include #include #include #include #include #include #include #include // For the system() and exit() calls char *version = "xml_sock_demo Ver: 1.00 14 Jun 09"; #define TRUE 1 #define FALSE 0 // A few global variables for the xml_sock_demo program: int inetsd; // Socket descriptor FILE *lg; // Logfile stream pointer int errno; // Error return code number time_t tloc; // Seconds since machine epoch void brokenpipe(); // Clean up after getting a brokenpipe signal int server(); // Set up the server end of a connection (ie the customer end) int serr(); // Specialized error/status reporting routine void chk_imalive(); // Check and report an absence of Imalive/any packets rcvd void e_mail_alert(); // Send e-mail when no packets /*---------------------------------------------------------------------------*/ int main(argc, argv) int argc; // Number of cmdline arguments char *argv[]; // Pointers to all the cmdline arguments { int port; // Port number of the socket to connect to int bytes; // Number of bytes read from the socket char hostname[64]; // Machine host name char xmlbuf[30000]; // The packet data buffer int i; // General loop variable struct sigvec vec; // Signal vector structure time_t nowtime; // The current system time time_t last_nowtime; // The time of the last packet received if(argc != 2) { printf("USAGE: xml_sock_demo \n"); exit(1); } port = atoi(argv[1]); // Get the cmdline value for the port number if((lg = fopen("xml_sock_demo.log", "a")) == NULL) // Open for appending { printf("Failed to open logfile. Exiting.\n"); exit(2); } fprintf(lg,"\n===================== New Session ========================\n"); fprintf(lg,"%s\n",version); fprintf(lg,"port=%d\n",port); // The following sets up the signal catcher that is used to detect when // the socket has been broken. vec.sv_handler = brokenpipe; vec.sv_mask = sigmask(SIGPIPE); vec.sv_flags = 0; if(sigvec(SIGPIPE, &vec, (struct sigvec *) NULL) == -1) { printf( "ERR: Sigvec() did not work when setting up brokenpipe.\n"); fprintf(lg,"ERR: Sigvec() did not work when setting up brokenpipe.\n"); } bzero((char *)xmlbuf,sizeof(xmlbuf)); // Clears the buffer time(&last_nowtime); // Init this so it doesn't trip on the first loop // Set up the server to allow/wait for the incoming connection from GCN: // hostname is not needed for an INET connection, // port number is defined/initialized above, and // AF_INET is for an inet connection. // The return value is the socket descriptor. inetsd = server(hostname, port, AF_INET); // An infinite loop for reading the socket, // and handling the reconnection if there is a break. // Also in this loop is your site/instrument-specific code (if you want). while(1) { time(&nowtime); // Get the current system time if((nowtime - last_nowtime) > 62) { printf( "WARN: It has been greater than 60sec since the last (=%ld).\n", nowtime - last_nowtime); fprintf(lg,"WARN: It has been greater than 60sec since the last (=%ld).\n", nowtime - last_nowtime); last_nowtime = nowtime; // Reset it so we don't get a million error statements } if(inetsd <= 0) { // If the sock descriptor is zero or less, the socket connection has been // broken and the call to server() will try to re-establish the socket. inetsd = server(hostname, port, AF_INET); } else { // If socket is connected, read the socket for packet; if the socket // does not have a packet, continue to loop. if((bytes=read(inetsd,(char *)xmlbuf,sizeof(xmlbuf))) > 0) { printf("\nINFO: bytes=%d\n",bytes); //debug // Unlike the regular GCN socket method, do NOT echo the packet back to GCN. // There is no loop-back protocol for the xml_socket method. // The next 6 lines just print the xml messages to the screen and to the logfile. // You can do something else with them at this point (eg pass the buffer // to your XML parser/go-look-at-this-target_action). i = 0; while(xmlbuf[i]) { printf( "%c",xmlbuf[i]); fprintf(lg,"%c",xmlbuf[i]); i++; } if(i != bytes) { printf( "ERR: Num_written(=%d) not equal to num_read(=%d).\n",i,bytes); fprintf(lg,"ERR: Num_written(=%d) not equal to num_read(=%d).\n",i,bytes); } bzero((char *)xmlbuf,sizeof(xmlbuf)); // Clears the buffer last_nowtime = nowtime; // Record the time we got this packet } else if((bytes == 0) && (errno != EWOULDBLOCK)) { // The connection is broken printf( "bytes==0 && errno!=EWOULDBLOCK (errno=%d).\n",errno); fprintf(lg,"bytes==0 && errno!=EWOULDBLOCK (errno=%d).\n",errno); brokenpipe(); } } chk_imalive(0,nowtime); // See if the 10-min time limit has been exceeded // This sleep() is ONLY for this demo program. It should NOT be included // in your site's application code as it most definitely adds up to 1 sec // to the response time to the socket/packet servicing. sleep(1); // Give the CPU a rest } } /*---------------------------------------------------------------------------*/ void brokenpipe() /* clean up after getting a brokenpipe signal */ { printf( "brokenpipe(): If this is the last line, then the shutdown() to GCN hung.\n"); fprintf(lg,"brokenpipe(): If this is the last line, then the shutdown() to GCN hung.\n"); if(shutdown(inetsd,2) == -1) { perror("brokenpipe(): shutdown(): "); printf( "ERR: brokenpipe(): The shutdown did NOT work. errno=%d\n",errno); fprintf(lg,"ERR: brokenpipe(): The shutdown did NOT work. errno=%d\n",errno); } else { printf( "brokenpipe(): The shutdown worked OK.\n"); fprintf(lg,"brokenpipe(): The shutdown worked OK.\n"); } printf( "brokenpipe(): After the shutdown call; the shutdown didn't hang.\n"); fprintf(lg,"brokenpipe(): After the shutdown call; the shutdown didn't hang.\n"); if(close(inetsd)) { perror("brokenpipe(): close(): "); printf( "ERR: close() problem in brokenpipe(), errno=%d\n",errno); fprintf(lg,"ERR: close() problem in brokenpipe(), errno=%d\n",errno); } inetsd = -1; } /*--------------------------------------------------------------------------*/ int server(hostname, port, type) char *hostname; // Machine host name int port; // Port number unsigned short type; // Type of socket connection { int sock; // The connected sock descriptor int sd=-1; // The offerred sock descriptor int temp; // Dummy variable int saddrlen; // Socket address length + 2 char on=1; // Flag for nonblocking I/O struct sockaddr saddr; // Socket structure for UNIX struct sockaddr *psaddr; // Pointer to sin struct sockaddr_in sin; // Socket structure for inet temp = 0; // If the socket is for inet connection, then set up the sin structure. if(type == AF_INET) { bzero((char *) &sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); # ifdef LINUXPC sin.sin_port = htons(port); # else sin.sin_port = (unsigned short)port; # endif psaddr = (struct sockaddr *) &sin; saddrlen = sizeof(sin); } else { // If the socket is for UNIX connection, then set up the saddr structure. saddr.sa_family = AF_UNIX; strcpy(saddr.sa_data, hostname); psaddr = &saddr; saddrlen = strlen(saddr.sa_data) + 2; } printf("server(): type=%d (int)type=%d\n",type,(int)type); // Debug // Initiate the socket connection. if((sd = socket((int)type, SOCK_STREAM, IPPROTO_TCP)) < 0) { printf("server(): socket() errno=%d.\n",errno); return(serr(sd,"server(): socket.")); } // Bind the name to the socket. if(bind(sd, psaddr, saddrlen) == -1) { printf("server(): bind() errno=%d.\n",errno); return(serr(sd,"server(): bind.")); } // Listen for the socket connection from the GCN machine. if(listen(sd, 5)) { printf("server(): listen() errno=%d.\n",errno); return(serr(sd,"server(): listen.")); } // Accept the socket connection from the GCN machine (the client). if((sock = accept(sd, &saddr, &temp)) < 0) { printf("server(): accept() errno=%d.\n",errno); return(serr(sd,"server(): accept.")); } // Now that we have the 'sock' descriptor, we can close the 'sd' descriptor from socket(). if(close(sd)) { printf("server(): close() errno=%d.\n",errno); // No real need to return() here, we have 'sock', so continue on. } // Make the connection nonblocking I/O. if(ioctl(sock, FIONBIO, &on) < 0) { printf("server(): ioctl() errno=%d.\n",errno); return(serr(sock,"server(): ioctl.")); } printf( "INFO: server(): the server is up."); fprintf(lg,"INFO: server(): the server is up."); return(sock); } /*---------------------------------------------------------------------------*/ int serr(fds, call) /* special error reporting and socket connection */ int fds; char *call; { if(fds > 0) if(close(fds)) { perror("serr(): close(): "); printf("close() problem in serr(), errno=%d.\n",errno); } printf( "%s\n",call); fprintf(lg,"%s\n",call); return(-1); } /*---------------------------------------------------------------------------*/ // This technique of trying to "notice" that imalives/anything have not arrived // for a long period of time works only some of the time. It depends on what // caused the interruption. Some forms of broken socket connections hang // in the "elseif" shutdown() system call, and so the program never actually // gets to the point of calling this routine to check the time and // send a message. void chk_imalive(which,ctloc) /* CHecK for IMALIVE pkt arrival gaps */ int which; // Is this an imalive timestamp or delta check call time_t ctloc; // Imalive timestamp or current time [sec since epoch] { static time_t last_ctloc; // Timestamp of last imalive received static int imalive_state = 0; // State of the checking machine char buf[32]; // Tmp place for date string modifications time_t nowtime; // The system machine time this instant char *ctime(); // Convert the sec-since-epoch to ASCII switch(imalive_state) { case 0: // Do something ONLY when we get that first Imalive/any timestamp. if(which) // Ctloc is time of most recent imalive { last_ctloc = ctloc; imalive_state = 1; // Go to testing deltas } break; case 1: // Now that we have an Imalive/any timestamp, check it against the // current time, and update the timestamp when new ones come in. if(which) // Ctloc is time of most recent imalive last_ctloc = ctloc; else if((ctloc - last_ctloc) > 600) { time(&nowtime); // Get system time for mesg timestamp strcpy(buf,ctime((time_t *)&nowtime)); buf[24] = '\0'; // Overwrite the newline with a null printf( "ERR: %s : No packets for >600sec.\n",buf); fprintf(lg,"ERR: %s : No packets for >600sec.\n",buf); e_mail_alert(1); imalive_state = 2; } break; case 2: // Waiting for a resumption of imalives/any so we can go back to testing // and e-mailing if they should stop for a 2nd (n-th) time. if(which) // Ctloc is time of the resumed imalive { last_ctloc = ctloc; time(&nowtime); // Get system time for mesg timestamp strcpy(buf,ctime((time_t *)&nowtime)); buf[24] = '\0'; // Overwrite the newline with a null printf( "OK: %s : Packets have resumed.\n",buf); fprintf(lg,"OK: %s : Packets have resumed.\n",buf); e_mail_alert(0); imalive_state = 1; // Go back to testing } break; default: time(&nowtime); // Get system time for mesg timestamp strcpy(buf,ctime((time_t *)&nowtime)); buf[24] = '\0'; // Overwrite the newline with a null printf( "ERR: %s : Bad imalive_state(=%d)\n",buf,imalive_state); fprintf(lg,"ERR: %s : Bad imalive_state(=%d)\n",buf,imalive_state); imalive_state = 0; // Reset to the initial state break; } } /*---------------------------------------------------------------------------*/ void e_mail_alert(which) /* send an E_MAIL ALERT if no pkts in 600sec */ int which; // End or re-starting of imalives { int rtn; // Return value from the system() call FILE *etmp; // The temp email scratch file stream pointer time_t nowtime; // The Sun machine time this instant char *ctime(); // Convert the sec-since-epoch to ASCII char buf[32]; // Tmp place for date string modifications static char cmd_str[256]; // Place to build the system() cmd string time(&nowtime); // Get the system time for the notice timestamp strcpy(buf,ctime((time_t *)&nowtime)); buf[24] = '\0'; // Overwrite the newline with a null if((etmp=fopen("e_me_xtmp", "w")) == NULL) { printf( "ERR: %s : Can't open scratch e_mail_alert file.\n",buf); fprintf(lg,"ERR: %s : Can't open scratch e_mail_alert file.\n",buf); return; } if(which) fprintf(etmp,"TITLE: xml_sock_demo GCN NO XML PACKETS ALERT\n"); else fprintf(etmp,"TITLE: xml_sock_demo GCN XML PACKETS RESUMED\n"); fprintf(etmp,"NOTICE_DATE: %s \n", buf); if(fclose(etmp)) // Close the email file so the filesys is updated { printf( "ERR: %s : Problem closing scratch email_me file.\n",buf); fprintf(lg,"ERR: %s : Problem closing scratch email_me file.\n",buf); } cmd_str[0] = 0; // Clear string buffer before building new string if(which) strcat(cmd_str,"mail -s GCN_NO_XML_PKT_ALERT "); else strcat(cmd_str,"mail -s GCN_XML_PKT_RESUMED "); // NOTICE TO GCN SOCKET SITES: Please don't forget to change this // e-mail address to your address(es). strcat(cmd_str,"scott@lheamail"); strcat(cmd_str," < "); strcat(cmd_str,"e_me_xtmp"); strcat(cmd_str," &"); printf( "%s\n",cmd_str); fprintf(lg,"%s\n",cmd_str); rtn = system(cmd_str); printf( "%sSystem() rtn=%d.\n",rtn?"ERR: ":"",rtn); fprintf(lg,"%sSystem() rtn=%d.\n",rtn?"ERR: ":"",rtn); }