rubidium@5720: /* $Id$ */ rubidium@5720: celestar@6447: /** celestar@6447: * @file tcp.cpp Basic functions to receive and send TCP packets. celestar@6447: */ celestar@6447: rubidium@5720: #ifdef ENABLE_NETWORK rubidium@5720: rubidium@5720: #include "../../stdafx.h" rubidium@5720: #include "../../debug.h" rubidium@5720: #include "../../openttd.h" rubidium@5720: #include "../../variables.h" rubidium@5726: #include "table/strings.h" rubidium@5720: #include "../../functions.h" rubidium@5720: rubidium@5778: #include "../network_data.h" rubidium@5720: #include "packet.h" rubidium@5720: #include "tcp.h" rubidium@5838: #include "../../helpers.hpp" rubidium@5720: rubidium@5875: /** Very ugly temporary hack !!! */ rubidium@5875: void NetworkTCPSocketHandler::Initialize() rubidium@5875: { rubidium@5875: this->sock = INVALID_SOCKET; rubidium@5875: rubidium@5875: this->index = 0; rubidium@5875: this->last_frame = 0; rubidium@5875: this->last_frame_server = 0; rubidium@5875: this->lag_test = 0; rubidium@5875: rubidium@5875: this->status = STATUS_INACTIVE; rubidium@5875: this->has_quit = false; rubidium@5875: this->writable = false; rubidium@5875: rubidium@5875: this->packet_queue = NULL; rubidium@5875: this->packet_recv = NULL; rubidium@5875: rubidium@5875: this->command_queue = NULL; rubidium@5875: } rubidium@5875: rubidium@6153: void NetworkTCPSocketHandler::Destroy() rubidium@6153: { rubidium@6153: closesocket(this->sock); rubidium@6153: this->writable = false; rubidium@6153: this->has_quit = true; rubidium@6153: rubidium@6153: /* Free all pending and partially received packets */ rubidium@6153: while (this->packet_queue != NULL) { rubidium@6153: Packet *p = this->packet_queue->next; rubidium@6153: delete this->packet_queue; rubidium@6153: this->packet_queue = p; rubidium@6153: } rubidium@6153: delete this->packet_recv; rubidium@6153: this->packet_recv = NULL; rubidium@6153: rubidium@6153: while (this->command_queue != NULL) { rubidium@6153: CommandPacket *p = this->command_queue->next; rubidium@6153: free(this->command_queue); rubidium@6153: this->command_queue = p; rubidium@6153: } rubidium@6153: } rubidium@6153: rubidium@5720: /** rubidium@5720: * Functions to help NetworkRecv_Packet/NetworkSend_Packet a bit rubidium@5720: * A socket can make errors. When that happens this handles what to do. rubidium@5720: * For clients: close connection and drop back to main-menu rubidium@5720: * For servers: close connection and that is it rubidium@5720: * @return the new status rubidium@5875: * TODO: needs to be splitted when using client and server socket packets rubidium@5720: */ rubidium@5875: NetworkRecvStatus NetworkTCPSocketHandler::CloseConnection() rubidium@5720: { rubidium@5875: NetworkCloseClient(this); rubidium@5720: rubidium@5720: /* Clients drop back to the main menu */ rubidium@5720: if (!_network_server && _networking) { rubidium@5720: _switch_mode = SM_MENU; rubidium@5720: _networking = false; rubidium@5720: _switch_mode_errorstr = STR_NETWORK_ERR_LOSTCONNECTION; rubidium@5720: rubidium@5720: return NETWORK_RECV_STATUS_CONN_LOST; rubidium@5720: } rubidium@5720: rubidium@5720: return NETWORK_RECV_STATUS_OKAY; rubidium@5720: } rubidium@5720: rubidium@5720: /** rubidium@5720: * This function puts the packet in the send-queue and it is send as rubidium@5720: * soon as possible. This is the next tick, or maybe one tick later rubidium@5720: * if the OS-network-buffer is full) rubidium@5720: * @param packet the packet to send rubidium@5720: */ rubidium@6153: void NetworkTCPSocketHandler::Send_Packet(Packet *packet) rubidium@5720: { rubidium@5720: Packet *p; rubidium@5720: assert(packet != NULL); rubidium@5720: rubidium@6149: packet->PrepareToSend(); rubidium@5720: rubidium@5720: /* Locate last packet buffered for the client */ rubidium@6153: p = this->packet_queue; rubidium@5720: if (p == NULL) { rubidium@5720: /* No packets yet */ rubidium@6153: this->packet_queue = packet; rubidium@5720: } else { rubidium@5720: /* Skip to the last packet */ rubidium@5720: while (p->next != NULL) p = p->next; rubidium@5720: p->next = packet; rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: /** rubidium@5720: * Sends all the buffered packets out for this client. It stops when: rubidium@5720: * 1) all packets are send (queue is empty) rubidium@5720: * 2) the OS reports back that it can not send any more rubidium@5720: * data right now (full network-buffer, it happens ;)) rubidium@5720: * 3) sending took too long rubidium@5720: */ rubidium@6153: bool NetworkTCPSocketHandler::Send_Packets() rubidium@5720: { rubidium@5720: ssize_t res; rubidium@5720: Packet *p; rubidium@5720: rubidium@5720: /* We can not write to this socket!! */ rubidium@6153: if (!this->writable) return false; rubidium@6153: if (!this->IsConnected()) return false; rubidium@5720: rubidium@6153: p = this->packet_queue; rubidium@5720: while (p != NULL) { rubidium@6153: res = send(this->sock, (const char*)p->buffer + p->pos, p->size - p->pos, 0); rubidium@5720: if (res == -1) { rubidium@5720: int err = GET_LAST_ERROR(); rubidium@5720: if (err != EWOULDBLOCK) { rubidium@5720: /* Something went wrong.. close client! */ rubidium@5720: DEBUG(net, 0, "send failed with error %d", err); rubidium@6153: this->CloseConnection(); rubidium@5720: return false; rubidium@5720: } rubidium@5720: return true; rubidium@5720: } rubidium@5720: if (res == 0) { rubidium@5720: /* Client/server has left us :( */ rubidium@6153: this->CloseConnection(); rubidium@5720: return false; rubidium@5720: } rubidium@5720: rubidium@5720: p->pos += res; rubidium@5720: rubidium@5720: /* Is this packet sent? */ rubidium@5720: if (p->pos == p->size) { rubidium@5720: /* Go to the next packet */ rubidium@6153: this->packet_queue = p->next; rubidium@6149: delete p; rubidium@6153: p = this->packet_queue; rubidium@5720: } else { rubidium@5720: return true; rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: return true; rubidium@5720: } rubidium@5720: rubidium@5720: /** rubidium@5720: * Receives a packet for the given client rubidium@5720: * @param status the variable to store the status into rubidium@5720: * @return the received packet (or NULL when it didn't receive one) rubidium@5720: */ rubidium@6153: Packet *NetworkTCPSocketHandler::Recv_Packet(NetworkRecvStatus *status) rubidium@5720: { rubidium@5720: ssize_t res; rubidium@5720: Packet *p; rubidium@5720: rubidium@5720: *status = NETWORK_RECV_STATUS_OKAY; rubidium@5720: rubidium@6153: if (!this->IsConnected()) return NULL; rubidium@5720: rubidium@6153: if (this->packet_recv == NULL) { rubidium@6153: this->packet_recv = new Packet(this); rubidium@6153: if (this->packet_recv == NULL) error("Failed to allocate packet"); rubidium@5720: } rubidium@5720: rubidium@6153: p = this->packet_recv; rubidium@5720: rubidium@5720: /* Read packet size */ rubidium@5720: if (p->pos < sizeof(PacketSize)) { rubidium@5720: while (p->pos < sizeof(PacketSize)) { rubidium@5875: /* Read the size of the packet */ rubidium@6153: res = recv(this->sock, (char*)p->buffer + p->pos, sizeof(PacketSize) - p->pos, 0); rubidium@5720: if (res == -1) { rubidium@5720: int err = GET_LAST_ERROR(); rubidium@5720: if (err != EWOULDBLOCK) { rubidium@5720: /* Something went wrong... (104 is connection reset by peer) */ rubidium@5720: if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); rubidium@6153: *status = this->CloseConnection(); rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: /* Connection would block, so stop for now */ rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: if (res == 0) { rubidium@5720: /* Client/server has left */ rubidium@6153: *status = this->CloseConnection(); rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: p->pos += res; rubidium@5720: } rubidium@5720: rubidium@6149: /* Read the packet size from the received packet */ rubidium@6149: p->ReadRawPacketSize(); rubidium@5720: rubidium@5720: if (p->size > SEND_MTU) { rubidium@6153: *status = this->CloseConnection(); rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: } rubidium@5720: rubidium@5720: /* Read rest of packet */ rubidium@5720: while (p->pos < p->size) { rubidium@6153: res = recv(this->sock, (char*)p->buffer + p->pos, p->size - p->pos, 0); rubidium@5720: if (res == -1) { rubidium@5720: int err = GET_LAST_ERROR(); rubidium@5720: if (err != EWOULDBLOCK) { rubidium@5720: /* Something went wrong... (104 is connection reset by peer) */ rubidium@5720: if (err != 104) DEBUG(net, 0, "recv failed with error %d", err); rubidium@6153: *status = this->CloseConnection(); rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: /* Connection would block */ rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: if (res == 0) { rubidium@5720: /* Client/server has left */ rubidium@6153: *status = this->CloseConnection(); rubidium@5720: return NULL; rubidium@5720: } rubidium@5720: rubidium@5720: p->pos += res; rubidium@5720: } rubidium@5720: rubidium@5720: /* Prepare for receiving a new packet */ rubidium@6153: this->packet_recv = NULL; rubidium@5720: rubidium@6149: p->PrepareToRead(); rubidium@5720: return p; rubidium@5720: } rubidium@5720: rubidium@6153: bool NetworkTCPSocketHandler::IsPacketQueueEmpty() rubidium@6153: { rubidium@6153: return this->packet_queue == NULL; rubidium@6153: } rubidium@6153: rubidium@6153: rubidium@5720: #endif /* ENABLE_NETWORK */