mirror of
https://github.com/thestk/stk
synced 2026-01-15 05:51:52 +00:00
Version 4.2.0
This commit is contained in:
committed by
Stephen Sinclair
parent
cf06b7598b
commit
a6381b9d38
702
src/Messager.cpp
702
src/Messager.cpp
@@ -2,393 +2,419 @@
|
||||
/*! \class Messager
|
||||
\brief STK input control message parser.
|
||||
|
||||
This class reads and parses control messages
|
||||
from a variety of sources, such as a MIDI
|
||||
port, scorefile, socket connection, or pipe.
|
||||
MIDI messages are retrieved using the RtMidi
|
||||
class. All other input sources (scorefile,
|
||||
socket, or pipe) are assumed to provide SKINI
|
||||
formatted messages.
|
||||
This class reads and parses control messages from a variety of
|
||||
sources, such as a scorefile, MIDI port, socket connection, or
|
||||
stdin. MIDI messages are retrieved using the RtMidi class. All
|
||||
other input sources (scorefile, socket, or stdin) are assumed to
|
||||
provide SKINI formatted messages. This class can be compiled with
|
||||
generic, non-realtime support, in which case only scorefile
|
||||
reading is possible.
|
||||
|
||||
For each call to nextMessage(), the active
|
||||
input sources are queried to see if a new
|
||||
control message is available.
|
||||
The various \e realtime message acquisition mechanisms (from MIDI,
|
||||
socket, or stdin) take place asynchronously, filling the message
|
||||
queue. A call to popMessage() will pop the next available control
|
||||
message from the queue and return it via the referenced Message
|
||||
structure. When a \e non-realtime scorefile is set, it is not
|
||||
possible to start reading realtime input messages (from MIDI,
|
||||
socket, or stdin). Likewise, it is not possible to read from a
|
||||
scorefile when a realtime input mechanism is running.
|
||||
|
||||
This class is primarily for use in STK main()
|
||||
event loops.
|
||||
When MIDI input is started, input is also automatically read from
|
||||
stdin. This allows for program termination via the terminal
|
||||
window. An __SK_Exit_ message is pushed onto the stack whenever
|
||||
an "exit" or "Exit" message is received from stdin or when all
|
||||
socket connections close and no stdin thread is running.
|
||||
|
||||
One of the original goals in creating this
|
||||
class was to simplify the message acquisition
|
||||
process by removing all threads. If the
|
||||
windoze select() function behaved just like
|
||||
the unix one, that would have been possible.
|
||||
Since it does not (it can't be used to poll
|
||||
STDIN), I am using a thread to acquire
|
||||
messages from STDIN, which sends these
|
||||
messages via a socket connection to the
|
||||
message socket server. Perhaps in the future,
|
||||
it will be possible to simplify things.
|
||||
This class is primarily for use in STK example programs but it is
|
||||
generic enough to work in many other contexts.
|
||||
|
||||
by Perry R. Cook and Gary P. Scavone, 1995 - 2002.
|
||||
by Perry R. Cook and Gary P. Scavone, 1995 - 2004.
|
||||
*/
|
||||
/***************************************************/
|
||||
|
||||
#include "Messager.h"
|
||||
#include <string.h>
|
||||
#include <iostream>
|
||||
#include "SKINI.msg"
|
||||
|
||||
int socket_port = 2001;
|
||||
static const int STK_FILE = 0x1;
|
||||
static const int STK_MIDI = 0x2;
|
||||
static const int STK_STDIN = 0x4;
|
||||
static const int STK_SOCKET = 0x8;
|
||||
|
||||
Messager :: Messager(int inputMask, int port)
|
||||
Messager :: Messager()
|
||||
{
|
||||
sources = inputMask;
|
||||
rtDelta = RT_BUFFER_SIZE;
|
||||
messageIndex = 0;
|
||||
nMessages = 0;
|
||||
skini = new SKINI();
|
||||
|
||||
data_.sources = 0;
|
||||
data_.queueLimit = DEFAULT_QUEUE_LIMIT;
|
||||
#if defined(__STK_REALTIME__)
|
||||
// If no input source is specified, we assume the input is coming
|
||||
// from a SKINI scorefile. If any input source is specified, we
|
||||
// will always check STDIN, even if STK_PIPE is not specified. This
|
||||
// provides a means to exit cleanly when reading MIDI or in case a
|
||||
// socket connection cannot be made after STK_SOCKET has been
|
||||
// specified. The current means of polling STDIN is via a thread,
|
||||
// which sends its data via a socket connection to the socket
|
||||
// server.
|
||||
if ( sources ) {
|
||||
|
||||
if ( sources & STK_MIDI ) {
|
||||
// Attempt to open a MIDI device, but don't throw an exception
|
||||
// if other input sources are specified.
|
||||
try {
|
||||
midi = new RtMidi();
|
||||
}
|
||||
catch (StkError &exception) {
|
||||
if ( sources == STK_MIDI ) {
|
||||
throw exception;
|
||||
}
|
||||
// Disable the MIDI input and keep going.
|
||||
sources &= ~STK_MIDI;
|
||||
}
|
||||
}
|
||||
|
||||
// If STK_PIPE is not specified, let the users know they can exit
|
||||
// the program via the console if necessary.
|
||||
if ( !(sources & STK_PIPE) && sources )
|
||||
std::cout << "\nType `Exit<cr>' to quit.\n" << std::endl;
|
||||
|
||||
sources |= STK_SOCKET;
|
||||
socket_port = port;
|
||||
soket = new Socket(port);
|
||||
if (inputMask & STK_SOCKET)
|
||||
printf("\nSocket server listening for connection(s) on port %d ...\n\n", port);
|
||||
|
||||
nSockets = 0;
|
||||
maxfd = 0;
|
||||
FD_ZERO(&mask);
|
||||
int d = soket->socket();
|
||||
FD_SET(d, &mask);
|
||||
if (d > maxfd) maxfd = d;
|
||||
|
||||
// The fd array is used to hold the file descriptors for all
|
||||
// connected sockets. This saves some time incrementing through
|
||||
// file descriptors when using select().
|
||||
for (int i=0; i<16; i++)
|
||||
fd[i] = 0;
|
||||
|
||||
// Start the stdin input thread.
|
||||
thread = new Thread();
|
||||
if ( !thread->start( (THREAD_FUNCTION)&stdinHandler, NULL ) ) {
|
||||
sprintf(error, "Messager: Unable to start stdin input thread!");
|
||||
handleError( error, StkError::PROCESS_THREAD );
|
||||
}
|
||||
}
|
||||
#endif // __STK_REALTIME__
|
||||
data_.socket = 0;
|
||||
data_.midi = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
Messager :: ~Messager()
|
||||
{
|
||||
delete skini;
|
||||
// Clear the queue in case any thread is waiting on its limit.
|
||||
#if defined(__STK_REALTIME__)
|
||||
data_.mutex.lock();
|
||||
#endif
|
||||
while ( data_.queue.size() ) data_.queue.pop();
|
||||
data_.sources = 0;
|
||||
|
||||
#if defined(__STK_REALTIME__)
|
||||
data_.mutex.unlock();
|
||||
if ( data_.socket ) {
|
||||
socketThread_.wait();
|
||||
delete data_.socket;
|
||||
}
|
||||
|
||||
if ( data_.midi ) delete data_.midi;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Messager :: setScoreFile( const char* filename )
|
||||
{
|
||||
if ( data_.sources ) {
|
||||
if ( data_.sources == STK_FILE ) {
|
||||
errorString_ << "Messager::setScoreFile: already reading a scorefile!";
|
||||
handleError( StkError::WARNING );
|
||||
}
|
||||
else {
|
||||
errorString_ << "Messager::setScoreFile: already reading realtime control input ... cannot do scorefile input too!";
|
||||
handleError( StkError::WARNING );
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !data_.skini.setFile( filename ) ) return false;
|
||||
data_.sources = STK_FILE;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Messager :: popMessage( Skini::Message& message )
|
||||
{
|
||||
if ( data_.sources == STK_FILE ) { // scorefile input
|
||||
if ( !data_.skini.nextMessage( message ) )
|
||||
message.type = __SK_Exit_;
|
||||
return;
|
||||
}
|
||||
|
||||
if ( data_.queue.size() == 0 ) {
|
||||
// An empty (or invalid) message is indicated by a type = 0.
|
||||
message.type = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy queued message to the message pointer structure and then "pop" it.
|
||||
#if defined(__STK_REALTIME__)
|
||||
data_.mutex.lock();
|
||||
#endif
|
||||
message = data_.queue.front();
|
||||
data_.queue.pop();
|
||||
#if defined(__STK_REALTIME__)
|
||||
data_.mutex.unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Messager :: pushMessage( Skini::Message& message )
|
||||
{
|
||||
#if defined(__STK_REALTIME__)
|
||||
data_.mutex.lock();
|
||||
#endif
|
||||
data_.queue.push( message );
|
||||
#if defined(__STK_REALTIME__)
|
||||
data_.mutex.unlock();
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__STK_REALTIME__)
|
||||
|
||||
if ( sources & STK_MIDI )
|
||||
delete midi;
|
||||
|
||||
if ( sources & STK_SOCKET ) {
|
||||
delete soket;
|
||||
delete thread;
|
||||
}
|
||||
#endif // __STK_REALTIME__
|
||||
}
|
||||
|
||||
long Messager :: getType() const
|
||||
bool Messager :: startStdInput()
|
||||
{
|
||||
return type;
|
||||
}
|
||||
|
||||
MY_FLOAT Messager :: getByteTwo() const
|
||||
{
|
||||
return byte2;
|
||||
}
|
||||
|
||||
MY_FLOAT Messager :: getByteThree() const
|
||||
{
|
||||
return byte3;
|
||||
}
|
||||
|
||||
long Messager :: getChannel() const
|
||||
{
|
||||
return channel;
|
||||
}
|
||||
|
||||
void Messager :: setRtDelta(long nSamples)
|
||||
{
|
||||
if ( nSamples > 0 )
|
||||
rtDelta = nSamples;
|
||||
else
|
||||
std::cerr << "Messager: setRtDelta(" << nSamples << ") less than or equal to zero!" << std::endl;
|
||||
}
|
||||
|
||||
long Messager :: getDelta() const
|
||||
{
|
||||
return delta;
|
||||
}
|
||||
|
||||
long Messager :: nextMessage()
|
||||
{
|
||||
if (nMessages > 0 ) nMessages--;
|
||||
type = 0;
|
||||
|
||||
if ( !sources ) {
|
||||
// No realtime flags ... assuming scorefile input.
|
||||
memset(message[messageIndex], 0, MESSAGE_LENGTH);
|
||||
if ( fgets(message[messageIndex], MESSAGE_LENGTH, stdin) == 0 ) {
|
||||
delta = 0;
|
||||
return -1; // end of file
|
||||
}
|
||||
nMessages++;
|
||||
}
|
||||
#if defined(__STK_REALTIME__)
|
||||
else if (nMessages == 0) {
|
||||
if ( midiMessage() ) return type;
|
||||
if ( !socketMessage() ) return type;
|
||||
}
|
||||
#endif
|
||||
|
||||
skini->parseThis(message[messageIndex++]);
|
||||
if (messageIndex >= MAX_MESSAGES) messageIndex = 0;
|
||||
type = skini->getType();
|
||||
if (type <= 0) {
|
||||
// Don't tick for comments or improperly formatted messages.
|
||||
nMessages--;
|
||||
delta = 0;
|
||||
type = 0;
|
||||
return type;
|
||||
if ( data_.sources == STK_FILE ) {
|
||||
errorString_ << "Messager::startStdInput: already reading a scorefile ... cannot do realtime control input too!";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
channel = skini->getChannel();
|
||||
byte2 = skini->getByteTwo();
|
||||
byte3 = skini->getByteThree();
|
||||
|
||||
MY_FLOAT temp = skini->getDelta();
|
||||
if ( temp >= 0.0 )
|
||||
delta = (long) (temp * Stk::sampleRate());
|
||||
else
|
||||
// Ignore negative delta times (absolute time).
|
||||
delta = rtDelta;
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
#if defined(__STK_REALTIME__)
|
||||
bool Messager :: midiMessage( void )
|
||||
{
|
||||
if (sources & STK_MIDI) {
|
||||
if ( midi->nextMessage() > 0 ) {
|
||||
// get MIDI message info
|
||||
type = midi->getType();
|
||||
channel = midi->getChannel();
|
||||
byte2 = midi->getByteTwo();
|
||||
byte3 = midi->getByteThree();
|
||||
nMessages++;
|
||||
delta = rtDelta;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Messager :: socketMessage()
|
||||
{
|
||||
register fd_set rmask;
|
||||
static struct timeval timeout = {0, 0};
|
||||
|
||||
rmask = mask;
|
||||
if ( select(maxfd+1, &rmask, (fd_set *)0, (fd_set *)0, &timeout) ) {
|
||||
// A file descriptor is set.
|
||||
|
||||
// Check if there's a new socket connection available.
|
||||
if ( FD_ISSET(soket->socket(), &rmask) ) {
|
||||
// Accept and service new connection.
|
||||
int newfd = soket->accept();
|
||||
if ( newfd < 0 ) {
|
||||
sprintf(error, "Messager: Couldn't accept connection request!");
|
||||
handleError(error, StkError::WARNING);
|
||||
}
|
||||
|
||||
// We assume the first connection will occur for the stdin
|
||||
// thread socket. Since this connection is "hidden" from
|
||||
// the user, only print connected messages for subsequent
|
||||
// connections.
|
||||
if (nSockets == 0)
|
||||
pipefd = newfd;
|
||||
else
|
||||
std::cout << "New socket connection made.\n" << std::endl;
|
||||
|
||||
// Set the socket to non-blocking mode.
|
||||
Socket::setBlocking( newfd, false );
|
||||
|
||||
// Save the descriptor and update the masks.
|
||||
fd[nSockets++] = newfd;
|
||||
FD_SET(newfd, &mask);
|
||||
if ( newfd > maxfd) maxfd = newfd;
|
||||
FD_CLR(soket->socket(), &rmask);
|
||||
}
|
||||
|
||||
// Check client socket connections.
|
||||
unsigned int client = 0;
|
||||
while ( client < nSockets ) {
|
||||
if ( !FD_ISSET(fd[client], &rmask) )
|
||||
client++;
|
||||
else {
|
||||
// This connection has data.
|
||||
if ( !readSocket( fd[client] ) ) {
|
||||
// The socket connection closed.
|
||||
nSockets--;
|
||||
if ( nSockets == 0 ) {
|
||||
type = -1;
|
||||
return false;
|
||||
}
|
||||
if ( nSockets == 1 && FD_ISSET(pipefd, &mask) ) {
|
||||
// The "piping" socket is still running.
|
||||
if (sources & STK_MIDI) {
|
||||
std::cout << "MIDI input still running ... type 'Exit<cr>' to quit.\n" << std::endl;
|
||||
}
|
||||
else if (!(sources & STK_PIPE) ) {
|
||||
// The user didn't specify this connection, so quit now.
|
||||
type = -1;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (client < nSockets) {
|
||||
// Move descriptors down in the list.
|
||||
for (unsigned int j=client; j<nSockets; j++)
|
||||
fd[j] = fd[j+1];
|
||||
}
|
||||
delta = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( !strncmp(message[messageIndex], "Exit", 4) || !strncmp(message[messageIndex], "exit", 4) ) {
|
||||
// We have an "Exit" message ... don't try to parse it.
|
||||
messageIndex++;
|
||||
nMessages--;
|
||||
delta = 0;
|
||||
return false;
|
||||
}
|
||||
// Not an "Exit" message ... parse it.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ( data_.sources & STK_STDIN ) {
|
||||
errorString_ << "Messager::startStdInput: stdin input thread already started.";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
// If we get here, we checked all devices but found no messages.
|
||||
delta = rtDelta;
|
||||
return false;
|
||||
// Start the stdin input thread.
|
||||
if ( !stdinThread_.start( (THREAD_FUNCTION)&stdinHandler, &data_ ) ) {
|
||||
errorString_ << "Messager::startStdInput: unable to start stdin input thread!";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
data_.sources |= STK_STDIN;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if (defined(__OS_IRIX__) || defined(__OS_LINUX__) || defined(__OS_MACOSX__))
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#endif
|
||||
|
||||
bool Messager :: readSocket(int fd)
|
||||
THREAD_RETURN THREAD_TYPE stdinHandler(void *ptr)
|
||||
{
|
||||
// This method will read all data available from a socket
|
||||
// connection, filling the message buffer. This is necessary
|
||||
// because the select() function triggers on socket activity, not on
|
||||
// the presence of (buffered) data. So, whenever activity is
|
||||
// indicated, we need to grab all available data.
|
||||
char buffer[MESSAGE_LENGTH];
|
||||
int index = 0, m = 0, bufferSize = 0;
|
||||
int nextMessage;
|
||||
Messager::MessagerData *data = (Messager::MessagerData *) ptr;
|
||||
Skini::Message message;
|
||||
|
||||
nextMessage = (messageIndex + nMessages) % MAX_MESSAGES;
|
||||
memset(message[nextMessage], 0, MESSAGE_LENGTH);
|
||||
std::string line;
|
||||
while ( !std::getline( std::cin, line).eof() ) {
|
||||
if ( line.empty() ) continue;
|
||||
if ( line.compare(0, 4, "Exit") == 0 || line.compare(0, 4, "exit") == 0 )
|
||||
break;
|
||||
|
||||
#if ( defined(__OS_IRIX__) || defined(__OS_LINUX__) || defined(__OS_MACOSX__) )
|
||||
errno = 0;
|
||||
while (bufferSize != -1 && errno != EAGAIN) {
|
||||
#elif defined(__OS_WINDOWS__)
|
||||
while (bufferSize != SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) {
|
||||
#endif
|
||||
while (index < bufferSize) {
|
||||
message[nextMessage][m++] = buffer[index];
|
||||
if (buffer[index++] == '\n') {
|
||||
m = 0;
|
||||
nMessages++;
|
||||
nextMessage = (messageIndex + nMessages) % MAX_MESSAGES;
|
||||
memset(message[nextMessage], 0, MESSAGE_LENGTH);
|
||||
}
|
||||
}
|
||||
index = 0;
|
||||
data->mutex.lock();
|
||||
if ( data->skini.parseString( line, message ) )
|
||||
data->queue.push( message );
|
||||
data->mutex.unlock();
|
||||
|
||||
// Receive a new socket buffer.
|
||||
memset(buffer, 0, MESSAGE_LENGTH);
|
||||
bufferSize = Socket::readBuffer(fd, buffer, MESSAGE_LENGTH, 0);
|
||||
if (bufferSize == 0) {
|
||||
FD_CLR(fd, &mask);
|
||||
Socket::close( fd );
|
||||
while ( data->queue.size() >= data->queueLimit ) Stk::sleep( 50 );
|
||||
}
|
||||
|
||||
// We assume here that if someone types an "exit" message in the
|
||||
// terminal window, all processing should stop.
|
||||
message.type = __SK_Exit_;
|
||||
data->queue.push( message );
|
||||
data->sources &= ~STK_STDIN;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void midiHandler( double timeStamp, std::vector<unsigned char> *bytes, void *ptr )
|
||||
{
|
||||
if ( bytes->size() < 2 ) return;
|
||||
|
||||
// Parse the MIDI bytes ... only keep MIDI channel messages.
|
||||
if ( bytes->at(0) > 239 ) return;
|
||||
|
||||
Messager::MessagerData *data = (Messager::MessagerData *) ptr;
|
||||
Skini::Message message;
|
||||
|
||||
message.type = bytes->at(0) & 0xF0;
|
||||
message.channel = bytes->at(0) & 0x0F;
|
||||
message.time = 0.0; // realtime messages should have delta time = 0.0
|
||||
message.intValues[0] = bytes->at(1);
|
||||
message.floatValues[0] = (StkFloat) message.intValues[0];
|
||||
if ( ( message.type != 0xC0 ) && ( message.type != 0xD0 ) ) {
|
||||
if ( bytes->size() < 3 ) return;
|
||||
message.intValues[1] = bytes->at(2);
|
||||
message.floatValues[1] = (StkFloat) message.intValues[1];
|
||||
}
|
||||
|
||||
while ( data->queue.size() >= data->queueLimit ) Stk::sleep( 50 );
|
||||
|
||||
data->mutex.lock();
|
||||
data->queue.push( message );
|
||||
data->mutex.unlock();
|
||||
}
|
||||
|
||||
bool Messager :: startMidiInput( int port )
|
||||
{
|
||||
if ( data_.sources == STK_FILE ) {
|
||||
errorString_ << "Messager::startMidiInput: already reading a scorefile ... cannot do realtime control input too!";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( data_.sources & STK_MIDI ) {
|
||||
errorString_ << "Messager::startMidiInput: MIDI input already started.";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
// First start the stdin input thread if it isn't already running
|
||||
// (to allow the user to exit).
|
||||
if ( !( data_.sources & STK_STDIN ) ) {
|
||||
if ( this->startStdInput() == false ) {
|
||||
errorString_ << "Messager::startMidiInput: unable to start input from stdin.";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
data_.midi = new RtMidiIn();
|
||||
data_.midi->setCallback( &midiHandler, (void *) &data_ );
|
||||
if ( port == -1 ) data_.midi->openVirtualPort();
|
||||
else data_.midi->openPort( (unsigned int)port );
|
||||
}
|
||||
catch ( RtError &error ) {
|
||||
errorString_ << "Messager::startMidiInput: error creating RtMidiIn instance (" << error.getMessage() << ").";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
data_.sources |= STK_MIDI;
|
||||
return true;
|
||||
}
|
||||
|
||||
THREAD_RETURN THREAD_TYPE stdinHandler(void *)
|
||||
bool Messager :: startSocketInput( int port )
|
||||
{
|
||||
char message[MESSAGE_LENGTH];
|
||||
if ( data_.sources == STK_FILE ) {
|
||||
errorString_ << "Messager::startSocketInput: already reading a scorefile ... cannot do realtime control input too!";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
Socket *s;
|
||||
if ( data_.sources & STK_SOCKET ) {
|
||||
errorString_ << "Messager::startSocketInput: socket input thread already started.";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create the socket server.
|
||||
try {
|
||||
s = new Socket( socket_port, "localhost" );
|
||||
data_.socket = new Socket( port );
|
||||
}
|
||||
catch (StkError &) {
|
||||
fprintf(stderr, "Messager: Couldn't create stdin input thread!\n");
|
||||
return NULL;
|
||||
catch ( StkError& ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
memset(message, 0, MESSAGE_LENGTH);
|
||||
if ( fgets(message, MESSAGE_LENGTH, stdin) == 0 )
|
||||
break;
|
||||
errorString_ << "Socket server listening for connection(s) on port " << port << "...";
|
||||
handleError( StkError::STATUS );
|
||||
|
||||
// Check for an "Exit" message.
|
||||
if ( !strncmp(message, "Exit", 4) || !strncmp(message, "exit", 4) )
|
||||
break;
|
||||
// Initialize socket descriptor information.
|
||||
FD_ZERO(&data_.mask);
|
||||
int fd = data_.socket->id();
|
||||
FD_SET( fd, &data_.mask );
|
||||
data_.fd.push_back( fd );
|
||||
|
||||
if ( s->writeBuffer( (void *)message, strlen(message), 0) < 0 ) {
|
||||
fprintf(stderr, "Messager: stdin thread connection to socket server failed!\n");
|
||||
break;
|
||||
// Start the socket thread.
|
||||
if ( !socketThread_.start( (THREAD_FUNCTION)&socketHandler, &data_ ) ) {
|
||||
errorString_ << "Messager::startSocketInput: unable to start socket input thread!";
|
||||
handleError( StkError::WARNING );
|
||||
return false;
|
||||
}
|
||||
|
||||
data_.sources |= STK_SOCKET;
|
||||
return true;
|
||||
}
|
||||
|
||||
#if (defined(__OS_IRIX__) || defined(__OS_LINUX__) || defined(__OS_MACOS__))
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/time.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/tcp.h>
|
||||
#endif
|
||||
|
||||
THREAD_RETURN THREAD_TYPE socketHandler(void *ptr)
|
||||
{
|
||||
Messager::MessagerData *data = (Messager::MessagerData *) ptr;
|
||||
Skini::Message message;
|
||||
std::vector<int>& fd = data->fd;
|
||||
|
||||
struct timeval timeout;
|
||||
fd_set rmask;
|
||||
int newfd;
|
||||
unsigned int i;
|
||||
const int bufferSize = 1024;
|
||||
char buffer[bufferSize];
|
||||
int index = 0, bytesRead = 0;
|
||||
std::string line;
|
||||
std::vector<int> fdclose;
|
||||
|
||||
while ( data->sources & STK_SOCKET ) {
|
||||
|
||||
// Use select function to periodically poll socket desriptors.
|
||||
rmask = data->mask;
|
||||
timeout.tv_sec = 0; timeout.tv_usec = 50000; // 50 milliseconds
|
||||
if ( select( fd.back()+1, &rmask, (fd_set *)0, (fd_set *)0, &timeout ) <= 0 ) continue;
|
||||
|
||||
// A file descriptor is set. Check if there's a new socket connection available.
|
||||
if ( FD_ISSET( data->socket->id(), &rmask ) ) {
|
||||
|
||||
// Accept and service new connection.
|
||||
newfd = data->socket->accept();
|
||||
if ( newfd >= 0 ) {
|
||||
std::cout << "New socket connection made.\n" << std::endl;
|
||||
|
||||
// Set the socket to non-blocking mode.
|
||||
Socket::setBlocking( newfd, false );
|
||||
|
||||
// Save the descriptor and update the masks.
|
||||
fd.push_back( newfd );
|
||||
std::sort( fd.begin(), data->fd.end() );
|
||||
FD_SET( newfd, &data->mask );
|
||||
FD_CLR( data->socket->id(), &rmask );
|
||||
}
|
||||
else
|
||||
std::cerr << "Messager: Couldn't accept connection request!\n";
|
||||
}
|
||||
|
||||
// Check the other descriptors.
|
||||
for ( i=0; i<fd.size(); i++ ) {
|
||||
|
||||
if ( !FD_ISSET( fd[i], &rmask ) ) continue;
|
||||
|
||||
// This connection has data. Read and parse it.
|
||||
bytesRead = 0;
|
||||
index = 0;
|
||||
#if ( defined(__OS_IRIX__) || defined(__OS_LINUX__) || defined(__OS_MACOSX__) )
|
||||
errno = 0;
|
||||
while (bytesRead != -1 && errno != EAGAIN) {
|
||||
#elif defined(__OS_WINDOWS__)
|
||||
while (bytesRead != SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) {
|
||||
#endif
|
||||
|
||||
while ( index < bytesRead ) {
|
||||
line += buffer[index];
|
||||
if ( buffer[index++] == '\n' ) {
|
||||
data->mutex.lock();
|
||||
if ( line.compare(0, 4, "Exit") == 0 || line.compare(0, 4, "exit") == 0 ) {
|
||||
// Ignore this line and assume the connection will be
|
||||
// closed on a subsequent read call.
|
||||
;
|
||||
}
|
||||
else if ( data->skini.parseString( line, message ) )
|
||||
data->queue.push( message );
|
||||
data->mutex.unlock();
|
||||
line.erase();
|
||||
}
|
||||
}
|
||||
index = 0;
|
||||
|
||||
bytesRead = Socket::readBuffer(fd[i], buffer, bufferSize, 0);
|
||||
if (bytesRead == 0) {
|
||||
// This socket connection closed.
|
||||
FD_CLR( fd[i], &data->mask );
|
||||
Socket::close( fd[i] );
|
||||
fdclose.push_back( fd[i] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now remove descriptors for closed connections.
|
||||
for ( i=0; i<fdclose.size(); i++ ) {
|
||||
for ( unsigned int j=0; j<fd.size(); j++ ) {
|
||||
if ( fd[j] == fdclose[i] ) {
|
||||
fd.erase( fd.begin() + j );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see whether all connections are closed. Note that
|
||||
// the server descriptor will always remain.
|
||||
if ( fd.size() == 1 ) {
|
||||
data->sources &= ~STK_SOCKET;
|
||||
if ( data->sources & STK_MIDI )
|
||||
std::cout << "MIDI input still running ... type 'exit<cr>' to quit.\n" << std::endl;
|
||||
else if ( !(data->sources & STK_STDIN) ) {
|
||||
// No stdin thread running, so quit now.
|
||||
message.type = __SK_Exit_;
|
||||
data->queue.push( message );
|
||||
}
|
||||
}
|
||||
fdclose.clear();
|
||||
}
|
||||
|
||||
// Wait until we're below the queue limit.
|
||||
while ( data->queue.size() >= data->queueLimit ) Stk::sleep( 50 );
|
||||
}
|
||||
|
||||
delete s;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#endif // __STK_REALTIME__
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user