X-Git-Url: http://developer.intra2net.com/git/?p=libt2n;a=blobdiff_plain;f=src%2Fsocket_server.cpp;h=f0e573b3101e8c6e36133f10b5032116b1d2e29f;hp=f94d9598671147d6f13f02cef30b98858929fff6;hb=5816531135da09aceb7a026eeade735cbd20ef03;hpb=ac7fdc22899c0c493fda5fdb3a4cb67e77504a6b diff --git a/src/socket_server.cpp b/src/socket_server.cpp index f94d959..f0e573b 100644 --- a/src/socket_server.cpp +++ b/src/socket_server.cpp @@ -17,7 +17,28 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + #include "socket_server.hxx" +#include "t2n_exception.hxx" + +using namespace std; namespace libt2n { @@ -25,49 +46,307 @@ namespace libt2n socket_server::socket_server(int port, const char* ip) : server() { - type=tcp; + socket_type=tcp_s; } -socket_server::socket_server(const char* path, mode_t chmod, const char* user, const char* group) +socket_server::socket_server(const char* path, mode_t filemode, const char* user, const char* group) : server() { - type=unix; + socket_type=unix_s; unix_path=path; - struct sockaddr_un unix_name; - /* Create the socket. */ - socket = socket (PF_UNIX, SOCK_STREAM, 0); - if (socket < 0) + sock = socket (PF_UNIX, SOCK_STREAM, 0); + if (sock < 0) { string err="error opening socket: "; - err+=::strerror(errno); + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + set_socket_options(sock); + + /* Give the socket a name. */ + struct sockaddr_un unix_name; + unix_name.sun_family = AF_UNIX; + strncpy (unix_name.sun_path, unix_path.c_str(),sizeof(unix_name.sun_path)); + + /* just to make sure there is no other socket file */ + unlink (unix_name.sun_path); + + if (bind (sock, (struct sockaddr *) &unix_name, sizeof (unix_name)) < 0) + { + string err="error binding socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + /* change permissions */ + if (chmod (unix_name.sun_path, filemode) != 0) + { + string err="error changing permission: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + struct passwd *socket_user = getpwnam (user); + if (socket_user == NULL) + { + string err="error getting socket user: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + struct group *socket_group = getgrnam (group); + if (socket_group == NULL) + { + string err="error getting socket group: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + if (chown (unix_name.sun_path, socket_user->pw_uid, socket_group->gr_gid) != 0) + { + string err="error changing socket ownership: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + if (listen (sock, 5) < 0) + { + string err="error listening to socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } -// throw EXCEPTION(network_server_error,err); + /* clear & insert server sock into the fd_tab to prepare select */ + FD_ZERO(&connection_set); + FD_SET (sock, &connection_set); +} + +void socket_server::set_socket_options(int sock) +{ + int i=1; + + /* fast reuse enable */ + if (setsockopt(sock,SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)) < 0) + { + string err="error setting socket option: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + /* keepalive enable */ + if (setsockopt(sock,SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)) < 0) + { + string err="error setting socket option: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); } + /* close on exec */ + int fdflags; + fdflags=fcntl(sock,F_GETFD, 0); + if (fdflags < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + fdflags |= FD_CLOEXEC; + if (fcntl(sock,F_SETFD,fdflags) < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + /* non-blocking mode */ + int flflags; + flflags=fcntl(sock,F_GETFL,0); + if (flflags < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + flflags |= O_NONBLOCK; + if (fcntl(sock,F_SETFL,flflags) < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } } socket_server::~socket_server() { - close(socket); + close(sock); - if (type==unix) + if (socket_type==unix_s) unlink(unix_path.c_str()); } +void socket_server::new_connection() +{ + struct sockaddr_un clientname; + + unsigned int size = sizeof (clientname); + int newsock = accept (sock,(struct sockaddr *) &clientname,&size); + if (newsock < 0) + { + if (errno == EAGAIN) + { + log(error, "accept error (EAGAIN): no connection waiting"); + return; + } + + /* default: break */ + string err="error accepting connection: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + FD_SET (newsock, &connection_set); + + int i=1; + + /* keepalive enable */ + if (setsockopt(newsock,SOL_SOCKET, SO_KEEPALIVE, &i, sizeof(i)) < 0) + { + string err="error setting socket option: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + /* close on exec */ + int fdflags; + fdflags=fcntl(newsock,F_GETFD, 0); + if (fdflags < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + fdflags |= FD_CLOEXEC; + if (fcntl(newsock,F_SETFD,fdflags) < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + + /* non-blocking mode */ + int flflags; + flflags=fcntl(newsock,F_GETFL,0); + if (flflags < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + flflags |= O_NONBLOCK; + if (fcntl(newsock,F_SETFL,flflags) < 0) + { + string err="fcntl error on socket: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + add_connection(new socket_connection(newsock, get_default_timeout())); + + return; +} void socket_server::fill_buffer(long long usec_timeout) { + fd_set used_fdset=connection_set; + + /* set timeout */ + struct timeval tval; + struct timeval *timeout_ptr; + + if (usec_timeout == -1) + timeout_ptr = NULL; + else + { + timeout_ptr = &tval; + // timeout von long long usec in int sec + int usec umrechnen + tval.tv_sec = usec_timeout / 1000000; + tval.tv_usec = usec_timeout % 1000000; + } + + int ret=select (FD_SETSIZE, &used_fdset, NULL, NULL, timeout_ptr); + + if (ret < 0) + { + if (errno == EINTR) + { + // select interrupted by signal + ret=0; + } + else + { + string err="select error: "; + err+=strerror(errno); + log(error, err); + throw t2n_server_error(err); + } + } + + if (ret > 0) + { + // we have data pending + + // check for new connection + if (FD_ISSET (sock, &used_fdset)) + { + new_connection(); + } + + // check all connections for pending data + fill_connection_buffers(); + } + + return; } -socket_connection::socket_connection(int _socket, int _timeout) - : connection(_timeout) +void socket_server::fill_connection_buffers() { + std::map::iterator ie=connections.end(); + for(std::map::iterator i=connections.begin(); i != ie; i++) + if (!i->second->is_closed()) + { + socket_connection* cp=dynamic_cast(i->second); + cp->fill_buffer(connection_set); + } +} +socket_connection::socket_connection(int _sock, int _timeout) + : connection(_timeout) +{ + sock=_sock; } void socket_connection::close() @@ -75,14 +354,124 @@ void socket_connection::close() } -void socket_connection::fill_buffer(void) +void socket_connection::fill_buffer(fd_set &cur_fdset) { + bool try_again=false; + + if (is_closed() || !FD_ISSET (sock, &cur_fdset)) + return; // no data pending or connection closed + + // data pending -> go and get it + char socket_buffer[recv_buffer_size]; + + int nbytes = read (sock, socket_buffer, recv_buffer_size); + if (nbytes < 0) + { + if (errno == EAGAIN) + { + if (my_server) + my_server->log(server::error,"read error: no data (EAGAIN) for connection "+get_id_string()); + return; + } + else if (errno == EINTR) + { + // interrupted, try again + try_again=true; + } + else + { + if (my_server) + my_server->log(server::error,"error reading from socket of connection "+get_id_string()+": "+strerror(errno)); + return; + } + } + + // End-of-file + if (nbytes == 0 && !try_again) + { + close(); + return; + } + + // Data read -> store it + if (nbytes > 0) + buffer.assign(socket_buffer,nbytes); + + // more data waiting? + fd_set active_fd_set; + struct timeval tval; + + FD_ZERO (&active_fd_set); + FD_SET (sock, &active_fd_set); + + /* no waiting */ + tval.tv_sec=0; + tval.tv_usec=0; + + if (select (FD_SETSIZE, &active_fd_set, NULL, NULL, &tval) >0) + { + /* more data waiting -> recurse */ + fill_buffer(active_fd_set); + } + reset_timeout(); + + return; } void socket_connection::write(const std::string& data) { + static const unsigned int write_block_size=4096; + + if (is_closed()) + return; + + // prepend packet size to data + packet_size_indicator psize=data.size(); + string send_data(data); + send_data.insert(0,(char*)psize,sizeof(packet_size_indicator)); + + int offset = 0; + while (offset < send_data.size()) + { + unsigned int write_size=write_block_size; + + if (offset+write_size > send_data.size()) + write_size = send_data.size()-offset; + + int rtn; + while ((rtn=::write(sock, send_data.data()+offset, write_size)) && + rtn == -1 && (errno == EAGAIN || errno == EINTR)) + { + usleep (80000); + if (my_server) + my_server->log(server::debug,"resuming write() call after EAGAIN or EINTR for connection "+get_id_string()); + } + + if (rtn == -1) + { + if (my_server) + my_server->log(server::error,"write() error on connection "+get_id_string()+": "+strerror(errno)); + } + else if (rtn != write_size) + { + if (my_server) + { + ostringstream msg; + msg << "write() error on connection " << get_id() + << ": wrote " << rtn << " bytes, should have been " + << write_size << " (complete: " << send_data.size() << ")"; + + my_server->log(server::error,msg.str()); + } + } + + offset += write_size; + } + + reset_timeout(); + return; } }