/* 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 #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" #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; } /* // set socket options socklen_t yes = 1; if (::setsockopt(netserver_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(socklen_t)) == -1) { con_error << "Network can't set socket options!" << std::endl; //perror("setsockopt"); 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 std::list:: 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(netmsg); (*it)->transmit(fd()); delete (*it); } clients.clear(); if (valid()) { #ifdef _WIN32 closesocket(fd()); #else close(fd()); #endif } } void NetServer::abort() { netserver_error = true; } // remove disconnected clients void NetServer::reap() { for (std::list:: iterator it = clients.begin(); it != clients.end(); it++) { NetClient *client = *it; if (client->client_timeout + NETTIMEOUT < application()->time()) { // client timed out, send a disconnect std::string netmsg("disconnect\n"); (*it)->send(netmsg); (*it)->transmit(fd()); (*it)->abort(); // 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 clients.erase(it); delete client; it=clients.begin(); } } } void NetServer::transmit() { for (std::list::iterator it = clients.begin(); it != clients.end(); it++) { (*it)->transmit(fd()); } } 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 (std::list::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::ostringstream netmsg; netmsg.str(""); netmsg << "msg info ^B" << Cvar::sv_name->str() << "\n"; client->send(netmsg.str()); client->transmit(fd()); // send entities std::map::iterator it; for (it=Entity::registry.begin(); it != Entity::registry.end(); it++) { netmsg.str(""); netmsg << "ent "; (*it).second->serialize(netmsg); netmsg << "\n"; client->send(netmsg.str()); } netmsg.str("connect\n"); client->send(netmsg.str()); client->transmit(fd()); } void NetServer::send(NetClient * client, std::string const & message) { client->send(message); } // send a message to all clients void NetServer::broadcast(std::string const & message, Player *ignore_player) { //std::cout << "NetServer: " << osstream.str(); for (std::list::iterator it = clients.begin(); it != clients.end(); it++) { NetClient *client = *it; if (!client->error() && (client->state() == NetClient::Connected) && client->player() != ignore_player) client->send(message); } } // find the client corresponding to a player NetClient *NetServer::find_client(Player const *player) { for (std::list::iterator it = clients.begin(); it != clients.end(); it++) { if ((*it)->player() == player) { return (*it); } } return 0; } // parse incoming client messages /** * The following incoming protocol messages are parsed; * * disconnect * cmd * cup * pif * ping * say * */ 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; } // pif - update player information // pif is the first command expected from the client if (command == "pif") { std::string oldname(client->player()->name()); client->player()->recieve_client_update(msgstream); if (client->state() == NetClient::Connecting) { client->client_state = NetClient::Connected; client_initialize(client); 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"; } else { entity->entity_dirty = true; entity->recieve_client_update(msgstream); } } return; } // say if (command == "say") { if (message.size() > command.size()+1) { server()->say(client->player(), message.substr(command.size()+1)); } return; } } }