Index: net/socket/tcp_client_socket.cc |
diff --git a/net/socket/tcp_client_socket.cc b/net/socket/tcp_client_socket.cc |
index a91d33d24c8cb453f9f314e2b980a37d981d7a62..af1c555caa0bd3ac175cb5d816f114b21ea5bae5 100644 |
--- a/net/socket/tcp_client_socket.cc |
+++ b/net/socket/tcp_client_socket.cc |
@@ -4,8 +4,14 @@ |
#include "net/socket/tcp_client_socket.h" |
+#include "base/callback_helpers.h" |
#include "base/file_util.h" |
#include "base/files/file_path.h" |
+#include "base/logging.h" |
+#include "net/base/io_buffer.h" |
+#include "net/base/ip_endpoint.h" |
+#include "net/base/net_errors.h" |
+#include "net/base/net_util.h" |
namespace net { |
@@ -56,4 +62,312 @@ bool IsTCPFastOpenEnabled() { |
return g_tcp_fastopen_enabled; |
} |
+#if defined(OS_WIN) |
+ |
+TCPClientSocket::TCPClientSocket(const AddressList& addresses, |
+ net::NetLog* net_log, |
+ const net::NetLog::Source& source) |
+ : socket_(new TCPSocket(net_log, source)), |
+ addresses_(addresses), |
+ current_address_index_(-1), |
+ next_connect_state_(CONNECT_STATE_NONE), |
+ previously_disconnected_(false) { |
+} |
+ |
+TCPClientSocket::TCPClientSocket(scoped_ptr<TCPSocket> connected_socket, |
+ const IPEndPoint& peer_address) |
+ : socket_(connected_socket.Pass()), |
+ addresses_(AddressList(peer_address)), |
+ current_address_index_(0), |
+ next_connect_state_(CONNECT_STATE_NONE), |
+ previously_disconnected_(false) { |
+ DCHECK(socket_); |
+ |
+ socket_->SetDefaultOptionsForClient(); |
+ use_history_.set_was_ever_connected(); |
+} |
+ |
+TCPClientSocket::~TCPClientSocket() { |
+} |
+ |
+int TCPClientSocket::Bind(const IPEndPoint& address) { |
+ if (current_address_index_ >= 0 || bind_address_) { |
+ // Cannot bind the socket if we are already connected or connecting. |
+ NOTREACHED(); |
+ return ERR_UNEXPECTED; |
+ } |
+ |
+ int result = OK; |
+ if (!socket_->IsValid()) { |
+ result = OpenSocket(address.GetFamily()); |
+ if (result != OK) |
+ return result; |
+ } |
+ |
+ result = socket_->Bind(address); |
+ if (result != OK) |
+ return result; |
+ |
+ bind_address_.reset(new IPEndPoint(address)); |
+ return OK; |
+} |
+ |
+int TCPClientSocket::Connect(const CompletionCallback& callback) { |
+ DCHECK(!callback.is_null()); |
+ |
+ // If connecting or already connected, then just return OK. |
+ if (socket_->IsValid() && current_address_index_ >= 0) |
+ return OK; |
+ |
+ socket_->StartLoggingMultipleConnectAttempts(addresses_); |
+ |
+ // We will try to connect to each address in addresses_. Start with the |
+ // first one in the list. |
+ next_connect_state_ = CONNECT_STATE_CONNECT; |
+ current_address_index_ = 0; |
+ |
+ int rv = DoConnectLoop(OK); |
+ if (rv == ERR_IO_PENDING) { |
+ connect_callback_ = callback; |
+ } else { |
+ socket_->EndLoggingMultipleConnectAttempts(rv); |
+ } |
+ |
+ return rv; |
+} |
+ |
+int TCPClientSocket::DoConnectLoop(int result) { |
+ DCHECK_NE(next_connect_state_, CONNECT_STATE_NONE); |
+ |
+ int rv = result; |
+ do { |
+ ConnectState state = next_connect_state_; |
+ next_connect_state_ = CONNECT_STATE_NONE; |
+ switch (state) { |
+ case CONNECT_STATE_CONNECT: |
+ DCHECK_EQ(OK, rv); |
+ rv = DoConnect(); |
+ break; |
+ case CONNECT_STATE_CONNECT_COMPLETE: |
+ rv = DoConnectComplete(rv); |
+ break; |
+ default: |
+ NOTREACHED() << "bad state " << state; |
+ rv = ERR_UNEXPECTED; |
+ break; |
+ } |
+ } while (rv != ERR_IO_PENDING && next_connect_state_ != CONNECT_STATE_NONE); |
+ |
+ return rv; |
+} |
+ |
+int TCPClientSocket::DoConnect() { |
+ DCHECK_GE(current_address_index_, 0); |
+ DCHECK_LT(current_address_index_, static_cast<int>(addresses_.size())); |
+ |
+ const IPEndPoint& endpoint = addresses_[current_address_index_]; |
+ |
+ if (previously_disconnected_) { |
+ use_history_.Reset(); |
+ previously_disconnected_ = false; |
+ } |
+ |
+ next_connect_state_ = CONNECT_STATE_CONNECT_COMPLETE; |
+ |
+ if (socket_->IsValid()) { |
+ DCHECK(bind_address_); |
+ } else { |
+ int result = OpenSocket(endpoint.GetFamily()); |
+ if (result != OK) |
+ return result; |
+ |
+ if (bind_address_) { |
+ result = socket_->Bind(*bind_address_); |
+ if (result != OK) { |
+ socket_->Close(); |
+ return result; |
+ } |
+ } |
+ } |
+ |
+ // |socket_| is owned by this class and the callback won't be run once |
+ // |socket_| is gone. Therefore, it is safe to use base::Unretained() here. |
+ return socket_->Connect(endpoint, |
+ base::Bind(&TCPClientSocket::DidCompleteConnect, |
+ base::Unretained(this))); |
+} |
+ |
+int TCPClientSocket::DoConnectComplete(int result) { |
+ if (result == OK) { |
+ use_history_.set_was_ever_connected(); |
+ return OK; // Done! |
+ } |
+ |
+ // Close whatever partially connected socket we currently have. |
+ DoDisconnect(); |
+ |
+ // Try to fall back to the next address in the list. |
+ if (current_address_index_ + 1 < static_cast<int>(addresses_.size())) { |
+ next_connect_state_ = CONNECT_STATE_CONNECT; |
+ ++current_address_index_; |
+ return OK; |
+ } |
+ |
+ // Otherwise there is nothing to fall back to, so give up. |
+ return result; |
+} |
+ |
+void TCPClientSocket::Disconnect() { |
+ DoDisconnect(); |
+ current_address_index_ = -1; |
+ bind_address_.reset(); |
+} |
+ |
+void TCPClientSocket::DoDisconnect() { |
+ // If connecting or already connected, record that the socket has been |
+ // disconnected. |
+ previously_disconnected_ = socket_->IsValid() && current_address_index_ >= 0; |
+ socket_->Close(); |
+} |
+ |
+bool TCPClientSocket::IsConnected() const { |
+ return socket_->IsConnected(); |
+} |
+ |
+bool TCPClientSocket::IsConnectedAndIdle() const { |
+ return socket_->IsConnectedAndIdle(); |
+} |
+ |
+int TCPClientSocket::GetPeerAddress(IPEndPoint* address) const { |
+ return socket_->GetPeerAddress(address); |
+} |
+ |
+int TCPClientSocket::GetLocalAddress(IPEndPoint* address) const { |
+ DCHECK(address); |
+ |
+ if (!socket_->IsValid()) { |
+ if (bind_address_) { |
+ *address = *bind_address_; |
+ return OK; |
+ } |
+ return ERR_SOCKET_NOT_CONNECTED; |
+ } |
+ |
+ return socket_->GetLocalAddress(address); |
+} |
+ |
+const BoundNetLog& TCPClientSocket::NetLog() const { |
+ return socket_->net_log(); |
+} |
+ |
+void TCPClientSocket::SetSubresourceSpeculation() { |
+ use_history_.set_subresource_speculation(); |
+} |
+ |
+void TCPClientSocket::SetOmniboxSpeculation() { |
+ use_history_.set_omnibox_speculation(); |
+} |
+ |
+bool TCPClientSocket::WasEverUsed() const { |
+ return use_history_.was_used_to_convey_data(); |
+} |
+ |
+bool TCPClientSocket::UsingTCPFastOpen() const { |
+ return socket_->UsingTCPFastOpen(); |
+} |
+ |
+bool TCPClientSocket::WasNpnNegotiated() const { |
+ return false; |
+} |
+ |
+NextProto TCPClientSocket::GetNegotiatedProtocol() const { |
+ return kProtoUnknown; |
+} |
+ |
+bool TCPClientSocket::GetSSLInfo(SSLInfo* ssl_info) { |
+ return false; |
+} |
+ |
+int TCPClientSocket::Read(IOBuffer* buf, |
+ int buf_len, |
+ const CompletionCallback& callback) { |
+ DCHECK(!callback.is_null()); |
+ |
+ // |socket_| is owned by this class and the callback won't be run once |
+ // |socket_| is gone. Therefore, it is safe to use base::Unretained() here. |
+ CompletionCallback read_callback = base::Bind( |
+ &TCPClientSocket::DidCompleteReadWrite, base::Unretained(this), callback); |
+ int result = socket_->Read(buf, buf_len, read_callback); |
+ if (result > 0) |
+ use_history_.set_was_used_to_convey_data(); |
+ |
+ return result; |
+} |
+ |
+int TCPClientSocket::Write(IOBuffer* buf, |
+ int buf_len, |
+ const CompletionCallback& callback) { |
+ DCHECK(!callback.is_null()); |
+ |
+ // |socket_| is owned by this class and the callback won't be run once |
+ // |socket_| is gone. Therefore, it is safe to use base::Unretained() here. |
+ CompletionCallback write_callback = base::Bind( |
+ &TCPClientSocket::DidCompleteReadWrite, base::Unretained(this), callback); |
+ int result = socket_->Write(buf, buf_len, write_callback); |
+ if (result > 0) |
+ use_history_.set_was_used_to_convey_data(); |
+ |
+ return result; |
+} |
+ |
+bool TCPClientSocket::SetReceiveBufferSize(int32 size) { |
+ return socket_->SetReceiveBufferSize(size); |
+} |
+ |
+bool TCPClientSocket::SetSendBufferSize(int32 size) { |
+ return socket_->SetSendBufferSize(size); |
+} |
+ |
+bool TCPClientSocket::SetKeepAlive(bool enable, int delay) { |
+ return socket_->SetKeepAlive(enable, delay); |
+} |
+ |
+bool TCPClientSocket::SetNoDelay(bool no_delay) { |
+ return socket_->SetNoDelay(no_delay); |
+} |
+ |
+void TCPClientSocket::DidCompleteConnect(int result) { |
+ DCHECK_EQ(next_connect_state_, CONNECT_STATE_CONNECT_COMPLETE); |
+ DCHECK_NE(result, ERR_IO_PENDING); |
+ DCHECK(!connect_callback_.is_null()); |
+ |
+ result = DoConnectLoop(result); |
+ if (result != ERR_IO_PENDING) { |
+ socket_->EndLoggingMultipleConnectAttempts(result); |
+ base::ResetAndReturn(&connect_callback_).Run(result); |
+ } |
+} |
+ |
+void TCPClientSocket::DidCompleteReadWrite(const CompletionCallback& callback, |
+ int result) { |
+ if (result > 0) |
+ use_history_.set_was_used_to_convey_data(); |
+ |
+ callback.Run(result); |
+} |
+ |
+int TCPClientSocket::OpenSocket(AddressFamily family) { |
+ DCHECK(!socket_->IsValid()); |
+ |
+ int result = socket_->Open(family); |
+ if (result != OK) |
+ return result; |
+ |
+ socket_->SetDefaultOptionsForClient(); |
+ |
+ return OK; |
+} |
+ |
+#endif |
+ |
} // namespace net |