600 lines
18 KiB
C++
600 lines
18 KiB
C++
|
/************************************************************************/
|
||
|
|
||
|
#ifdef __GNUC__
|
||
|
#ifndef _GNU_SOURCE
|
||
|
#define _GNU_SOURCE /* for strsignal() */
|
||
|
#endif
|
||
|
#endif
|
||
|
|
||
|
#include <stdio.h>
|
||
|
#include <string.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <stddef.h>
|
||
|
#include <unistd.h>
|
||
|
#include <fcntl.h>
|
||
|
#include <time.h>
|
||
|
#include <signal.h>
|
||
|
#include <syslog.h>
|
||
|
#include <errno.h>
|
||
|
#include <sys/types.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <string>
|
||
|
#include <zmqpp/zmqpp.hpp>
|
||
|
|
||
|
/*************************************************************************/
|
||
|
|
||
|
/* global variables and constants */
|
||
|
|
||
|
volatile sig_atomic_t gGracefulShutdown = 0;
|
||
|
volatile sig_atomic_t gCaughtHupSignal = 0;
|
||
|
|
||
|
int gLockFileDesc = -1;
|
||
|
int gMasterSocket = -1;
|
||
|
|
||
|
/* the 'well-known' port on which our server will be listening */
|
||
|
|
||
|
const int gHelloServerPort = 30153;
|
||
|
|
||
|
/* the path to our lock file */
|
||
|
|
||
|
const char *const gLockFilePath = "/tmp/helloserver.pid";
|
||
|
|
||
|
/*************************************************************************/
|
||
|
|
||
|
/* prototypes */
|
||
|
|
||
|
int BecomeDaemonProcess(const char *const lockFileName, const char *const logPrefix, const int logLevel, int *const lockFileDesc, int *const thisPID);
|
||
|
int ConfigureSignalHandlers(void);
|
||
|
void FatalSigHandler(int sig);
|
||
|
void TermHandler(int sig);
|
||
|
void HupHandler(int sig);
|
||
|
void Usr1Handler(int sig);
|
||
|
void TidyUp(void);
|
||
|
|
||
|
/*************************************************************************/
|
||
|
|
||
|
/* an idea from 'Advanced Programming in the Unix Environment'
|
||
|
Stevens 1993 - see BecomeDaemonProcess() */
|
||
|
|
||
|
#define OPEN_MAX_GUESS 256
|
||
|
|
||
|
/*************************************************************************/
|
||
|
|
||
|
int main(void /*int argc,char *argv[]*/)
|
||
|
{
|
||
|
using namespace std;
|
||
|
int result;
|
||
|
pid_t daemonPID;
|
||
|
|
||
|
/*************************************************************/
|
||
|
/* perhaps at this stage you would read a configuration file */
|
||
|
/*************************************************************/
|
||
|
|
||
|
/* the first task is to put ourself into the background (i.e
|
||
|
become a daemon. */
|
||
|
|
||
|
if ((result = BecomeDaemonProcess(gLockFilePath, "beehub_server", LOG_DEBUG, &gLockFileDesc, &daemonPID))<0)
|
||
|
{
|
||
|
perror("Failed to become daemon process");
|
||
|
exit(result);
|
||
|
}
|
||
|
|
||
|
/* set up signal processing */
|
||
|
|
||
|
if ((result = ConfigureSignalHandlers())<0)
|
||
|
{
|
||
|
syslog(LOG_LOCAL0 | LOG_INFO, "ConfigureSignalHandlers failed, errno=%d", errno);
|
||
|
unlink("/var/log/helloserver.pid");
|
||
|
exit(result);
|
||
|
}
|
||
|
|
||
|
/* now we must create a socket and bind it to a port */
|
||
|
|
||
|
// if ((result = BindPassiveSocket(INADDR_ANY, gHelloServerPort, &gMasterSocket))<0)
|
||
|
// {
|
||
|
// syslog(LOG_LOCAL0 | LOG_INFO, "BindPassiveSocket failed, errno=%d", errno);
|
||
|
// unlink("/var/log/helloserver.pid");
|
||
|
// exit(result);
|
||
|
// }
|
||
|
|
||
|
/* now enter an infinite loop handling connections */
|
||
|
const std::string endpoint = "tcp://*:4242";
|
||
|
|
||
|
// initialize the 0MQ context
|
||
|
zmqpp::context context;
|
||
|
|
||
|
// generate a pull socket
|
||
|
zmqpp::socket_type type = zmqpp::socket_type::pull;
|
||
|
zmqpp::socket socket (context, type);
|
||
|
|
||
|
// bind to the socket
|
||
|
cout << "Binding to " << endpoint << "..." << endl;
|
||
|
socket.bind(endpoint);
|
||
|
|
||
|
do
|
||
|
{
|
||
|
// 等待下一个请求
|
||
|
zmqpp::message request;
|
||
|
// decompose the message
|
||
|
socket.receive(request);
|
||
|
|
||
|
string text;
|
||
|
request >> text ;
|
||
|
std::string msg = "Received request: [" + text + "]";
|
||
|
|
||
|
//syslog(LOG_LOCAL0 | LOG_INFO, msg.c_str());
|
||
|
|
||
|
/* the next conditional will be true if we caught signal SIGUSR1 */
|
||
|
|
||
|
if ((gGracefulShutdown == 1) && (gCaughtHupSignal == 0))
|
||
|
break;
|
||
|
|
||
|
/* if we caught SIGHUP, then start handling connections again */
|
||
|
|
||
|
gGracefulShutdown = gCaughtHupSignal = 0;
|
||
|
} while (1);
|
||
|
|
||
|
TidyUp(); /* close the socket and kill the lock file */
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
BecomeDaemonProcess
|
||
|
|
||
|
Fork the process into the background, make a lock file, and open the
|
||
|
system log.
|
||
|
|
||
|
Inputs:
|
||
|
|
||
|
lockFileName I the path to the lock file
|
||
|
|
||
|
logPrefix I the string that will appear at the
|
||
|
start of all log messages
|
||
|
|
||
|
logLevel I the logging level for this process
|
||
|
|
||
|
lockFileDesc O the file descriptor of the lock file
|
||
|
|
||
|
thisPID O the PID of this process after fork()
|
||
|
has placed it in the background
|
||
|
Returns:
|
||
|
|
||
|
status code indicating success - 0 = success
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
int BecomeDaemonProcess(const char *const lockFileName,
|
||
|
const char *const logPrefix,
|
||
|
const int logLevel,
|
||
|
int *const lockFileDesc,
|
||
|
pid_t *const thisPID)
|
||
|
{
|
||
|
int curPID, stdioFD, lockResult, killResult, lockFD, i,
|
||
|
numFiles;
|
||
|
char pidBuf[17], *lfs, pidStr[7];
|
||
|
FILE *lfp;
|
||
|
unsigned long lockPID;
|
||
|
struct flock exclusiveLock;
|
||
|
|
||
|
/* set our current working directory to root to avoid tying up
|
||
|
any directories. In a real server, we might later change to
|
||
|
another directory and call chroot() for security purposes
|
||
|
(especially if we are writing something that serves files */
|
||
|
|
||
|
chdir("/");
|
||
|
|
||
|
/* try to grab the lock file */
|
||
|
|
||
|
lockFD = open(lockFileName, O_RDWR | O_CREAT | O_EXCL, 0644);
|
||
|
|
||
|
if (lockFD == -1)
|
||
|
{
|
||
|
/* Perhaps the lock file already exists. Try to open it */
|
||
|
|
||
|
lfp = fopen(lockFileName, "r");
|
||
|
|
||
|
if (lfp == 0) /* Game over. Bail out */
|
||
|
{
|
||
|
perror("Can't get lockfile");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* We opened the lockfile. Our lockfiles store the daemon PID in them.
|
||
|
Find out what that PID is */
|
||
|
|
||
|
lfs = fgets(pidBuf, 16, lfp);
|
||
|
|
||
|
if (lfs != 0)
|
||
|
{
|
||
|
if (pidBuf[strlen(pidBuf) - 1] == '\n') /* strip linefeed */
|
||
|
pidBuf[strlen(pidBuf) - 1] = 0;
|
||
|
|
||
|
lockPID = strtoul(pidBuf, (char**)0, 10);
|
||
|
|
||
|
/* see if that process is running. Signal 0 in kill(2) doesn't
|
||
|
send a signal, but still performs error checking */
|
||
|
|
||
|
killResult = kill(lockPID, 0);
|
||
|
|
||
|
if (killResult == 0)
|
||
|
{
|
||
|
printf("\n\nERROR\n\nA lock file %s has been detected. It appears it is owned\nby the (active) process with PID %ld.\n\n", lockFileName, lockPID);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (errno == ESRCH) /* non-existent process */
|
||
|
{
|
||
|
printf("\n\nERROR\n\nA lock file %s has been detected. It appears it is owned\nby the process with PID %ld, which is now defunct. Delete the lock file\nand try again.\n\n", lockFileName, lockPID);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
perror("Could not acquire exclusive lock on lock file");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
perror("Could not read lock file");
|
||
|
|
||
|
fclose(lfp);
|
||
|
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* we have got this far so we have acquired access to the lock file.
|
||
|
Set a lock on it */
|
||
|
|
||
|
exclusiveLock.l_type = F_WRLCK; /* exclusive write lock */
|
||
|
exclusiveLock.l_whence = SEEK_SET; /* use start and len */
|
||
|
exclusiveLock.l_len = exclusiveLock.l_start = 0; /* whole file */
|
||
|
exclusiveLock.l_pid = 0; /* don't care about this */
|
||
|
lockResult = fcntl(lockFD, F_SETLK, &exclusiveLock);
|
||
|
|
||
|
if (lockResult<0) /* can't get a lock */
|
||
|
{
|
||
|
close(lockFD);
|
||
|
perror("Can't get lockfile");
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
/* now we move ourselves into the background and become a daemon.
|
||
|
Remember that fork() inherits open file descriptors among others so
|
||
|
our lock file is still valid */
|
||
|
|
||
|
curPID = fork();
|
||
|
|
||
|
switch (curPID)
|
||
|
{
|
||
|
case 0: /* we are the child process */
|
||
|
break;
|
||
|
|
||
|
case -1: /* error - bail out (fork failing is very bad) */
|
||
|
fprintf(stderr, "Error: initial fork failed: %s\n",
|
||
|
strerror(errno));
|
||
|
return -1;
|
||
|
break;
|
||
|
|
||
|
default: /* we are the parent, so exit */
|
||
|
exit(0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* make the process a session and process group leader. This simplifies
|
||
|
job control if we are spawning child servers, and starts work on
|
||
|
detaching us from a controlling TTY */
|
||
|
|
||
|
if (setsid()<0)
|
||
|
return -1;
|
||
|
|
||
|
/* ignore SIGHUP as this signal is sent when session leader terminates */
|
||
|
|
||
|
signal(SIGHUP, SIG_IGN);
|
||
|
|
||
|
/* fork again to let session group leader exit. Now we can't
|
||
|
have a controlling TTY. */
|
||
|
|
||
|
curPID = fork();
|
||
|
|
||
|
switch (curPID) /* return codes as before */
|
||
|
{
|
||
|
case 0:
|
||
|
break;
|
||
|
|
||
|
case -1:
|
||
|
return -1;
|
||
|
break;
|
||
|
|
||
|
default:
|
||
|
exit(0);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* log PID to lock file */
|
||
|
|
||
|
/* truncate just in case file already existed */
|
||
|
|
||
|
if (ftruncate(lockFD, 0)<0)
|
||
|
return -1;
|
||
|
|
||
|
/* store our PID. Then we can kill the daemon with
|
||
|
kill `cat <lockfile>` where <lockfile> is the path to our
|
||
|
lockfile */
|
||
|
|
||
|
sprintf(pidStr, "%d\n", (int)getpid());
|
||
|
|
||
|
write(lockFD, pidStr, strlen(pidStr));
|
||
|
|
||
|
*lockFileDesc = lockFD; /* return lock file descriptor to caller */
|
||
|
|
||
|
/* close open file descriptors */
|
||
|
|
||
|
numFiles = sysconf(_SC_OPEN_MAX); /* how many file descriptors? */
|
||
|
|
||
|
if (numFiles<0) /* sysconf has returned an indeterminate value */
|
||
|
numFiles = OPEN_MAX_GUESS; /* from Stevens '93 */
|
||
|
|
||
|
for (i = numFiles - 1; i >= 0; --i) /* close all open files except lock */
|
||
|
{
|
||
|
if (i != lockFD) /* don't close the lock file! */
|
||
|
close(i);
|
||
|
}
|
||
|
|
||
|
/* stdin/out/err to /dev/null */
|
||
|
|
||
|
umask(0); /* set this to whatever is appropriate for you */
|
||
|
|
||
|
stdioFD = open("/dev/null", O_RDWR); /* fd 0 = stdin */
|
||
|
dup(stdioFD); /* fd 1 = stdout */
|
||
|
dup(stdioFD); /* fd 2 = stderr */
|
||
|
|
||
|
/* open the system log - here we are using the LOCAL0 facility */
|
||
|
|
||
|
openlog(logPrefix, LOG_PID | LOG_CONS | LOG_NDELAY | LOG_NOWAIT, LOG_LOCAL0);
|
||
|
|
||
|
(void)setlogmask(LOG_UPTO(logLevel)); /* set logging level */
|
||
|
|
||
|
/* put server into its own process group. If this process now spawns
|
||
|
child processes, a signal sent to the parent will be propagated
|
||
|
to the children */
|
||
|
|
||
|
setpgrp();
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
ConfigureSignalHandlers
|
||
|
|
||
|
Set up the behaviour of the various signal handlers for this process.
|
||
|
Signals are divided into three groups: those we can ignore; those that
|
||
|
cause a fatal error but in which we are not particularly interested and
|
||
|
those that are used to control the server daemon. We don't bother with
|
||
|
the new real-time signals under Linux since these are blocked by default
|
||
|
anyway.
|
||
|
|
||
|
Returns: none
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
int ConfigureSignalHandlers(void)
|
||
|
{
|
||
|
struct sigaction sighupSA, sigusr1SA, sigtermSA;
|
||
|
|
||
|
/* ignore several signals because they do not concern us. In a
|
||
|
production server, SIGPIPE would have to be handled as this
|
||
|
is raised when attempting to write to a socket that has
|
||
|
been closed or has gone away (for example if the client has
|
||
|
crashed). SIGURG is used to handle out-of-band data. SIGIO
|
||
|
is used to handle asynchronous I/O. SIGCHLD is very important
|
||
|
if the server has forked any child processes. */
|
||
|
|
||
|
signal(SIGUSR2, SIG_IGN);
|
||
|
signal(SIGPIPE, SIG_IGN);
|
||
|
signal(SIGALRM, SIG_IGN);
|
||
|
signal(SIGTSTP, SIG_IGN);
|
||
|
signal(SIGTTIN, SIG_IGN);
|
||
|
signal(SIGTTOU, SIG_IGN);
|
||
|
signal(SIGURG, SIG_IGN);
|
||
|
signal(SIGXCPU, SIG_IGN);
|
||
|
signal(SIGXFSZ, SIG_IGN);
|
||
|
signal(SIGVTALRM, SIG_IGN);
|
||
|
signal(SIGPROF, SIG_IGN);
|
||
|
signal(SIGIO, SIG_IGN);
|
||
|
signal(SIGCHLD, SIG_IGN);
|
||
|
|
||
|
/* these signals mainly indicate fault conditions and should be logged.
|
||
|
Note we catch SIGCONT, which is used for a type of job control that
|
||
|
is usually inapplicable to a daemon process. We don't do anyting to
|
||
|
SIGSTOP since this signal can't be caught or ignored. SIGEMT is not
|
||
|
supported under Linux as of kernel v2.4 */
|
||
|
|
||
|
signal(SIGQUIT, FatalSigHandler);
|
||
|
signal(SIGILL, FatalSigHandler);
|
||
|
signal(SIGTRAP, FatalSigHandler);
|
||
|
signal(SIGABRT, FatalSigHandler);
|
||
|
signal(SIGIOT, FatalSigHandler);
|
||
|
signal(SIGBUS, FatalSigHandler);
|
||
|
#ifdef SIGEMT /* this is not defined under Linux */
|
||
|
signal(SIGEMT, FatalSigHandler);
|
||
|
#endif
|
||
|
signal(SIGFPE, FatalSigHandler);
|
||
|
signal(SIGSEGV, FatalSigHandler);
|
||
|
signal(SIGSTKFLT, FatalSigHandler);
|
||
|
signal(SIGCONT, FatalSigHandler);
|
||
|
signal(SIGPWR, FatalSigHandler);
|
||
|
signal(SIGSYS, FatalSigHandler);
|
||
|
|
||
|
/* these handlers are important for control of the daemon process */
|
||
|
|
||
|
/* TERM - shut down immediately */
|
||
|
|
||
|
sigtermSA.sa_handler = TermHandler;
|
||
|
sigemptyset(&sigtermSA.sa_mask);
|
||
|
sigtermSA.sa_flags = 0;
|
||
|
sigaction(SIGTERM, &sigtermSA, NULL);
|
||
|
|
||
|
/* USR1 - finish serving the current connection and then close down
|
||
|
(graceful shutdown) */
|
||
|
|
||
|
sigusr1SA.sa_handler = Usr1Handler;
|
||
|
sigemptyset(&sigusr1SA.sa_mask);
|
||
|
sigusr1SA.sa_flags = 0;
|
||
|
sigaction(SIGUSR1, &sigusr1SA, NULL);
|
||
|
|
||
|
/* HUP - finish serving the current connection and then restart
|
||
|
connection handling. This could be used to force a re-read of
|
||
|
a configuration file for example */
|
||
|
|
||
|
sighupSA.sa_handler = HupHandler;
|
||
|
sigemptyset(&sighupSA.sa_mask);
|
||
|
sighupSA.sa_flags = 0;
|
||
|
sigaction(SIGHUP, &sighupSA, NULL);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
FatalSigHandler
|
||
|
|
||
|
General catch-all signal handler to mop up signals that we aren't
|
||
|
especially interested in. It shouldn't be called (if it is it
|
||
|
probably indicates an error). It simply dumps a report of the
|
||
|
signal to the log and dies. Note the strsignal() function may not be
|
||
|
available on all platform/compiler combinations.
|
||
|
|
||
|
sig I the signal number
|
||
|
|
||
|
Returns: none
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
void FatalSigHandler(int sig)
|
||
|
{
|
||
|
#ifdef _GNU_SOURCE
|
||
|
syslog(LOG_LOCAL0 | LOG_INFO, "caught signal: %s - exiting", strsignal(sig));
|
||
|
#else
|
||
|
syslog(LOG_LOCAL0 | LOG_INFO, "caught signal: %d - exiting", sig);
|
||
|
#endif
|
||
|
|
||
|
closelog();
|
||
|
TidyUp();
|
||
|
_exit(0);
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
TermHandler
|
||
|
|
||
|
Handler for the SIGTERM signal. It cleans up the lock file and
|
||
|
closes the server's master socket, then immediately exits.
|
||
|
|
||
|
sig I the signal number (SIGTERM)
|
||
|
|
||
|
Returns: none
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
void TermHandler(int sig)
|
||
|
{
|
||
|
TidyUp();
|
||
|
_exit(0);
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
HupHandler
|
||
|
|
||
|
Handler for the SIGHUP signal. It sets the gGracefulShutdown and
|
||
|
gCaughtHupSignal flags. The latter is used to distinguish this from
|
||
|
catching SIGUSR1. Typically in real-world servers, SIGHUP is used to
|
||
|
tell the server that it should re-read its configuration file. Many
|
||
|
important daemons do this, including syslog and xinetd (under Linux).
|
||
|
|
||
|
sig I the signal number (SIGTERM)
|
||
|
|
||
|
Returns: none
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
void HupHandler(int sig)
|
||
|
{
|
||
|
syslog(LOG_LOCAL0 | LOG_INFO, "caught SIGHUP");
|
||
|
gGracefulShutdown = 1;
|
||
|
gCaughtHupSignal = 1;
|
||
|
|
||
|
/****************************************************************/
|
||
|
/* perhaps at this point you would re-read a configuration file */
|
||
|
/****************************************************************/
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
Usr1Handler
|
||
|
|
||
|
Handler for the SIGUSR1 signal. This sets the gGracefulShutdown flag,
|
||
|
which permits active connections to run to completion before shutdown.
|
||
|
It is therefore a more friendly way to shut down the server than
|
||
|
sending SIGTERM.
|
||
|
|
||
|
sig I the signal number (SIGTERM)
|
||
|
|
||
|
Returns: none
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
void Usr1Handler(int sig)
|
||
|
{
|
||
|
syslog(LOG_LOCAL0 | LOG_INFO, "caught SIGUSR1 - soft shutdown");
|
||
|
gGracefulShutdown = 1;
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**************************************************************************/
|
||
|
/***************************************************************************
|
||
|
|
||
|
TidyUp
|
||
|
|
||
|
Dispose of system resources. This function is not strictly necessary,
|
||
|
as UNIX processes clean up after themselves (heap memory is freed,
|
||
|
file descriptors are closed, etc.) but it is good practice to
|
||
|
explicitly release that which you have allocated.
|
||
|
|
||
|
Returns: none
|
||
|
|
||
|
***************************************************************************/
|
||
|
/**************************************************************************/
|
||
|
|
||
|
void TidyUp(void)
|
||
|
{
|
||
|
if (gLockFileDesc != -1)
|
||
|
{
|
||
|
close(gLockFileDesc);
|
||
|
unlink(gLockFilePath);
|
||
|
gLockFileDesc = -1;
|
||
|
}
|
||
|
|
||
|
if (gMasterSocket != -1)
|
||
|
{
|
||
|
close(gMasterSocket);
|
||
|
gMasterSocket = -1;
|
||
|
}
|
||
|
}
|