/*! \file server.cpp
This is the Server class implementation.
It implements a standard non-blocking select() server.
\author Jon A. Lambert
\date 05/02/2003
\version 0.30
*/
#include "sysconfig.h"
#include "server.h"
#include "event.h"
/*!
Constructor for Server
*/
Server::Server(EventQueue& r_inque, EventQueue& r_outque, Log& r_lgfile)
: mShutdown(false), mrLog(r_lgfile),
mrInQueue(r_inque), mrOutQueue(r_outque) {
}
/*!
Destructor for Server
\note
If we shutdown normally we should not have any connections in our list
but we will attempt to delete them anyways. Maybe we should assert this.
*/
Server::~Server() {
ConnList::iterator i;
for (i = mConns.begin(); i != mConns.end(); i++) {
delete (*i);
}
}
/*!
Boot initializes the Server and gets it ready to accept incoming
Connections.
\return true if server boots correctly, false if an error occurs.
*/
bool Server::Boot(unsigned short port) {
// Get a TCP protocol socket for the server to listen on.
mAcceptor = socket (AF_INET, SOCK_STREAM, 0);
if (mAcceptor == INVALID_SOCKET) {
ServerLog().Write("ERROR-Server(socket): %d", WSAGetLastError());
return false;
}
// Set the listening socket to be non-blocking
if (!SetNonBlocking(mAcceptor))
return false;
// Set up listening socket to allow local address reuse
int sockopt_val = 1; // Non-zero means turn on for boolean options,
// some options require other types and values.
if (setsockopt(mAcceptor, SOL_SOCKET, SO_REUSEADDR, (char *)&sockopt_val,
sizeof(sockopt_val)) == SOCKET_ERROR) {
ServerLog().Write("ERROR-Server(setsockopt): %d",WSAGetLastError());
closesocket(mAcceptor);
return false;
}
// Associate our local address with this socket
mTo.sin_family = AF_INET; // protocol - internet
mTo.sin_addr.s_addr = INADDR_ANY; // accept on whatever IP we're set up as
mTo.sin_port = htons(port); // port must be in network byte order
if (bind(mAcceptor, (struct sockaddr *)&mTo, sizeof(mTo)) == SOCKET_ERROR) {
ServerLog().Write("ERROR-Server(bind): %d",WSAGetLastError());
closesocket(mAcceptor);
return false;
}
// Let's start listening for incoming connections
if (listen(mAcceptor, 5) == SOCKET_ERROR) { // Connection backlog of 5.
ServerLog().Write("ERROR-Server(listen): %d",WSAGetLastError());
closesocket(mAcceptor);
return false;
}
/*
mNumFds is calculated for the select() function - Windows ignores
it as the underlying FD sets are constructed differently than Unixes
On Unixes FDs are include all files, pipes and sockets open.
We need to calculate the highest FD number for the Unix select.
In bootup the highest FD is the listening socket so...
*/
mNumFds = mAcceptor;
return true;
}
/*!
Run starts the Server running to process incoming connection, input and
output requests. It also executes commands from input requests.
/param parms Not used.
/note
This should be where we begin threading. Not in Boot.
/todo
Polling time should runtime reconfigurable.
*/
#pragma argsused
void Server::Run(void * parms) {
ServerLog().Write("INFO-StartServer(): Server thread entered.");
struct timeval timeout = { 0, 1 }; // Will poll for 1 millisecond
while (!mShutdown) { // Loops until a SHUTDOWN_E message is received.
ConnList::iterator i, curr;
// Reset our socket interest set
InitFDs();
// Poll our socket interest set for about a millisec.
if (select(mNumFds + 1, &mInputFDs, &mOutputFDs, NULL, &timeout) == SOCKET_ERROR) {
ServerLog().Write("ERROR-Server(select): %d",WSAGetLastError());
break;
}
// If we have incoming connections on our listening port handle them.
if (FD_ISSET(mAcceptor, &mInputFDs)) {
AcceptConnection();
}
// If we have incoming data on any connections handle it.
for(i = mConns.begin(); i != mConns.end(); i++) {
if (FD_ISSET((*i)->GetSocket(), &mInputFDs)) {
(*i)->HandleInput();
// Loop through fully formed messages (CRLF).
string* msg = (*i)->ReadMsg();
while(msg) {
// notify the chat driver.
mrOutQueue.Push(new Event(MESSAGE_E,
(*i)->GetSocket(), msg->length(), msg->c_str()));
delete msg; // ReadMsg creates a new string - we must delete it.
msg = (*i)->ReadMsg();
} // while
} // if
} // for
// Process the event queue.
ProcessQueue();
// If we have outgoing data ready on any connections handle it.
for(i = mConns.begin(); i != mConns.end(); i++) {
if (FD_ISSET((*i)->GetSocket(), &mOutputFDs))
(*i)->HandleOutput();
}
// Cleanup sockets that have been scheduled for disconnection.
for(i = mConns.begin(); i != mConns.end();) {
curr = i++;
if ((*curr)->CanBeDisconnected()) {
(*curr)->Disconnect();
// Tell the chat driver about it.
mrOutQueue.Push(new Event(DISCONNECT_E, (*curr)->GetSocket(),
0, NULL));
delete (*curr);
mConns.erase(curr);
}
}
} // while
ShutdownServer();
ServerLog().Write("INFO-StartServer(): Server thread exited.");
}
/*!
AcceptConnection handles an incoming connection request on the server's
listening port by creating a Connection object for the socket.
\remarks
Dang this code looks dense.
*/
void Server::AcceptConnection() {
struct sockaddr_in addr; // An address structure for our incoming connection.
int addrlen = sizeof(addr);
// Try to accept the connection
SOCKET psock = accept(mAcceptor, (struct sockaddr *)&addr, &addrlen);
if (psock != INVALID_SOCKET) {
// Set the socket to non-blocking.
if (SetNonBlocking(psock)) {
// The socket set up correctly. Let us add a new Connection.
Connection *conn = new Connection(psock, this);
ServerLog().Write("INFO-Server(accept): Connection %d accepted", psock);
mConns.push_back(conn); // Put it on the connection list.
// Tell the chat server we have a new connection
mrOutQueue.Push(new Event(CONNECT_E, conn->GetSocket(), 0, NULL));
}
} else {
// Weve encountered an error during accept.
ServerLog().Write("ERROR-ServerAccept(accept): %d", WSAGetLastError());
}
}
/*!
InitFDs initializes the server's select queue and then re-registers
interest in all the connection's sockets in the server's user list
including the server's listening port itself.
\note
It may well be more efficient to register or unregister interest
on a connection by connection basis in the various connection
handling routines.
\remarks
[1] On Windows, setting interest in all connected sockets for output
will cause our select loop to spin. So we only set it if we know there
is output waiting.
*/
void Server::InitFDs () {
FD_ZERO (&mInputFDs); // Clears our interest sets out.
FD_ZERO (&mOutputFDs);
FD_SET (mAcceptor, &mInputFDs); // Register interest in listening socket.
for(ConnList::iterator i = mConns.begin(); i != mConns.end(); i++) {
/*
mNumFds is calculated for the select() function - Windows ignores
it as the underlying FD sets are constructed differently than Unixes
On Unixes FDs are include all files, pipes and sockets open.
We need to calculate the highest FD number for the Unix select.
*/
mNumFds = max(mNumFds, (*i)->GetSocket());
FD_SET((*i)->GetSocket(), &mInputFDs); // Always interested in input.
if ((*i)->HasOutput()) // See [1] above.
FD_SET((*i)->GetSocket(), &mOutputFDs);
}
}
/*!
ProcessQueue attempts to process all the events contained in the eventqueue
coming from our chat engine.
\remarks
The are only two kinds of messages.
- @shutdown - requests the server to shutdown
- everything else is interpreted as public conversation
\todo
Implement a disconnect request coming from driver. Allow specialized
Telnet commands to come from driver, and filter them through the
connection depending on whether it is capable.
*/
void Server::ProcessQueue() {
// manage event queue
Event* e;
while ((e = mrInQueue.Pop()) != NULL) {
switch (e->mEventType) {
case DISCONNECT_E:
// We've been told by the chat driver to kill a connection.
{
// Not yet implemented.
}
break;
case CONNECT_E:
// Should never happen - included for completeness
break;
case MESSAGE_E:
// A message from the chat driver to route to a connection here.
{
// Test for null message data and ignore it
if (e->mpData && e->mDataLen) {
string msg;
msg.append(e->mpData, e->mDataLen);
for(ConnList::iterator c = mConns.begin(); c != mConns.end(); c++) {
if ((*c)->GetSocket() == e->mClientId) {
(*c)->SendMsg(msg);
// Register interest as we have data ready on the wire
FD_SET ((*c)->GetSocket(), &mOutputFDs);
}
}
}
}
break;
case SHUTDOWN_E:
// We've been requested to shutdown so let's comply.
mShutdown = true;
break;
case NONE_E:
break;
} // switch
delete e;
} // while
}
/*!
ShutdownServer requests each of the connections to disconnect in the
server's user list, deletes the connections, and erases them from
the user list. It then closes its own listening port.
*/
void Server::ShutdownServer() {
ServerLog().Write("INFO-Server(shutdown): Server shutting down");
ConnList::iterator ci, curr;
for(ci = mConns.begin(); ci != mConns.end();) {
curr = ci++;// :WARN: The list containers iterator is invalidated on erase.
// So we need to increment it first and delete the old.
(*curr)->Disconnect();
// Tell chat driver about each connection as it leaves
mrOutQueue.Push(new Event(DISCONNECT_E, (*curr)->GetSocket(),
0, NULL));
delete (*curr);
mConns.erase(curr);
}
closesocket(mAcceptor);
mrOutQueue.Push(new Event(SHUTDOWN_E, 0, 0, 0));
}
/*!
ServerLog returns a reference to this server's logging utility so that
messages can be written to it by its associates and aggregates.
\return a reference to this server's Log
*/
Log& Server::ServerLog() {
return mrLog;
}
/*!
ServerLog returns a reference to this server's logging utility so that
messages can be written to it by its associates and aggregates.
\param s The socket to be set non-blocking.
\return true if successful, false if unsuccessful.
*/
bool Server::SetNonBlocking(SOCKET s) {
unsigned long ioctl_cmd = 1; // Non-zero means turn on for ioctl commands.
if (ioctlsocket(s, FIONBIO, &ioctl_cmd) == SOCKET_ERROR) {
ServerLog().Write("ERROR-Server(SetNonBlocking): %d on socket %d",
WSAGetLastError(), s);
closesocket(s);
return false;
}
return true;
}