// This file is part of Timmi. // // Timmi 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 3 of the License, or // (at your option) any later version. #include #include #include #include #include #include #include #include #include #include #include #include TCPServer::TCPServer(): listensocket_(0) {} TCPServer::~TCPServer() { int i; for (i = 0; i < clients_.size(); i++) { delete clients_.at(i); } clients_.clear(); } bool TCPServer::bind_socket(int port) { // Create new socket listensocket_ = socket(AF_INET, SOCK_STREAM, 0); if (listensocket_ < 0) { debug_with_errno(CRITICAL, "Creating listen socket"); return false; } // Set socket to reuse address, even if old instance has still // connections waiting. int on = 1; setsockopt(listensocket_, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); // Bind socket sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_port = htons(port); if (bind(listensocket_, (struct sockaddr *) &addr, sizeof(addr)) < 0) { debug_with_errno(CRITICAL, "Binding listen socket"); return false; } // Set socket to listen listen(listensocket_, 5); // Set non-blocking IO fcntl(listensocket_, F_SETFL, O_NONBLOCK); std::ostringstream msg(""); msg << "Listening on " << port; debug(INFO, msg.str()); return true; } bool TCPServer::wait_for_event(int timeout) const { // Construct a list of fds to poll for int count = clients_.size() + 1; pollfd *fds = new pollfd[count]; fds[0].fd = listensocket_; fds[0].events = POLLIN; fds[0].revents = 0; int i; for (i = 0; i < clients_.size(); i++) { fds[i + 1].fd = clients_.at(i)->getfd(); fds[i + 1].events = POLLIN; fds[i + 1].revents = 0; } int status = poll(fds, count, timeout); delete[] fds; if (status < 0) { debug_with_errno(WARNING, "Poll error"); } return status != 0; // 0 == timeout } void TCPServer::do_events() { do_accept(); do_recv(); } TCPSocket *TCPServer::find_socket(int fd) const { int i; for (i = 0; i < clients_.size(); i++) { if (clients_.at(i)->getfd() == fd) { return clients_.at(i); } } debug(WARNING, "Socket by fd not found"); return 0; } void TCPServer::send_line(int fd, std::string line) { // TODO: Finding the socket might be slow if we have many clients send_line(*find_socket(fd), line); } void TCPServer::send_line(TCPSocket &socket, std::string line) { socket.putline(line); std::ostringstream msg(""); msg << "Line to fd " << socket.getfd() << ": " << line; debug(VERBOSE, msg.str()); } void TCPServer::send_to_all(std::string line) { int i; for (i = 0; i < clients_.size(); i++) { send_line(*clients_.at(i), line); } } void TCPServer::mainloop() { while (true) { wait_for_event(); do_events(); } } void TCPServer::handle_connection(TCPSocket &socket) { std::ostringstream msg(""); msg << "New connection as " << socket.getfd() << " from " << socket.getaddr(); debug(INFO, msg.str()); } void TCPServer::handle_line(TCPSocket &socket, std::string line) { std::ostringstream msg(""); msg << "Line from fd " << socket.getfd() << ": " << line; debug(VERBOSE, msg.str()); } void TCPServer::handle_disconnect(TCPSocket &socket) { std::ostringstream msg(""); msg << "Lost connection " << socket.getfd() << " that was from " << socket.getaddr(); debug(INFO, msg.str()); int i; for (i = 0; i < clients_.size(); i++) { if (clients_.at(i)->getfd() == socket.getfd()) { delete clients_.at(i); clients_.erase(clients_.begin() + i); return; } } debug(WARNING, "Client to disconnect not found"); } // Private void TCPServer::do_accept() { sockaddr_in addr; int addrsize = sizeof(addr); int fd; TCPSocket *client; while(true) { // Try to get new connection fd = accept(listensocket_, (sockaddr *) &addr, (socklen_t *) &addrsize); if (fd < 0) { if (errno != EAGAIN) { // EAGAIN is normal in non-blocking usage debug_with_errno(WARNING, "Accept on listening socket"); } // No more new connections break; } client = new TCPSocket(fd, std::string(inet_ntoa(addr.sin_addr))); clients_.push_back(client); handle_connection(*client); } } void TCPServer::do_recv() { std::string line(""); int i; for (i = 0; i < clients_.size(); i++) { while (clients_.at(i)->getline(line)) { handle_line(*clients_.at(i), line); } if (!clients_.at(i)->good()) { handle_disconnect(*clients_.at(i)); // Be careful here - if the client was removed, i now points // to next one.. i--; } } }