/* net/netconnection.cc This file is part of the Osirion project and is distributed under the terms of the GNU General Public License version 2 */ #include #include #include "sys/sys.h" #include "core/application.h" #include "core/gameconnection.h" #include "core/netconnection.h" #include "core/player.h" #include "core/stats.h" namespace core { NetConnection::NetConnection() { connection_timeout = core::application()->time(); connection_state = Connecting; receive_compressed = false; received_compressed_size = 0; compressed_size = 0; } NetConnection::~NetConnection() { disconnect(); } void NetConnection::abort() { connection_error= true; } void NetConnection::connect(std::string const &to_host, int to_port) { connection_error = false; connection_fd = -1; connection_state = Connecting; if (valid()) return; // resolve serverhostname struct hostent *serverhostent; serverhostent = gethostbyname(to_host.c_str()); if (!serverhostent) { con_warn << "Could not resolve '" << to_host.c_str() << "'" << std::endl; abort(); return; } // Get a socket file descriptor connection_fd = socket(PF_INET, SOCK_DGRAM, 0); if (connection_fd == -1) { //con_error << "Network socket() failed!" << std::endl; abort(); return; } // make the connection server_addr.sin_family = AF_INET; server_addr.sin_port = htons(to_port); // FIXME inet_addr can still fail server_addr.sin_addr.s_addr = inet_addr(inet_ntoa(*((struct in_addr *)serverhostent->h_addr))); memset(server_addr.sin_zero, '\0', sizeof server_addr.sin_zero); if (server_addr.sin_addr.s_addr == INADDR_NONE) { con_error << "Network invalid address " << to_host << "!" << std::endl; abort(); return; } connection_host = to_host; connection_port = to_port; connection_error = false; FD_ZERO(&clientset); #ifdef _WIN32 FD_SET((unsigned int) fd(), &clientset); #else FD_SET(fd(), &clientset); #endif connection_timeout = application()->time(); connection_keepalive = application()->time(); game()->localplayer()->player_dirty = true; con_print << "Connecting to " << inet_ntoa(*((struct in_addr *)serverhostent->h_addr)) << ":" << to_port << "..." << std::endl; } void NetConnection::disconnect() { if (connection_fd != -1) { sendq.clear(); sendq.assign("disconnect\n"); transmit(); FD_ZERO(&clientset); #ifdef _WIN32 closesocket(connection_fd); #else close(connection_fd); #endif } connection_fd = -1; connection_error = false; connection_host.clear(); connection_port = 0; connection_state = Connecting; } bool NetConnection::has_messages() const { return (recvq.size() > 0); } void NetConnection::retreive(std::string & message) { if (recvq.size() > 0) { message.assign(recvq.front()); recvq.pop_front(); } else { message.clear(); } } // receive data and decode it into lines void NetConnection::receive() { // TODO: binary mode data transfer if (error() || invalid()) return; ssize_t bytes_received; memset(recvbuf, 0, BLOCKSIZE); bytes_received = ::recv(connection_fd, recvbuf, BLOCKSIZE-1, 0); Stats::network_bytes_received += bytes_received; connection_timeout = core::application()->time(); if (bytes_received == 0) { con_print << "^BDisconnected." << std::endl; abort(); return; } else if (bytes_received < 0) { con_error << "Network receive() error!" << std::endl; //perror("recv"); abort(); return; } const char *c = recvbuf; while (bytes_received) { if (receive_compressed) { zrecvbuf[received_compressed_size] = *c; received_compressed_size++; if (received_compressed_size == compressed_size) { // uncompress char zunbuf[BLOCKSIZE]; memset(zunbuf, 0, sizeof(zunbuf)); size_t zunbuf_size = BLOCKSIZE - 1; int status = uncompress((Bytef *) zunbuf, &zunbuf_size, (Bytef *) zrecvbuf, compressed_size); if (status != Z_OK) { con_warn << "zlib error " << status << " uncompressing incoming datablock!\n"; } else { const char *zc = zunbuf; while (*zc) { if (( *zc == '\n') || (*zc == '\r')) { if (messageblock.size()) { recvq.push_back(messageblock); messageblock.clear(); } } else { if (messageblock.size() < FRAMESIZE) { messageblock += *zc; } else { con_warn << "Incoming uncompressed message exceeds " << FRAMESIZE << " bytes!\n"; messageblock.clear(); } } zc++; } } // reset receive_compressed = false; received_compressed_size = 0; compressed_size = 0; } } else if (!messageblock.size() && (bytes_received > 3 ) && ( *c == '\xff') && (*(c+1) == '\xff')) { receive_compressed = true; received_compressed_size = 0; compressed_size = *(c+2) + (*(c+3) << 8); c += 3; bytes_received -= 3; memset(zrecvbuf, 0, sizeof(zrecvbuf)); } else if (( *c == '\n') || (*c == '\r')) { if (messageblock.size()) { recvq.push_back(messageblock); messageblock.clear(); } } else { if (messageblock.size() < FRAMESIZE) { messageblock += *c; } else { con_warn << "Incoming message exceeds " << FRAMESIZE << " bytes!\n"; messageblock.clear(); } } c++; bytes_received--; } } void NetConnection::frame(float seconds) { timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; fd_set readset = clientset; int nb = select(fd()+1, &readset, NULL, NULL, &timeout); if (nb == 0) { if (connection_timeout + NETTIMEOUT < core::application()->time()) { con_error << "Connection timeout!\n"; abort(); } return; } if (nb == -1) { con_error << "Network error on select()" << std::endl; //perror("select"); abort(); } while (FD_ISSET(this->fd(), &readset) && !error()) { receive(); while (has_messages()) { std::string message; retreive(message); parse_incoming_message(message); } nb = select(fd()+1, &readset, NULL, NULL, &timeout); if (nb == 0) { return; } if (nb == -1) { con_error << "Network error on select()" << std::endl; //perror("select"); abort(); } } } void NetConnection::send(std::string const &msg) { sendq.append(msg); } void NetConnection::transmit() { if (error() || invalid()) return; if (!sendq.size()) { if (connection_keepalive + NETTIMEOUT /2 < application()->time()) { sendq.assign("ping\n"); } else { return; } } else if (sendq.size() >= BLOCKSIZE - 16) { con_warn << "Outgoing data exceeds " << BLOCKSIZE - 16 << " bytes!\n"; sendq.clear(); return; } ssize_t bytes_sent = 0; while (sendq.size()) { bytes_sent = ::sendto(connection_fd, sendq.c_str(), sendq.size(), 0, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (bytes_sent <= 0) { con_error << "Network send() error!" << std::endl; //perror("send"); abort(); return; } // assert (bytes_sent <= sendbuf.size()); sendq.erase(0, bytes_sent); Stats::network_bytes_sent += bytes_sent; } connection_keepalive = application()->time(); } // parse incoming client messages /** * The following incoming messages are parsed; * * connect * disconnect * msg info * msg public * msg snd * die * ent * frame * sup * pif */ void NetConnection::parse_incoming_message(const std::string & message) { std::istringstream msgstream(message); std::string command; msgstream >> command; if (command == "msg") { std::string level; if (msgstream >> level) { if (level =="info") { if (message.size() > 9) { application()->notify_message(message.substr(9)); } } else if (level == "public") { // FIXME - separate sender nickname if (message.size() > 11) { application()->notify_message(message.substr(11)); application()->notify_sound("com/chat.wav"); } } else if (level == "snd") { if (message.size() > 8) { application()->notify_sound(message.substr(8).c_str()); } } } } else if (command == "connect") { connection_state = Connected; con_print << "^BConnected." << std::endl; return; } else if (command == "disconnect") { con_error << "Server disconnected!" << std::endl; abort(); } else if (command == "ping") { } else if (command == "frame") { float timestamp, prevtimestamp; if ((msgstream >> timestamp) && (msgstream >> prevtimestamp)) { game()->reset_clientstate(timestamp, prevtimestamp); } } else if (command == "die") { unsigned int id; if (msgstream >> id) { //con_debug << "Received die entity id " << id << std::endl; Entity *e = Entity::find(id); if (localcontrol() == e) localplayer()->player_control = 0; if (e) Entity::remove(id); } } else if (command == "ent") { unsigned int type; if (msgstream >> type) { //con_debug << "Received create entity type " << type << std::endl; switch (type) { case Entity::Default: game()->update_entity_clientstate(new Entity(msgstream)); break; case Entity::Dynamic: game()->update_entity_clientstate(new EntityDynamic(msgstream)); break; case Entity::Controlable: game()->update_entity_clientstate(new EntityControlable(msgstream)); break; case Entity::Globe: game()->update_entity_clientstate(new EntityGlobe(msgstream)); break; default: con_warn << "Create for unknown entity type " << type << std::endl; break; } } } else if (command == "sup") { if (connection_state == Connected) { unsigned int id; if (msgstream >> id) { //con_debug << "Received update entity id " << id << std::endl; Entity *entity = Entity::find(id); if (!entity) { con_warn << "Update for unknown entity " << id << std::endl; } else entity->recieve_server_update(msgstream); } } } else if (command == "pif") { //con_debug << "Received update player info" << std::endl; connection()->localplayer()->recieve_server_update(msgstream); } } }