553 lines
18 KiB
C++
553 lines
18 KiB
C++
// Filename: connection.cxx
|
|
// Created by: jns (07Feb00)
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
//
|
|
// PANDA 3D SOFTWARE
|
|
// Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
//
|
|
// All use of this software is subject to the terms of the revised BSD
|
|
// license. You should have received a copy of this license along
|
|
// with this source code in a file named "LICENSE."
|
|
//
|
|
////////////////////////////////////////////////////////////////////
|
|
|
|
#include "connection.h"
|
|
#include "connectionManager.h"
|
|
#include "netDatagram.h"
|
|
#include "datagramTCPHeader.h"
|
|
#include "datagramUDPHeader.h"
|
|
#include "config_net.h"
|
|
#include "config_express.h" // for collect_tcp
|
|
#include "trueClock.h"
|
|
#include "pnotify.h"
|
|
#include "lightReMutexHolder.h"
|
|
#include "socket_ip.h"
|
|
#include "socket_tcp.h"
|
|
#include "socket_udp.h"
|
|
#include "dcast.h"
|
|
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::Constructor
|
|
// Access: Published
|
|
// Description: Creates a connection. Normally this constructor
|
|
// should not be used directly by user code; use one of
|
|
// the methods in ConnectionManager to make a new
|
|
// connection.
|
|
////////////////////////////////////////////////////////////////////
|
|
Connection::
|
|
Connection(ConnectionManager *manager, Socket_IP *socket) :
|
|
_manager(manager),
|
|
_socket(socket)
|
|
{
|
|
_collect_tcp = collect_tcp;
|
|
_collect_tcp_interval = collect_tcp_interval;
|
|
_queued_data_start = 0.0;
|
|
_queued_count = 0;
|
|
|
|
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
|
|
// In the presence of SIMPLE_THREADS, we use non-blocking I/O. We
|
|
// simulate blocking by yielding the thread.
|
|
if (_socket->SetNonBlocking() != ALL_OK) {
|
|
net_cat.warning()
|
|
<< "Unable to set non-blocking status on socket\n";
|
|
}
|
|
#endif
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::Destructor
|
|
// Access: Published
|
|
// Description: Closes a connection.
|
|
////////////////////////////////////////////////////////////////////
|
|
Connection::
|
|
~Connection() {
|
|
net_cat.info()
|
|
<< "Deleting connection " << (void *)this << "\n";
|
|
|
|
if (_socket != (Socket_IP *)NULL) {
|
|
flush();
|
|
|
|
_socket->Close();
|
|
delete _socket;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::get_address
|
|
// Access: Published
|
|
// Description: Returns the address bound to this connection, if it
|
|
// is a TCP connection.
|
|
////////////////////////////////////////////////////////////////////
|
|
NetAddress Connection::
|
|
get_address() const {
|
|
Socket_Address addr = _socket->GetPeerName();
|
|
return NetAddress(addr);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::get_manager
|
|
// Access: Published
|
|
// Description: Returns a pointer to the ConnectionManager object
|
|
// that serves this connection.
|
|
////////////////////////////////////////////////////////////////////
|
|
ConnectionManager *Connection::
|
|
get_manager() const {
|
|
return _manager;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::get_socket
|
|
// Access: Published
|
|
// Description: Returns the internal Socket_IP that defines the
|
|
// connection.
|
|
////////////////////////////////////////////////////////////////////
|
|
Socket_IP *Connection::
|
|
get_socket() const {
|
|
return _socket;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_collect_tcp
|
|
// Access: Published
|
|
// Description: Enables or disables "collect-tcp" mode. In this
|
|
// mode, individual TCP packets are not sent
|
|
// immediately, but rather they are collected together
|
|
// and accumulated to be sent periodically as one larger
|
|
// TCP packet. This cuts down on overhead from the
|
|
// TCP/IP protocol, especially if many small packets
|
|
// need to be sent on the same connection, but it
|
|
// introduces additional latency (since packets must be
|
|
// held before they can be sent).
|
|
//
|
|
// See set_collect_tcp_interval() to specify the
|
|
// interval of time for which to hold packets before
|
|
// sending them.
|
|
//
|
|
// If you enable this mode, you may also need to
|
|
// periodically call consider_flush() to flush the queue
|
|
// if no packets have been sent recently.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_collect_tcp(bool collect_tcp) {
|
|
_collect_tcp = collect_tcp;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::get_collect_tcp
|
|
// Access: Published
|
|
// Description: Returns the current setting of "collect-tcp" mode.
|
|
// See set_collect_tcp().
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
get_collect_tcp() const {
|
|
return _collect_tcp;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_collect_tcp_interval
|
|
// Access: Published
|
|
// Description: Specifies the interval in time, in seconds, for which
|
|
// to hold TCP packets before sending all of the
|
|
// recently received packets at once. This only has
|
|
// meaning if "collect-tcp" mode is enabled; see
|
|
// set_collect_tcp().
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_collect_tcp_interval(double interval) {
|
|
_collect_tcp_interval = interval;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::get_collect_tcp_interval
|
|
// Access: Published
|
|
// Description: Returns the interval in time, in seconds, for which
|
|
// to hold TCP packets before sending all of the
|
|
// recently received packets at once. This only has
|
|
// meaning if "collect-tcp" mode is enabled; see
|
|
// set_collect_tcp().
|
|
////////////////////////////////////////////////////////////////////
|
|
double Connection::
|
|
get_collect_tcp_interval() const {
|
|
return _collect_tcp_interval;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::consider_flush
|
|
// Access: Published
|
|
// Description: Sends the most recently queued TCP datagram(s) if
|
|
// enough time has elapsed. This only has meaning if
|
|
// set_collect_tcp() has been set to true.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
consider_flush() {
|
|
LightReMutexHolder holder(_write_mutex);
|
|
|
|
if (!_collect_tcp) {
|
|
return do_flush();
|
|
|
|
} else {
|
|
double elapsed =
|
|
TrueClock::get_global_ptr()->get_short_time() - _queued_data_start;
|
|
// If the elapsed time is negative, someone must have reset the
|
|
// clock back, so just go ahead and flush.
|
|
if (elapsed < 0.0 || elapsed >= _collect_tcp_interval) {
|
|
return do_flush();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::flush
|
|
// Access: Published
|
|
// Description: Sends the most recently queued TCP datagram(s) now.
|
|
// This only has meaning if set_collect_tcp() has been
|
|
// set to true.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
flush() {
|
|
LightReMutexHolder holder(_write_mutex);
|
|
return do_flush();
|
|
}
|
|
|
|
/*
|
|
This method is disabled. We don't provide enough interface to use
|
|
non-blocking I/O effectively at this level, so we shouldn't provide
|
|
this call. Specifically, we don't provide a way to query whether an
|
|
operation failed because it would have blocked or not.
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_nonblock
|
|
// Access: Published
|
|
// Description: Sets whether nonblocking I/O should be in effect.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_nonblock(bool flag) {
|
|
if (flag) {
|
|
_socket->SetNonBlocking();
|
|
} else {
|
|
_socket->SetBlocking();
|
|
}
|
|
}
|
|
*/
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_linger
|
|
// Access: Published
|
|
// Description: Sets the time to linger on close if data is present.
|
|
// If flag is false, when you close a socket with data
|
|
// available the system attempts to deliver the data to
|
|
// the peer (the default behavior). If flag is false
|
|
// but time is zero, the system discards any undelivered
|
|
// data when you close the socket. If flag is false but
|
|
// time is nonzero, the system waits up to time seconds
|
|
// to deliver the data.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_linger(bool flag, double time) {
|
|
Socket_TCP *tcp;
|
|
DCAST_INTO_V(tcp, _socket);
|
|
|
|
if (flag) {
|
|
tcp->SetLinger((int)time);
|
|
} else {
|
|
tcp->DontLinger();
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_reuse_addr
|
|
// Access: Published
|
|
// Description: Sets whether local address reuse is allowed.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_reuse_addr(bool flag) {
|
|
_socket->SetReuseAddress(flag);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_keep_alive
|
|
// Access: Published
|
|
// Description: Sets whether the connection is periodically tested to
|
|
// see if it is still alive.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_keep_alive(bool flag) {
|
|
// TODO.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_recv_buffer_size
|
|
// Access: Published
|
|
// Description: Sets the size of the receive buffer, in bytes.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_recv_buffer_size(int size) {
|
|
_socket->SetRecvBufferSize(size);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_send_buffer_size
|
|
// Access: Published
|
|
// Description: Sets the size of the send buffer, in bytes.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_send_buffer_size(int size) {
|
|
Socket_TCP *tcp;
|
|
DCAST_INTO_V(tcp, _socket);
|
|
|
|
tcp->SetSendBufferSize(size);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_ip_time_to_live
|
|
// Access: Published
|
|
// Description: Sets IP time-to-live.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_ip_time_to_live(int ttl) {
|
|
// TODO.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_ip_type_of_service
|
|
// Access: Published
|
|
// Description: Sets IP type-of-service and precedence.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_ip_type_of_service(int tos) {
|
|
// TODO.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_no_delay
|
|
// Access: Published
|
|
// Description: If flag is true, this disables the Nagle algorithm,
|
|
// and prevents delaying of send to coalesce packets.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_no_delay(bool flag) {
|
|
Socket_TCP *tcp;
|
|
DCAST_INTO_V(tcp, _socket);
|
|
|
|
tcp->SetNoDelay(flag);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::set_max_segment
|
|
// Access: Published
|
|
// Description: Sets the maximum segment size.
|
|
////////////////////////////////////////////////////////////////////
|
|
void Connection::
|
|
set_max_segment(int size) {
|
|
// TODO.
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::send_datagram
|
|
// Access: Private
|
|
// Description: This method is intended only to be called by
|
|
// ConnectionWriter. It atomically writes the given
|
|
// datagram to the socket, returning true on success,
|
|
// false on failure. If the socket seems to be closed,
|
|
// it notifies the ConnectionManager.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
send_datagram(const NetDatagram &datagram, int tcp_header_size) {
|
|
nassertr(_socket != (Socket_IP *)NULL, false);
|
|
|
|
if (_socket->is_exact_type(Socket_UDP::get_class_type())) {
|
|
// We have to send UDP right away.
|
|
Socket_UDP *udp;
|
|
DCAST_INTO_R(udp, _socket, false);
|
|
|
|
LightReMutexHolder holder(_write_mutex);
|
|
DatagramUDPHeader header(datagram);
|
|
string data;
|
|
data += header.get_header();
|
|
data += datagram.get_message();
|
|
|
|
if (net_cat.is_debug()) {
|
|
header.verify_datagram(datagram);
|
|
}
|
|
|
|
int bytes_to_send = data.length();
|
|
Socket_Address addr = datagram.get_address().get_addr();
|
|
|
|
bool okflag = udp->SendTo(data, addr);
|
|
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
|
|
while (!okflag && udp->GetLastError() == LOCAL_BLOCKING_ERROR) {
|
|
Thread::force_yield();
|
|
okflag = udp->SendTo(data, addr);
|
|
}
|
|
#endif // SIMPLE_THREADS
|
|
|
|
if (net_cat.is_spam()) {
|
|
net_cat.spam()
|
|
<< "Sent UDP datagram with "
|
|
<< bytes_to_send << " bytes to " << (void *)this
|
|
<< ", ok = " << okflag << "\n";
|
|
}
|
|
|
|
return check_send_error(okflag);
|
|
}
|
|
|
|
// We might queue up TCP packets for later sending.
|
|
if (tcp_header_size == 2 && datagram.get_length() >= 0x10000) {
|
|
net_cat.error()
|
|
<< "Attempt to send TCP datagram of " << datagram.get_length()
|
|
<< " bytes--too long!\n";
|
|
nassert_raise("Datagram too long");
|
|
return false;
|
|
}
|
|
|
|
DatagramTCPHeader header(datagram, tcp_header_size);
|
|
|
|
LightReMutexHolder holder(_write_mutex);
|
|
_queued_data += header.get_header();
|
|
_queued_data += datagram.get_message();
|
|
_queued_count++;
|
|
|
|
if (net_cat.is_debug()) {
|
|
header.verify_datagram(datagram, tcp_header_size);
|
|
}
|
|
|
|
if (!_collect_tcp ||
|
|
TrueClock::get_global_ptr()->get_short_time() - _queued_data_start >= _collect_tcp_interval) {
|
|
return do_flush();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::send_raw_datagram
|
|
// Access: Private
|
|
// Description: This method is intended only to be called by
|
|
// ConnectionWriter. It atomically writes the given
|
|
// datagram to the socket, without the Datagram header.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
send_raw_datagram(const NetDatagram &datagram) {
|
|
nassertr(_socket != (Socket_IP *)NULL, false);
|
|
|
|
if (_socket->is_exact_type(Socket_UDP::get_class_type())) {
|
|
// We have to send UDP right away.
|
|
Socket_UDP *udp;
|
|
DCAST_INTO_R(udp, _socket, false);
|
|
|
|
string data = datagram.get_message();
|
|
|
|
LightReMutexHolder holder(_write_mutex);
|
|
Socket_Address addr = datagram.get_address().get_addr();
|
|
bool okflag = udp->SendTo(data, addr);
|
|
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
|
|
while (!okflag && udp->GetLastError() == LOCAL_BLOCKING_ERROR) {
|
|
Thread::force_yield();
|
|
okflag = udp->SendTo(data, addr);
|
|
}
|
|
#endif // SIMPLE_THREADS
|
|
|
|
if (net_cat.is_spam()) {
|
|
net_cat.spam()
|
|
<< "Sent UDP datagram with "
|
|
<< data.size() << " bytes to " << (void *)this
|
|
<< ", ok = " << okflag << "\n";
|
|
}
|
|
|
|
return check_send_error(okflag);
|
|
}
|
|
|
|
// We might queue up TCP packets for later sending.
|
|
LightReMutexHolder holder(_write_mutex);
|
|
_queued_data += datagram.get_message();
|
|
_queued_count++;
|
|
|
|
if (!_collect_tcp ||
|
|
TrueClock::get_global_ptr()->get_short_time() - _queued_data_start >= _collect_tcp_interval) {
|
|
return do_flush();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::do_flush
|
|
// Access: Private
|
|
// Description: The private implementation of flush(), this assumes
|
|
// the _write_mutex is already held.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
do_flush() {
|
|
if (_queued_data.empty()) {
|
|
_queued_count = 0;
|
|
_queued_data_start = TrueClock::get_global_ptr()->get_short_time();
|
|
return true;
|
|
}
|
|
|
|
if (net_cat.is_spam()) {
|
|
net_cat.spam()
|
|
<< "Sending " << _queued_count << " TCP datagram(s) with "
|
|
<< _queued_data.length() << " total bytes to " << (void *)this << "\n";
|
|
}
|
|
|
|
Socket_TCP *tcp;
|
|
DCAST_INTO_R(tcp, _socket, false);
|
|
|
|
string sending_data;
|
|
_queued_data.swap(sending_data);
|
|
|
|
_queued_count = 0;
|
|
_queued_data_start = TrueClock::get_global_ptr()->get_short_time();
|
|
|
|
int data_sent = tcp->SendData(sending_data);
|
|
bool okflag = (data_sent == (int)sending_data.size());
|
|
#if defined(HAVE_THREADS) && defined(SIMPLE_THREADS)
|
|
if (!okflag) {
|
|
int total_sent = 0;
|
|
if (data_sent > 0) {
|
|
total_sent += data_sent;
|
|
}
|
|
while (!okflag && (data_sent > 0 || tcp->GetLastError() == LOCAL_BLOCKING_ERROR)) {
|
|
Thread::force_yield();
|
|
data_sent = tcp->SendData(sending_data.data() + total_sent, sending_data.size() - total_sent);
|
|
if (data_sent > 0) {
|
|
total_sent += data_sent;
|
|
}
|
|
okflag = (total_sent == (int)sending_data.size());
|
|
}
|
|
}
|
|
#endif // SIMPLE_THREADS
|
|
|
|
return check_send_error(okflag);
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////
|
|
// Function: Connection::check_send_error
|
|
// Access: Private
|
|
// Description: Checks the return value of a Send() or SendTo()
|
|
// call.
|
|
////////////////////////////////////////////////////////////////////
|
|
bool Connection::
|
|
check_send_error(bool okflag) {
|
|
if (!okflag) {
|
|
static ConfigVariableBool abort_send_error("abort-send-error", false);
|
|
if (abort_send_error) {
|
|
nassertr(false, false);
|
|
}
|
|
|
|
// Assume any error means the connection has been reset; tell
|
|
// our manager about it and ignore it.
|
|
if (_manager != (ConnectionManager *)NULL) {
|
|
_manager->flush_read_connection(this);
|
|
_manager->connection_reset(this, okflag);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|