/* 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/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(fd()); 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 the game server if (client->state() == NetClient::Connected) 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); if (client->error()) { con_warn << client->host() << ":" << client->port() << " connection failed!\n"; delete(client); return 0; } clients.push_back(client); client->player()->player_dirty = false; return client; } void NetServer::client_initialize(NetClient *client) { // send welcome message std::string welcome("^B"); welcome.append(Cvar::sv_name->str()); send_message(client, "info", welcome); client->transmit(fd()); // send zones for (Zone::Registry::iterator it = Zone::registry().begin(); it != Zone::registry().end(); it++) { send_zone_update(client, (*it).second); } // send connect completed std::string connect("connect\n"); client->send_raw(connect); client->transmit(fd()); // set client state to pending client->client_state = NetClient::Pending; } // send updates to one client void NetServer::client_frame(NetClient *client, float timestamp, float previoustimestamp) { 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, previoustimestamp); // 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); } } } } } // run a network server frame, send updates to clients void NetServer::frame(float timestamp, float previoustimestamp) { /* FIXME This code has to be rewritten to send per-player updates. Players should only receive updates from their current zone Fix zonechange events 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(fd()); if (client->state() == NetClient::Connected) client_frame(client, timestamp, previoustimestamp); // update player info always gets through if (client->player()->dirty() || client->player()->zonechange()) { send_player_update(client); client->player()->player_dirty = false; client->player()->player_zonechange = false; } client->transmit(fd()); } } // send outgoing messages to clients /** * The following messages can be send to a client * * frame * ent * die * sup * msg * supported message channels are "info" "public" "rcon" and "snd" * "snd" is a special channel to transmit sound events * zone */ // broadcast a "msg " message to all clients void NetServer::broadcast_message(const char *channel, std::string const & message, Player *ignore_player) { if (!channel) return; std::string msg("msg "); msg.append(channel); msg += ' '; msg.append(message); msg += '\n'; for (Clients::iterator it = clients.begin(); it != clients.end(); it++) { if (((*it)->player() && (*it)->player() != ignore_player) && ((*it)->state() == NetClient::Connected)) { (*it)->send_raw(msg); } } } // 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(netserver_fd); client->abort(); } // send a "frame" message to a client void NetServer::send_frame_marker(NetClient *client, float timestamp, float previoustimestamp) { std::ostringstream msg(""); msg << "frame " << timestamp << " " << previoustimestamp << "\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 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()); } // parse incoming client messages /** * The following incoming protocol messages are parsed; * * disconnect * cmd * cup * 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") { 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; } // 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->entity_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; } } }