/************************************************************************\ |* CIS 432 Vahid Moghaddasi *| |* (Socket Programming) *| |* >> A Simple PING Clone << *| +------------------------------------------------------------------------+ | | | NAME: pingserver.c | | PLATFORMS: tested on Linux Redhat 5.x, 6.x , Solaris 2.51, | | Solaris 2.6, HP-UX 10.20, AIX 4.1.2 | | SYNOPSIS: pingserver portnum | | DESCRIPTION: | | A simple ping clone. Accepts tcp and udp requests on port | | portnum and sends an anwer back to the client. The answer is the | | number of 'pings' the server received since it has been up. | | | | NOTES: | | This program fork()s a child process to handle the UDP requests. | | The TCP requests are handled by the parent process. If one of | | the two processes terminates due to a caught error, the other | | process keeps on running. | | The number (nn) of pings is stored in a 'database' file DBFILE. | | At start any existing file with the name DBFILE will be truncated| | to zero length. | | After receiving INT_MAX pings, the server resets nn to zero and | | informs the client about this. | | To function properly, the program must be able to create a file | | with the name DBFILE, it must have the permission to access the | | port given on the command line, and be able to perform all | | necessary socket operations. | | If there are QLEN TCP requests waiting, all further TCP connects | | are rejected. | | Since all requests from a client involve accessing the database | | file DBFILE (which can only be done by one process at a time) | | and not much more, it does not make sense to implement the server| | concurrent connection-oriented (TCP part). | | | | gcc -lsocket -lnsl pingserver.c | | | \************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #define QLEN 5 /* length of queue for waiting TCP requests */ #define DEBUG 0 /* set to '1' for diagnostic messages */ #define DBFILE "./ping.db" /* file name of the 'database' */ static int s; /* socket on server side */ static FILE *ping_db; /* 'database' to store number of pings */ typedef void (*sighandler_t)(int); extern void handler(); extern void ServerShutdown(); void handler(int sig) /* a simple signal handler */ { ServerShutdown(); signal(sig, (sighandler_t)handler); exit(-2); } void status (int return_value, char *description) /* display error/diagnostic messages, handle errors */ { /* This also saves me from writing to many comments... :-) */ if (return_value) /* function call successful, output only if DEBUG=1 */ { if (DEBUG) printf("%s ... OK (%d)\n", description, return_value); } else /* function call returned with error */ { fprintf(stderr,"ERROR: could not %s!\n", description); ServerShutdown(); exit(-1); } } void ServerShutdown() /* close socket, delete file */ { if (DEBUG) printf("server shut down...\n"); if (close(s)) perror("ERROR: Failed to close socket"); fclose(ping_db); /* TRY to close file, maybe it doesn't exist */ if (remove(DBFILE)) /* or the other process is still using it */ if(errno!=ENOENT) perror("ERROR: Failed to delete file"); } int lockfile(short command) { /* set or clear the (advisory) exclusive lock flag for DBFILE */ struct flock file_lock; memset(&file_lock, 0, sizeof(file_lock)); file_lock.l_type = command; file_lock.l_whence=file_lock.l_start=file_lock.l_len=0; return (fcntl(fileno(ping_db),F_SETLKW,&file_lock)); } void main (int argc, char *argv[]) { int len; struct protoent *pp; struct sockaddr_in sin, from; int port; /* local port to bind to */ char buf[20]; /* buffer to hold received/sent data */ int ssock; /* socket to handle incoming TCP requests */ int nn; /* number of pings received */ int pid; /* process ID of the fork()ed child */ if (signal(SIGINT, SIG_IGN)!=SIG_IGN) /* Install signal handler */ signal(SIGINT,(sighandler_t)handler); /* If one of the two server processes terminates because of an error, the other process will not be affected */ if (argc!=2) /* abort if not exactly one argument */ { fprintf(stderr, "usage: %s portnum\n", argv[0]); exit(1); } /* initialize db file */ status((ping_db=fopen(DBFILE,"w+"))!=NULL, "open/create database file"); status(fprintf(ping_db,"%d\n",0)>0, "write initial value to file"); status(fflush(ping_db)==0, "flush data to file"); status(port=atoi(argv[1]), "read port number from command line"); memset(&sin, 0, sizeof(sin)); /* init sockaddr_in structure */ sin.sin_family = AF_INET; /* fill struct with data */ sin.sin_addr.s_addr= INADDR_ANY; /* unrestricted access */ sin.sin_port = htons(port); /* port number in network order */ len=sizeof(from); /* size of sockaddr_in */ memset(&buf, 0, sizeof(buf)); /* clear the buffer */ /* end of protocol independent initialisation */ status((pid=fork())>=0, "fork a new process"); if (pid==0){ /* child handles UDP requests */ status((pp=getprotobyname("udp"))!=NULL, "get protocol by name [UDP]"); status((s=socket(PF_INET, SOCK_DGRAM, pp->p_proto))>=0, "create socket [UDP]"); status(bind(s, (struct sockaddr *)&sin, sizeof(sin))==0, "bind to port [UDP]"); if (DEBUG) printf("Listening on port %d for udp requests ...\n", port); while("P"!="NP"){ /*knock on wood... */ status(recvfrom(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, &len)>0, "receive message from socket [UDP]"); if (DEBUG) printf("received <%s>\n",buf); status(lockfile(F_WRLCK)!=-1, "lock file [UDP]"); rewind(ping_db); status(fscanf(ping_db, "%d", &nn)>0, "read data from file [UDP]"); ++nn; /* count one ping */ if (nn==INT_MAX) nn=0; /* if nn to large then reset */ if (DEBUG) printf("[udp] send <%d>\n",nn); rewind(ping_db); status((fprintf(ping_db, "%d\n", nn)>0), "write data to file [UDP]"); status(fflush(ping_db)==0, "flush data to file [UDP]"); status(lockfile(F_UNLCK)!=-1, "unlock file [UDP]"); if (nn>0) sprintf(buf, "%d", nn); /* compose message for client */ else sprintf(buf, "%d(RESET!)",INT_MAX); status((sendto(s, buf, sizeof(buf), 0, (struct sockaddr*)&from, sizeof(from))>0), "send reply to the client [UDP]"); } /* end of UDP server loop */ } /* end of child process */ else{ /* parent handles TCP requests */ status((pp=getprotobyname("tcp"))!=NULL, "get protocol by name [TCP]"); status((s=socket(PF_INET, SOCK_STREAM, pp->p_proto))>=0, "create socket [TCP]"); status(bind(s, (struct sockaddr *)&sin, sizeof(sin))==0, "bind to port [TCP]"); status(listen(s, QLEN)==0, "listen on socket for connection [TCP]"); while(1){ status((ssock=accept(s, (struct sockaddr *)&from, &len))>=0, "accept connection"); status(lockfile(F_WRLCK)!=-1, "lock file [TCP]"); rewind(ping_db); status(fscanf(ping_db, "%d", &nn)>0, "read data from file [TCP]"); ++nn; /* count one ping */ if (nn==INT_MAX) nn=0; /* if nn to large then reset */ if (DEBUG) printf("[tcp]send <%d>\n", nn); rewind(ping_db); status(fprintf(ping_db, "%d\n", nn)>0, "write data to file [TCP]"); status(fflush(ping_db)==0, "flush data to file [TCP]"); status(lockfile(F_UNLCK)!=-1, "lock file [TCP]"); if (nn>0) sprintf(buf, "%d", nn); /* compose message for client */ else sprintf(buf, "%d (RESET!)",INT_MAX); status(write(ssock, buf, sizeof(buf))==sizeof(buf), "write to socket[TCP]"); status(close(ssock)==0, "close socket [TCP]"); } /* end of tcp server loop */ } /* end of parent process */ } /* END OF pingserver.c */