558 lines
16 KiB
C++
558 lines
16 KiB
C++
// ------------------------------------------------------------------------
|
|
// eca-neteci-server.c: NetECI server implementation.
|
|
// Copyright (C) 2002,2004,2009 Kai Vehmanen
|
|
//
|
|
// Attributes:
|
|
// eca-style-version: 3
|
|
//
|
|
// This program is free software; you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation; either version 2 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
// ------------------------------------------------------------------------
|
|
|
|
#include <cassert>
|
|
#include <cstring> /* memcpy() */
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
#include <fcntl.h> /* POSIX: fcntl() */
|
|
#include <pthread.h> /* POSIX: pthread_* */
|
|
#include <unistd.h> /* POSIX: fcntl() */
|
|
#include <arpa/inet.h> /* BSD: inet_ntoa() */
|
|
#include <netinet/in.h> /* BSD: inet_ntoa() */
|
|
#include <sys/poll.h> /* POSIX: poll() */
|
|
#include <sys/socket.h> /* BSD: getpeername() */
|
|
#include <sys/types.h> /* OSX: u_int32_t (INADDR_ANY) */
|
|
|
|
#include <kvu_dbc.h>
|
|
#include <kvu_fd_io.h>
|
|
#include <kvu_numtostr.h>
|
|
#include <kvu_utils.h>
|
|
|
|
#include <eca-control-mt.h>
|
|
#include <eca-logger.h>
|
|
#include <eca-logger-wellformed.h>
|
|
|
|
#include "ecasound.h"
|
|
#include "eca-neteci-server.h"
|
|
|
|
/**
|
|
* Options
|
|
*/
|
|
// #define NETECI_DEBUG_ENABLED
|
|
|
|
#define ECA_NETECI_START_BUFFER_SIZE 128
|
|
#define ECA_NETECI_MAX_BUFFER_SIZE 65536
|
|
|
|
/**
|
|
* Macro definitions
|
|
*/
|
|
|
|
#ifdef NETECI_DEBUG_ENABLED
|
|
#define NETECI_DEBUG(x) x
|
|
#else
|
|
#define NETECI_DEBUG(x) ((void) 0)
|
|
#endif
|
|
|
|
/**
|
|
* Import namespaces
|
|
*/
|
|
|
|
using namespace std;
|
|
|
|
ECA_NETECI_SERVER::ECA_NETECI_SERVER(ECASOUND_RUN_STATE* state)
|
|
: state_repp(state),
|
|
srvfd_rep(-1),
|
|
server_listening_rep(false),
|
|
unix_sockets_rep(false),
|
|
cleanup_request_rep(false)
|
|
{
|
|
}
|
|
|
|
ECA_NETECI_SERVER::~ECA_NETECI_SERVER(void)
|
|
{
|
|
if (server_listening_rep == true) {
|
|
close_server_socket();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Launches the server thread.
|
|
*
|
|
* @param arg pointer to a ECA_NETECI_SERVER object
|
|
*/
|
|
void* ECA_NETECI_SERVER::launch_server_thread(void* arg)
|
|
{
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects, "Server thread started");
|
|
|
|
ECA_NETECI_SERVER* self =
|
|
reinterpret_cast<ECA_NETECI_SERVER*>(arg);
|
|
self->run();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Starts running the NetECI server.
|
|
*
|
|
* After calling this function, the ECA_CONTROL_MAIN object
|
|
* may be used at any time from the NetECI server thread.
|
|
*/
|
|
void ECA_NETECI_SERVER::run(void)
|
|
{
|
|
create_server_socket();
|
|
open_server_socket();
|
|
if (server_listening_rep == true) {
|
|
listen_for_events();
|
|
}
|
|
else {
|
|
ECA_LOG_MSG(ECA_LOGGER::info,
|
|
"Unable to start NetECI server. Please check that no other program is using the TCP port "
|
|
+ kvu_numtostr(state_repp->neteci_tcp_port)
|
|
+ ".");
|
|
}
|
|
close_server_socket();
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::user_objects,
|
|
"server thread exiting");
|
|
}
|
|
|
|
/**
|
|
* Creates a server socket with 'socket()'. Depending on
|
|
* object configuration either UNIX or IP socket is
|
|
* created.
|
|
*/
|
|
void ECA_NETECI_SERVER::create_server_socket(void)
|
|
{
|
|
DBC_REQUIRE(server_listening_rep != true);
|
|
DBC_REQUIRE(srvfd_rep <= 0);
|
|
|
|
if (unix_sockets_rep == true) {
|
|
srvfd_rep = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
if (srvfd_rep >= 0) {
|
|
/* create a temporary filename for the socket in a secure way */
|
|
socketpath_rep = "/tmp/neteci_server_1";
|
|
addr_un_rep.sun_family = AF_UNIX;
|
|
memcpy(addr_un_rep.sun_path, socketpath_rep.c_str(), socketpath_rep.size() + 1);
|
|
addr_repp = reinterpret_cast<struct sockaddr*>(&addr_un_rep);
|
|
}
|
|
}
|
|
else {
|
|
srvfd_rep = socket(PF_INET, SOCK_STREAM, 0);
|
|
if (srvfd_rep >= 0) {
|
|
addr_in_rep.sin_family = AF_INET;
|
|
addr_in_rep.sin_port = htons(state_repp->neteci_tcp_port);
|
|
addr_in_rep.sin_addr.s_addr = INADDR_ANY;
|
|
|
|
addr_repp = reinterpret_cast<struct sockaddr*>(&addr_in_rep);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Opens the server socket for listening. If succesful,
|
|
* 'server_listening_rep' will be true after the call.
|
|
*/
|
|
void ECA_NETECI_SERVER::open_server_socket(void)
|
|
{
|
|
DBC_REQUIRE(server_listening_rep != true);
|
|
DBC_REQUIRE(srvfd_rep > 0);
|
|
|
|
int val = 1;
|
|
int ret = setsockopt(srvfd_rep, SOL_SOCKET, SO_REUSEADDR, (void *)&val, sizeof(val));
|
|
if (ret < 0)
|
|
std::cerr << "setsockopt() failed." << endl;
|
|
|
|
// int res = bind(srvfd_rep, (struct sockaddr*)addr_repp, sizeof(*addr_repp));
|
|
|
|
int res = 0;
|
|
if (unix_sockets_rep == true)
|
|
res = bind(srvfd_rep, (struct sockaddr*)&addr_un_rep, sizeof(addr_un_rep));
|
|
else
|
|
res = bind(srvfd_rep, (struct sockaddr*)&addr_in_rep, sizeof(addr_in_rep));
|
|
|
|
if (res == 0) {
|
|
res = listen(srvfd_rep, 5);
|
|
if (res == 0) {
|
|
int res = fcntl(srvfd_rep, F_SETFL, O_NONBLOCK);
|
|
if (res == -1)
|
|
std::cerr << "fcntl() failed." << endl;
|
|
|
|
NETECI_DEBUG(std::cout << "server socket created." << endl);
|
|
server_listening_rep = true;
|
|
}
|
|
else
|
|
std::cerr << "listen() failed." << endl;
|
|
}
|
|
else {
|
|
if (unix_sockets_rep == true) {
|
|
unlink(socketpath_rep.c_str());
|
|
}
|
|
socketpath_rep.resize(0);
|
|
std::cerr << "bind() failed." << endl;
|
|
}
|
|
|
|
DBC_ENSURE((unix_sockets_rep == true &&
|
|
(((server_listening_rep == true && socketpath_rep.size() > 0) ||
|
|
(server_listening_rep != true && socketpath_rep.size() == 0)))) ||
|
|
(unix_sockets_rep != true));
|
|
}
|
|
|
|
/**
|
|
* Closes the server socket.
|
|
*/
|
|
void ECA_NETECI_SERVER::close_server_socket(void)
|
|
{
|
|
DBC_REQUIRE(srvfd_rep > 0);
|
|
DBC_REQUIRE(server_listening_rep == true);
|
|
|
|
NETECI_DEBUG(cerr << "closing socket " << kvu_numtostr(srvfd_rep) << "." << endl);
|
|
close(srvfd_rep);
|
|
srvfd_rep = -1;
|
|
server_listening_rep = false;
|
|
|
|
DBC_ENSURE(srvfd_rep == -1);
|
|
DBC_ENSURE(server_listening_rep != true);
|
|
}
|
|
|
|
/**
|
|
* Listens for and accepts incoming connections.
|
|
*/
|
|
void ECA_NETECI_SERVER::listen_for_events(void)
|
|
{
|
|
/*
|
|
* - loop until we get an exit request from network or from
|
|
* ecasound_state
|
|
*/
|
|
|
|
/* - enter poll
|
|
* - if new connections, accept them and add the new client to
|
|
* client list
|
|
* - if incoming bytes, grab ecasound_state lock, send command,
|
|
* store retval, release lock, send the reply to client
|
|
* - return to poll
|
|
*/
|
|
while(state_repp->exit_request == 0) {
|
|
// NETECI_DEBUG(cerr << "checking for events" << endl);
|
|
check_for_events(2000);
|
|
}
|
|
|
|
if (state_repp->exit_request != 0) {
|
|
NETECI_DEBUG(cerr << "exit_request received" << endl);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks for new connections and messages from
|
|
* clients.
|
|
*
|
|
* @param timeout upper-limit in ms for how long
|
|
* function waits for events; if -1,
|
|
* call will return immediately
|
|
* (ie. is non-blocking)
|
|
*/
|
|
void ECA_NETECI_SERVER::check_for_events(int timeout)
|
|
{
|
|
int nfds = clients_rep.size() + 1;
|
|
struct pollfd* ufds = new struct pollfd [nfds];
|
|
|
|
ufds[0].fd = srvfd_rep;
|
|
ufds[0].events = POLLIN;
|
|
ufds[0].revents = 0;
|
|
|
|
std::list<struct ecasound_neteci_server_client*>::iterator p = clients_rep.begin();
|
|
for(int n = 1; n < nfds; n++) {
|
|
ufds[n].fd = (*p)->fd;
|
|
ufds[n].events = POLLIN;
|
|
ufds[n].revents = 0;
|
|
++p;
|
|
}
|
|
DBC_CHECK(nfds == 1 || p == clients_rep.end());
|
|
|
|
int ret = poll(ufds, nfds, timeout);
|
|
if (ret > 0) {
|
|
if (ufds[0].revents & POLLIN) {
|
|
/* 1. new incoming connection */
|
|
handle_connection(srvfd_rep);
|
|
}
|
|
p = clients_rep.begin();
|
|
for(int n = 1; n < nfds; n++) {
|
|
if (ufds[n].revents & POLLIN) {
|
|
/* 2. client has sent a message */
|
|
handle_client_messages(*p);
|
|
}
|
|
else if (ufds[n].revents == POLLERR ||
|
|
ufds[n].revents == POLLHUP ||
|
|
ufds[n].revents == POLLNVAL) {
|
|
/* 3. error, remove client */
|
|
remove_client(*p);
|
|
}
|
|
if (p != clients_rep.end()) ++p;
|
|
}
|
|
}
|
|
|
|
if (cleanup_request_rep == true) {
|
|
clean_removed_clients();
|
|
}
|
|
|
|
delete[] ufds;
|
|
}
|
|
|
|
void ECA_NETECI_SERVER::handle_connection(int fd)
|
|
{
|
|
socklen_t bytes = 0;
|
|
string peername;
|
|
int connfd = 0;
|
|
|
|
if (unix_sockets_rep == true) {
|
|
bytes = static_cast<socklen_t>(sizeof(addr_un_rep));
|
|
connfd = accept(fd, reinterpret_cast<struct sockaddr*>(&addr_un_rep), &bytes);
|
|
peername = "UNIX:" + socketpath_rep;
|
|
}
|
|
else {
|
|
bytes = static_cast<socklen_t>(sizeof(addr_in_rep));
|
|
connfd = accept(fd, reinterpret_cast<struct sockaddr*>(&addr_in_rep), &bytes);
|
|
|
|
if (connfd > 0) {
|
|
struct sockaddr_in peeraddr;
|
|
socklen_t peernamelen;
|
|
// struct in_addr peerip;
|
|
peername = "TCP/IP:";
|
|
int res = getpeername(connfd,
|
|
reinterpret_cast<struct sockaddr*>(&peeraddr),
|
|
reinterpret_cast<socklen_t*>(&peernamelen));
|
|
if (res == 0)
|
|
peername += string(inet_ntoa(peeraddr.sin_addr));
|
|
else
|
|
peername += string(inet_ntoa(addr_in_rep.sin_addr));
|
|
}
|
|
}
|
|
|
|
ECA_LOG_MSG(ECA_LOGGER::info,
|
|
"New connection from " +
|
|
peername + ".");
|
|
|
|
|
|
if (connfd >= 0) {
|
|
NETECI_DEBUG(cerr << "incoming connection accepted" << endl);
|
|
struct ecasound_neteci_server_client* client = new struct ecasound_neteci_server_client; /* add a new client */
|
|
client->fd = connfd;
|
|
client->buffer_length = ECA_NETECI_START_BUFFER_SIZE;
|
|
client->buffer = new char [client->buffer_length];
|
|
client->buffer_current_ptr = 0;
|
|
client->peername = peername;
|
|
clients_rep.push_back(client);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle incoming messages for client 'client'.
|
|
*/
|
|
void ECA_NETECI_SERVER::handle_client_messages(struct ecasound_neteci_server_client* client)
|
|
{
|
|
char* buf[128];
|
|
int connfd = client->fd;
|
|
|
|
NETECI_DEBUG(cerr << "handle_client_messages for fd "
|
|
<< connfd << endl);
|
|
|
|
ssize_t c = kvu_fd_read(connfd, buf, 128, 5000);
|
|
if (c > 0) {
|
|
parse_raw_incoming_data(reinterpret_cast<char*>(buf), c, client);
|
|
while(parsed_cmd_queue_rep.size() > 0) {
|
|
const string& nextcmd = parsed_cmd_queue_rep.front();
|
|
if (nextcmd == "quit" || nextcmd == "q") {
|
|
NETECI_DEBUG(cerr << "client initiated quit, removing client-fd " << connfd << "." << endl);
|
|
remove_client(client);
|
|
}
|
|
else {
|
|
handle_eci_command(nextcmd, client);
|
|
}
|
|
parsed_cmd_queue_rep.pop_front();
|
|
}
|
|
/* ... */
|
|
}
|
|
else {
|
|
/* read() <= 0 */
|
|
NETECI_DEBUG(cerr << "read error, removing client-fd " << connfd << "." << endl);
|
|
remove_client(client);
|
|
}
|
|
}
|
|
|
|
void ECA_NETECI_SERVER::parse_raw_incoming_data(const char* buffer,
|
|
ssize_t bytes,
|
|
struct ecasound_neteci_server_client* client)
|
|
{
|
|
DBC_REQUIRE(buffer != 0);
|
|
DBC_REQUIRE(bytes >= 0);
|
|
DBC_REQUIRE(client != 0);
|
|
DBC_DECLARE(int old_client_ptr = client->buffer_current_ptr);
|
|
DBC_DECLARE(unsigned int old_cmd_queue_size = parsed_cmd_queue_rep.size());
|
|
|
|
NETECI_DEBUG(cerr << "parse incoming data; "
|
|
<< bytes << " bytes. Buffer length is "
|
|
<< client->buffer_length << endl);
|
|
|
|
for(int n = 0; n < bytes; n++) {
|
|
DBC_CHECK(client->buffer_current_ptr <= client->buffer_length);
|
|
if (client->buffer_current_ptr == client->buffer_length) {
|
|
int new_buffer_length = client->buffer_length * 2;
|
|
char *new_buffer = new char [new_buffer_length];
|
|
|
|
if (new_buffer_length > ECA_NETECI_MAX_BUFFER_SIZE) {
|
|
cerr << "client buffer overflow, unable to increase buffer size. flushing..." << endl;
|
|
client->buffer_current_ptr = 0;
|
|
}
|
|
else {
|
|
NETECI_DEBUG(cerr << "client buffer overflow, increasing buffer size from "
|
|
<< client->buffer_length << " to " << new_buffer_length << " bytes." << endl);
|
|
|
|
for(int i = 0; i < client->buffer_length; i++) new_buffer[i] = client->buffer[i];
|
|
|
|
delete[] client->buffer;
|
|
client->buffer = new_buffer;
|
|
client->buffer_length = new_buffer_length;
|
|
}
|
|
}
|
|
|
|
NETECI_DEBUG(cerr << "copying '" << buffer[n] << "'\n");
|
|
client->buffer[client->buffer_current_ptr] = buffer[n];
|
|
if (client->buffer_current_ptr > 0 &&
|
|
client->buffer[client->buffer_current_ptr] == '\n' &&
|
|
client->buffer[client->buffer_current_ptr - 1] == '\r') {
|
|
|
|
string cmd (client->buffer, client->buffer_current_ptr - 1);
|
|
NETECI_DEBUG(cerr << "storing command '" << cmd << "'" << endl);
|
|
parsed_cmd_queue_rep.push_back(cmd);
|
|
|
|
NETECI_DEBUG(cerr << "copying "
|
|
<< client->buffer_length - client->buffer_current_ptr - 1
|
|
<< " bytes from " << client->buffer_current_ptr + 1
|
|
<< " to the beginning of the buffer."
|
|
<< " Index is " << client->buffer_current_ptr << endl);
|
|
|
|
DBC_CHECK(client->buffer_current_ptr < client->buffer_length);
|
|
|
|
#if 0
|
|
/* must not use memcpy() as the
|
|
affected areas may overlap! */
|
|
for(int o = 0, p = index + 1;
|
|
p < client->buffer_length; o++, p++) {
|
|
client->buffer[o] = client->buffer[p];
|
|
}
|
|
#endif
|
|
client->buffer_current_ptr = 0;
|
|
}
|
|
else {
|
|
// NETECI_DEBUG(cerr << "crlf not found, index=" << index << ", n=" << n << "cur_ptr=" << client->buffer_current_ptr << ".\n");
|
|
client->buffer_current_ptr++;
|
|
}
|
|
}
|
|
|
|
DBC_ENSURE(client->buffer_current_ptr > old_client_ptr ||
|
|
parsed_cmd_queue_rep.size() > old_cmd_queue_size);
|
|
}
|
|
|
|
void ECA_NETECI_SERVER::handle_eci_command(const string& cmd, struct ecasound_neteci_server_client* client)
|
|
{
|
|
ECA_CONTROL_MT* ctrl = state_repp->control;
|
|
|
|
NETECI_DEBUG(cerr << "handle eci command: " << cmd << endl);
|
|
|
|
assert(ctrl != 0);
|
|
|
|
struct eci_return_value retval;
|
|
ctrl->command(cmd, &retval);
|
|
|
|
string strtosend =
|
|
ECA_LOGGER_WELLFORMED::create_wellformed_message(ECA_LOGGER::eiam_return_values,
|
|
std::string(ECA_CONTROL_MAIN::return_value_type_to_string(&retval))
|
|
+ " " +
|
|
ECA_CONTROL_MAIN::return_value_to_string(&retval));
|
|
|
|
int bytes_to_send = strtosend.size();
|
|
while(bytes_to_send > 0) {
|
|
int ret = kvu_fd_write(client->fd, strtosend.c_str(), strtosend.size(), 5000);
|
|
if (ret < 0) {
|
|
cerr << "error in kvu_fd_write(), removing client.\n";
|
|
remove_client(client);
|
|
break;
|
|
}
|
|
else {
|
|
bytes_to_send -= ret;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes 'client' from list of clients.
|
|
*
|
|
* Note! Internally, the 'fd' field of the deleted client
|
|
* is marked to be -1.
|
|
*
|
|
* @see clean_removed_clients()
|
|
*/
|
|
void ECA_NETECI_SERVER::remove_client(struct ecasound_neteci_server_client* client)
|
|
{
|
|
NETECI_DEBUG(std::cout << "removing client." << std::endl);
|
|
|
|
if (client != 0 && client->fd > 0) {
|
|
ECA_LOG_MSG(ECA_LOGGER::info,
|
|
"Closing connection " +
|
|
client->peername + ".");
|
|
close(client->fd);
|
|
client->fd = -1;
|
|
}
|
|
|
|
cleanup_request_rep = true;
|
|
}
|
|
|
|
/**
|
|
* Cleans the list of clients from removed objects.
|
|
*
|
|
* @see remove_client()
|
|
*/
|
|
void ECA_NETECI_SERVER::clean_removed_clients(void)
|
|
{
|
|
DBC_DECLARE(size_t oldsize = clients_rep.size());
|
|
DBC_DECLARE(size_t counter = 0);
|
|
|
|
NETECI_DEBUG(std::cerr << "cleaning removed clients." << std::endl);
|
|
|
|
list<struct ecasound_neteci_server_client*>::iterator p = clients_rep.begin();
|
|
while(p != clients_rep.end()) {
|
|
NETECI_DEBUG(std::cerr << "checking for delete, client " << *p << std::endl);
|
|
if (*p != 0 && (*p)->fd == -1) {
|
|
if ((*p)->buffer != 0) {
|
|
delete[] (*p)->buffer;
|
|
(*p)->buffer = 0;
|
|
}
|
|
std::list<struct ecasound_neteci_server_client*>::iterator q = p;
|
|
++q;
|
|
NETECI_DEBUG(std::cerr << "deleting client " << *p << std::endl);
|
|
delete *p;
|
|
NETECI_DEBUG(std::cerr << "erasing client " << *p << std::endl);
|
|
*p = 0;
|
|
clients_rep.erase(p);
|
|
p = q;
|
|
DBC_DECLARE(++counter);
|
|
}
|
|
else {
|
|
++p;
|
|
}
|
|
}
|
|
|
|
cleanup_request_rep = false;
|
|
|
|
DBC_ENSURE(clients_rep.size() == oldsize - counter);
|
|
}
|