/* net/netserver.h 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 #ifndef _WIN32 #include #include #include #include #include #else #include #endif #include #include #include "sys/sys.h" #include "core/application.h" #include "core/gameserver.h" #include "core/netclient.h" #include "core/netserver.h" #include "core/cvar.h" #include "core/func.h" #include "core/core.h" #include "core/stats.h" #include "core/zone.h" #ifdef _WIN32 typedef int socklen_t; #endif namespace core { NetServer::NetServer(std::string const host, unsigned int const port) { con_print << "^BInitializing network server..." << std::endl; // initialize variables netserver_fd = -1; netserver_error = true; // initialize socket netserver_fd = ::socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP); if (netserver_fd == -1) { con_error << "Network can't create socket!" << std::endl; //perror("socket"); return; } // Get the local adress to bind to netserver_addr.sin_family = AF_INET; netserver_addr.sin_port = htons(port); if (host.size()) { netserver_addr.sin_addr.s_addr = inet_addr(host.c_str()); if (netserver_addr.sin_addr.s_addr == INADDR_NONE) { con_error << "Network invalid address " << host << "!" << std::endl; return; } } else { netserver_addr.sin_addr.s_addr = htonl(INADDR_ANY); } memset(netserver_addr.sin_zero, '\0', sizeof(netserver_addr.sin_zero)); // bind the local address to the socket ( note the typecast) if (::bind(netserver_fd, (struct sockaddr *) &netserver_addr, sizeof(struct sockaddr)) == -1) { con_error << "Network can't bind to local address!" << std::endl; //perror("bind"); return; } con_print << " listening on " << inet_ntoa(netserver_addr.sin_addr) << ":" << ntohs(netserver_addr.sin_port) << std::endl; // add the listening socket to the file descriptor set FD_ZERO(&serverset); #ifdef _WIN32 FD_SET((unsigned int) netserver_fd, &serverset); #else FD_SET(netserver_fd, &serverset); #endif netserver_error = false; } NetServer::~NetServer() { con_print << "^BShutting down network server..." << std::endl; std::string netmsg("disconnect\n"); // delete all clients Clients:: iterator it; for (it = clients.begin(); it != clients.end(); it++) { // notify the game server if ((*it)->state() == NetClient::Connected) server()->player_disconnect((*it)->player()); (*it)->send_raw(netmsg); (*it)->transmit(); delete(*it); } clients.clear(); if (valid()) { #ifdef _WIN32 closesocket(fd()); #else close(fd()); #endif } } void NetServer::abort() { netserver_error = true; } // find the client corresponding to a player NetClient *NetServer::find_client(Player const *player) { for (Clients::iterator it = clients.begin(); it != clients.end(); it++) { if ((*it)->player() == player) { return (*it); } } return 0; } // remove disconnected clients void NetServer::reap() { for (Clients:: iterator it = clients.begin(); it != clients.end();) { NetClient *client = *it; if (client->client_timeout + NETTIMEOUT < application()->time()) { // client timed out, send a disconnect send_disconnect((*it)); // print a message std::string message("^B"); message.append(client->player()->name()); message.append(" ^Btimed out."); if (client->state() == NetClient::Connected) { server()->broadcast(message, client->player()); } else { con_print << message << std::endl; } } if (client->error()) { // notify other clients for (Clients::iterator cit = clients.begin(); cit != clients.end(); cit++) { if ((*cit) != (*it)) { send_player_disconnect_info((*cit), (*it)->player()); } } if (client->state() == NetClient::Connected) { // notify the game server server()->player_disconnect((*it)->player()); } // remove the client delete client; clients.erase(it++); } else { ++it; } } } void NetServer::receive() { if (error()) return; timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 2500; fd_set readset = serverset; int nb = select(fd() + 1, &readset, NULL, NULL, &timeout); if (nb == -1) { #ifndef _WIN32 // ncurses needs SIGWINCH catched if (errno == EINTR) { return; } #endif con_error << "Network error on select()" << std::endl; this->abort(); return; } if (nb && FD_ISSET(fd(), &readset)) { // receive incoming data struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); memset(recbuf, '\0', sizeof(recbuf)); ssize_t bytes_received = ::recvfrom(fd(), recbuf, FRAMESIZE - 1, 0, (struct sockaddr *) & client_addr, &client_addr_len); if (bytes_received == -1) { con_error << "Network error on recvfrom()!" << std::endl; this->abort(); return; } else { //con_debug << "Incoming data '" << recbuf << "'"<< bytes_received << " bytes" << std::endl; } Stats::network_bytes_received += bytes_received; // originator std::string client_host(inet_ntoa(client_addr.sin_addr)); unsigned int client_port = ntohs(client_addr.sin_port); // get messages from clients bool msg_received = false; for (Clients::iterator it = clients.begin(); it != clients.end() && !msg_received; it++) { NetClient *client = *it; if ((client->host() == client_host) && (client->port() == (int) client_port)) { // receive data client->receive(recbuf); // process parsed messages while (client->has_messages()) { std::string message; client->retreive(message); parse_incoming_message(client, message); } msg_received = true; } } if (!msg_received) { // new connection // FIXME maxclients NetClient *client = client_connect(client_host , client_port); if (client) { // receive data client->receive(recbuf); // process parsed messages while (client->has_messages()) { std::string message; client->retreive(message); parse_incoming_message(client, message); } } } } // remove dead connections reap(); } NetClient * NetServer::client_connect(std::string const host, int const port) { con_debug << "client_connect " << host << ":" << port << "\n"; NetClient *client = new NetClient(host, port, fd()); if (client->error()) { con_warn << client->host() << ":" << client->port() << " connection failed!\n"; delete(client); return 0; } clients.push_back(client); client->player()->set_dirty(false); return client; } void NetServer::client_initialize(NetClient *client) { // send welcome message std::string welcome("^B"); welcome.append(Cvar::sv_name->str()); client->player()->send(welcome); client->transmit(); // send zones for (Zone::Registry::iterator it = Zone::registry().begin(); it != Zone::registry().end(); it++) { send_zone_update(client, (*it).second); } // send player list for (GameInterface::Players::iterator it = server()->players().begin(); it != server()->players().end(); it++) { if (((*it)->id() != client->player()->id())) { send_player_update(client, (*it)); } } // send connect completed std::string connect("connect\n"); client->send_raw(connect); client->transmit(); // set client state to pending client->client_state = NetClient::Pending; } // send updates to one client void NetServer::client_frame(NetClient *client, unsigned long timestamp) { if (client->state() != NetClient::Connected) return; Zone *zone = client->player()->zone(); if (zone) { if (client->player()->zonechange()) { // send zone info send_zone_update(client, zone); // send entities in zone if (client->player()->zone()) { for (Zone::Content::iterator it = zone->content().begin(); it != zone->content().end(); it++) { Entity *entity = (*it); if (!entity->entity_destroyed) { send_entity_create(client, entity); } } } // the actual zone change is send by the "pif" message } else { // send a server frame marker send_frame_marker(client, timestamp); // send updates for entities in the zone for (Entity::Registry::iterator it = Entity::registry().begin(); it != Entity::registry().end(); it++) { Entity *entity = (*it).second; if (entity->zone() == zone) { if (entity->entity_destroyed) { if (!entity->entity_created) { send_entity_delete(client, entity); } } else if (entity->entity_created) { send_entity_create(client, entity); } else if (entity->oldzone()) { // this entity has entered the zone send_entity_create(client, entity); } else if (entity->dirty() && !(entity->flags() & Entity::Static)) { // FIXME only within visual range send_entity_update(client, entity); } } else if (entity->oldzone() == zone) { // the entity has left the zone send_entity_delete(client, entity); } } } } // send updates for other players for (GameInterface::Players::iterator it = server()->players().begin(); it != server()->players().end(); it++) { if (((*it)->id() != client->player()->id()) && ((*it)->dirty())) { send_player_update(client, (*it)); } } } // run a network server frame, send updates to clients void NetServer::frame(unsigned long timestamp) { /* FIXME Only entities within visual range should send updates (1024 game units?) Fix reliable messages It would be nice if players with the highest ping got their updates first */ // send updates to each client for (Clients::iterator it = clients.begin(); it != clients.end(); it++) { NetClient *client = *it; client->transmit(); // send game state changes if (client->state() == NetClient::Connected) client_frame(client, timestamp); if (client->player()->dirty() || client->player()->zonechange()) { send_player_update(client); } client->transmit(); } // clear dirty state for (Clients::iterator it = clients.begin(); it != clients.end(); it++) { NetClient *client = *it; if (client->player()->dirty() || client->player()->zonechange()) { client->player()->set_dirty(false); client->player()->set_zonechange(false); } } } // send outgoing messages to clients /** * The following messages can be send to a client * * frame * ent * die * ping * sup * * msg * supported message channels are "info" "public" "rcon" and "snd" * "snd" is a special channel to transmit sound events * zone */ // send a "msg " message to one client void NetServer::send_message(NetClient *client, const char *channel, std::string const & message) { if (!channel) return; std::string msg("msg "); msg.append(channel); msg += ' '; msg.append(message); msg += '\n'; client->send_raw(msg); } // disconnect a client void NetServer::send_disconnect(NetClient *client) { client->send_raw("disconnect\n"); client->transmit(); client->abort(); } // send a "frame" message to a client void NetServer::send_frame_marker(NetClient *client, unsigned long timestamp) { std::ostringstream msg(""); msg << "frame " << timestamp << "\n"; if (client->state() == NetClient::Connected) { client->send_raw(msg.str()); } } // send a "ent" create entity message to all clients void NetServer::send_entity_create(NetClient *client, Entity *entity) { if ((client->state() == NetClient::Connected) && !entity->serverside()) { std::ostringstream msg; msg << "ent " << entity->id() << " " << entity->type() << " "; entity->serialize_server_create(msg); msg << '\n'; client->send_raw(msg.str()); // send entity menus for (Entity::Menus::iterator it = entity->menus().begin(); it != entity->menus().end(); it++) { msg.clear(); msg.str(""); msg << "menu " << entity->id() << " "; Descriptions::serialize((*it), msg); msg << '\n'; client->send_raw(msg.str()); // con_debug << "sending menu " << entity->label() << " " << (*it)->label() << std::endl; } } } // send a "die" delete entity message to a client void NetServer::send_entity_delete(NetClient *client, Entity *entity) { if ((client->state() == NetClient::Connected) && !entity->serverside()) { std::ostringstream msg(""); msg << "die " << entity->id() << '\n'; client->send_raw(msg.str()); } } // broadcast a "sup" server update entity message to all clients void NetServer::send_entity_update(NetClient *client, Entity *entity) { if ((client->state() == NetClient::Connected) && !entity->serverside()) { std::ostringstream msg; msg << "sup " << entity->id() << " "; entity->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } } // send a "zone" update zone message to a client void NetServer::send_zone_update(NetClient *client, Zone *zone) { std::ostringstream msg; msg << "zone " << zone->id() << " "; zone->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a "pif" update player information to a single player void NetServer::send_player_update(NetClient *client) { std::ostringstream msg; msg << "pif 0 "; client->player()->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a short "pif" update player information to a single player void NetServer::send_player_update(NetClient *client, Player *player) { std::ostringstream msg; msg << "pif " << player->id() << ' '; player->serialize_short_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // send a "pid" player disconnect information void NetServer::send_player_disconnect_info(NetClient *client, Player *player) { std::ostringstream msg; msg << "pid " << player->id() << '\n'; client->send_raw(msg.str()); } // send a "inf" info record void NetServer::send_info_update(NetClient *client, Info *info) { std::ostringstream msg; msg << "inf " << '"' << info->label() << '"' << ' '; info->serialize_server_update(msg); msg << '\n'; client->send_raw(msg.str()); } // parse incoming client messages /** * The following incoming protocol messages are parsed; * * disconnect * cmd * cup * inf * pif * ping * say * priv * */ void NetServer::parse_incoming_message(NetClient *client, const std::string & message) { if (!message.size()) return; std::stringstream msgstream(message); std::string command; msgstream >> command; // disconnect if (command == "disconnect") { client->abort(); return; } // connection request // connect is the first command expected from the client if (command == "connect") { if (client->state() != NetClient::Connecting) return; unsigned int protover; if (msgstream >> protover) { if (protover != PROTOCOLVERSION) { // set protocol version mismatch notification std::stringstream netmsgstream(""); netmsgstream << "^WProtocol version mismatch: "; netmsgstream << "client " << protover << " server " << PROTOCOLVERSION << "!\n"; con_print << client->host() << ":" << client->port() << " " << netmsgstream.str() << std::endl; send_message(client, "info", netmsgstream.str()); send_disconnect(client); } else { client_initialize(client); } } else { std::string message("Unknown client protocol version!"); con_print << client->host() << ":" << client->port() << " " << message << std::endl; send_message(client, "info", message); send_disconnect(client); } return; } // pif - update player information // client connection is completed on the first pif if (command == "pif") { std::string oldname(client->player()->name()); client->player()->receive_client_update(msgstream); if (client->state() == NetClient::Pending) { client->client_state = NetClient::Connected; server()->player_connect(client->player()); } else if ((client->state() == NetClient::Connected) && (client->player()->name() != oldname)) { std::string netmsg("^B"); netmsg.append(oldname); netmsg.append(" ^Brenamed to "); netmsg.append(client->player()->name()); server()->broadcast(netmsg); } } if (command == "ping") { unsigned long timestamp; if (msgstream >> timestamp) { client->player()->set_ping(application()->timestamp() - server()->startup() - timestamp); } return; } if (client->state() != NetClient::Connected) return; // cmd if (command == "cmd") { if (message.size() > command.size() + 1) { std::string cmdline(message.substr(command.size() + 1)); server()->exec(client->player(), cmdline); } return; } if (command == "inf") { std::string n; char c; while ((msgstream.get(c)) && (c != '"')); while ((msgstream.get(c)) && (c != '"')) n += c; if (n.size()) { Info *info = Info::find(n); if (info) { send_info_update(client, info); client->transmit(); } } } if (command == "rcon") { if ((message.size() > command.size() + 1) && Cvar::sv_password->str().size()) { if ((Cvar::sv_password->str().compare(client->player()->rconpassword()) == 0)) { con_print << "^B" << client->player()->name() << "^F rcon: " << message.substr(command.size() + 1) << std::endl; core::CommandBuffer::exec(); core::console()->set_rcon(true); core::cmd() << message.substr(command.size() + 1) << "\n"; core::CommandBuffer::exec(); while (console()->rconbuf().size()) { send_message(client, "rcon", (*console()->rconbuf().begin())); core::console()->rconbuf().pop_front(); } // disable rcon buffering console()->set_rcon(false); } else { send_message(client, "info", "rcon access denied"); con_print << "^B" << client->player()->name() << "^W rcon access denied" << std::endl; } } return; } // cup - client update entity if (command == "cup") { //con_debug << message << "\n"; unsigned int id; if (msgstream >> id) { Entity *entity = Entity::find(id); if (!entity) { con_warn << client->host() << ":" << client->port() << " update for unknown entity " << id << "\n"; return; } if (entity->type() != Entity::Controlable) { con_warn << client->host() << ":" << client->port() << " update for non-controlable entity " << id << "\n"; return; } EntityControlable *entitycontrolable = (EntityControlable *)entity; if (entitycontrolable->owner() != client->player()) { con_warn << client->host() << ":" << client->port() << " update for not-owned entity " << id << "\n"; return; } entitycontrolable->set_dirty(true); entitycontrolable->receive_client_update(msgstream); } return; } // say if (command == "say") { if (message.size() > command.size() + 1) { server()->say(client->player(), message.substr(command.size() + 1)); } return; } // priv if (command == "priv") { if (message.size() > command.size() + 1) { server()->private_message(client->player(), message.substr(command.size() + 1)); } return; } } }