diff options
author | dkegel@google.com <dkegel@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-10 22:53:29 +0000 |
---|---|---|
committer | dkegel@google.com <dkegel@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-10 22:53:29 +0000 |
commit | 06a11cab7c6c726ac97606dc70d6012e4455ecfd (patch) | |
tree | 5b7d32e238fc57f8e07f717d1e96c23100fae532 /net/base/tcp_client_socket_win.cc | |
parent | 07d212e60e60aa937f81cf0be686a4fb7997bfd4 (diff) | |
download | chromium_src-06a11cab7c6c726ac97606dc70d6012e4455ecfd.zip chromium_src-06a11cab7c6c726ac97606dc70d6012e4455ecfd.tar.gz chromium_src-06a11cab7c6c726ac97606dc70d6012e4455ecfd.tar.bz2 |
Rename tcp_client_socket.cc to _win.cc (should have been done long ago).
Move some truly win32-specific from the 'portable' to the 'platform-specific' category.
Enable some tests that already work.
Fix a couple signedness mismatch errors that showed up while testing the above.
Review URL: http://codereview.chromium.org/9465
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@5134 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'net/base/tcp_client_socket_win.cc')
-rw-r--r-- | net/base/tcp_client_socket_win.cc | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/net/base/tcp_client_socket_win.cc b/net/base/tcp_client_socket_win.cc new file mode 100644 index 0000000..bb25e34 --- /dev/null +++ b/net/base/tcp_client_socket_win.cc @@ -0,0 +1,351 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/base/tcp_client_socket.h" + +#include "base/string_util.h" +#include "base/trace_event.h" +#include "net/base/net_errors.h" +#include "net/base/winsock_init.h" + +namespace net { + +//----------------------------------------------------------------------------- + +static int MapWinsockError(DWORD err) { + // There are numerous Winsock error codes, but these are the ones we thus far + // find interesting. + switch (err) { + case WSAENETDOWN: + return ERR_INTERNET_DISCONNECTED; + case WSAETIMEDOUT: + return ERR_TIMED_OUT; + case WSAECONNRESET: + case WSAENETRESET: // Related to keep-alive + return ERR_CONNECTION_RESET; + case WSAECONNABORTED: + return ERR_CONNECTION_ABORTED; + case WSAECONNREFUSED: + return ERR_CONNECTION_REFUSED; + case WSAEDISCON: + // Returned by WSARecv or WSARecvFrom for message-oriented sockets (where + // a return value of zero means a zero-byte message) to indicate graceful + // connection shutdown. We should not ever see this error code for TCP + // sockets, which are byte stream oriented. + NOTREACHED(); + return ERR_CONNECTION_CLOSED; + case WSAEHOSTUNREACH: + case WSAENETUNREACH: + return ERR_ADDRESS_UNREACHABLE; + case WSAEADDRNOTAVAIL: + return ERR_ADDRESS_INVALID; + case WSA_IO_INCOMPLETE: + return ERR_UNEXPECTED; + case ERROR_SUCCESS: + return OK; + default: + LOG(WARNING) << "Unknown error " << err << " mapped to net::ERR_FAILED"; + return ERR_FAILED; + } +} + +//----------------------------------------------------------------------------- + +TCPClientSocket::TCPClientSocket(const AddressList& addresses) + : socket_(INVALID_SOCKET), + addresses_(addresses), + current_ai_(addresses_.head()), + wait_state_(NOT_WAITING), + callback_(NULL) { + memset(&overlapped_, 0, sizeof(overlapped_)); + EnsureWinsockInit(); +} + +TCPClientSocket::~TCPClientSocket() { + Disconnect(); +} + +int TCPClientSocket::Connect(CompletionCallback* callback) { + // If already connected, then just return OK. + if (socket_ != INVALID_SOCKET) + return OK; + + TRACE_EVENT_BEGIN("socket.connect", this, ""); + const struct addrinfo* ai = current_ai_; + DCHECK(ai); + + int rv = CreateSocket(ai); + if (rv != OK) + return rv; + + overlapped_.hEvent = WSACreateEvent(); + // WSAEventSelect sets the socket to non-blocking mode as a side effect. + // Our connect() and recv() calls require that the socket be non-blocking. + WSAEventSelect(socket_, overlapped_.hEvent, FD_CONNECT); + + if (!connect(socket_, ai->ai_addr, static_cast<int>(ai->ai_addrlen))) { + TRACE_EVENT_END("socket.connect", this, ""); + // Connected without waiting! + return OK; + } + + DWORD err = WSAGetLastError(); + if (err != WSAEWOULDBLOCK) { + LOG(ERROR) << "connect failed: " << err; + return MapWinsockError(err); + } + + watcher_.StartWatching(overlapped_.hEvent, this); + wait_state_ = WAITING_CONNECT; + callback_ = callback; + return ERR_IO_PENDING; +} + +int TCPClientSocket::ReconnectIgnoringLastError(CompletionCallback* callback) { + // No ignorable errors! + return ERR_UNEXPECTED; +} + +void TCPClientSocket::Disconnect() { + if (socket_ == INVALID_SOCKET) + return; + + TRACE_EVENT_INSTANT("socket.disconnect", this, ""); + + // Make sure the message loop is not watching this object anymore. + watcher_.StopWatching(); + + // In most socket implementations, closing a socket results in a graceful + // connection shutdown, but in Winsock we have to call shutdown explicitly. + // See the MSDN page "Graceful Shutdown, Linger Options, and Socket Closure" + // at http://msdn.microsoft.com/en-us/library/ms738547.aspx + shutdown(socket_, SD_SEND); + + // This cancels any pending IO. + closesocket(socket_); + socket_ = INVALID_SOCKET; + + WSACloseEvent(overlapped_.hEvent); + memset(&overlapped_, 0, sizeof(overlapped_)); + + // Reset for next time. + current_ai_ = addresses_.head(); +} + +bool TCPClientSocket::IsConnected() const { + if (socket_ == INVALID_SOCKET || wait_state_ == WAITING_CONNECT) + return false; + + // Check if connection is alive. + char c; + int rv = recv(socket_, &c, 1, MSG_PEEK); + if (rv == 0) + return false; + if (rv == SOCKET_ERROR && WSAGetLastError() != WSAEWOULDBLOCK) + return false; + + return true; +} + +int TCPClientSocket::Read(char* buf, + int buf_len, + CompletionCallback* callback) { + DCHECK(socket_ != INVALID_SOCKET); + DCHECK(wait_state_ == NOT_WAITING); + DCHECK(!callback_); + + buffer_.len = buf_len; + buffer_.buf = buf; + + TRACE_EVENT_BEGIN("socket.read", this, ""); + // TODO(wtc): Remove the CHECKs after enough testing. + CHECK(WaitForSingleObject(overlapped_.hEvent, 0) == WAIT_TIMEOUT); + DWORD num, flags = 0; + int rv = WSARecv(socket_, &buffer_, 1, &num, &flags, &overlapped_, NULL); + if (rv == 0) { + CHECK(WaitForSingleObject(overlapped_.hEvent, 0) == WAIT_OBJECT_0); + BOOL ok = WSAResetEvent(overlapped_.hEvent); + CHECK(ok); + TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", num)); + return static_cast<int>(num); + } + int err = WSAGetLastError(); + if (err == WSA_IO_PENDING) { + watcher_.StartWatching(overlapped_.hEvent, this); + wait_state_ = WAITING_READ; + callback_ = callback; + return ERR_IO_PENDING; + } + return MapWinsockError(err); +} + +int TCPClientSocket::Write(const char* buf, + int buf_len, + CompletionCallback* callback) { + DCHECK(socket_ != INVALID_SOCKET); + DCHECK(wait_state_ == NOT_WAITING); + DCHECK(!callback_); + + buffer_.len = buf_len; + buffer_.buf = const_cast<char*>(buf); + + TRACE_EVENT_BEGIN("socket.write", this, ""); + // TODO(wtc): Remove the CHECKs after enough testing. + CHECK(WaitForSingleObject(overlapped_.hEvent, 0) == WAIT_TIMEOUT); + DWORD num; + int rv = WSASend(socket_, &buffer_, 1, &num, 0, &overlapped_, NULL); + if (rv == 0) { + CHECK(WaitForSingleObject(overlapped_.hEvent, 0) == WAIT_OBJECT_0); + BOOL ok = WSAResetEvent(overlapped_.hEvent); + CHECK(ok); + TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", num)); + return static_cast<int>(num); + } + int err = WSAGetLastError(); + if (err == WSA_IO_PENDING) { + watcher_.StartWatching(overlapped_.hEvent, this); + wait_state_ = WAITING_WRITE; + callback_ = callback; + return ERR_IO_PENDING; + } + return MapWinsockError(err); +} + +int TCPClientSocket::CreateSocket(const struct addrinfo* ai) { + socket_ = WSASocket(ai->ai_family, ai->ai_socktype, ai->ai_protocol, NULL, 0, + WSA_FLAG_OVERLAPPED); + if (socket_ == INVALID_SOCKET) { + DWORD err = WSAGetLastError(); + LOG(ERROR) << "WSASocket failed: " << err; + return MapWinsockError(err); + } + + // Increase the socket buffer sizes from the default sizes. + // In performance testing, there is substantial benefit by increasing + // from 8KB to 32KB. I tested 64, 128, and 256KB as well, but did not + // see additional performance benefit (will be network dependent). + // See also: + // http://support.microsoft.com/kb/823764/EN-US + // On XP, the default buffer sizes are 8KB. + const int kSocketBufferSize = 32 * 1024; + int rv = setsockopt(socket_, SOL_SOCKET, SO_SNDBUF, + reinterpret_cast<const char*>(&kSocketBufferSize), + sizeof(kSocketBufferSize)); + DCHECK(!rv) << "Could not set socket send buffer size"; + rv = setsockopt(socket_, SOL_SOCKET, SO_RCVBUF, + reinterpret_cast<const char*>(&kSocketBufferSize), + sizeof(kSocketBufferSize)); + DCHECK(!rv) << "Could not set socket receive buffer size"; + + // Disable Nagle. + // The Nagle implementation on windows is governed by RFC 896. The idea + // behind Nagle is to reduce small packets on the network. When Nagle is + // enabled, if a partial packet has been sent, the TCP stack will disallow + // further *partial* packets until an ACK has been received from the other + // side. Good applications should always strive to send as much data as + // possible and avoid partial-packet sends. However, in most real world + // applications, there are edge cases where this does not happen, and two + // partil packets may be sent back to back. For a browser, it is NEVER + // a benefit to delay for an RTT before the second packet is sent. + // + // As a practical example in Chromium today, consider the case of a small + // POST. I have verified this: + // Client writes 649 bytes of header (partial packet #1) + // Client writes 50 bytes of POST data (partial packet #2) + // In the above example, with Nagle, a RTT delay is inserted between these + // two sends due to nagle. RTTs can easily be 100ms or more. The best + // fix is to make sure that for POSTing data, we write as much data as + // possible and minimize partial packets. We will fix that. But disabling + // Nagle also ensure we don't run into this delay in other edge cases. + // See also: + // http://technet.microsoft.com/en-us/library/bb726981.aspx + const BOOL kDisableNagle = TRUE; + rv = setsockopt(socket_, IPPROTO_TCP, TCP_NODELAY, + reinterpret_cast<const char*>(&kDisableNagle), sizeof(kDisableNagle)); + DCHECK(!rv) << "Could not disable nagle"; + + return OK; +} + +void TCPClientSocket::DoCallback(int rv) { + DCHECK(rv != ERR_IO_PENDING); + DCHECK(callback_); + + // since Run may result in Read being called, clear callback_ up front. + CompletionCallback* c = callback_; + callback_ = NULL; + c->Run(rv); +} + +void TCPClientSocket::DidCompleteConnect() { + int result; + + TRACE_EVENT_END("socket.connect", this, ""); + wait_state_ = NOT_WAITING; + + WSANETWORKEVENTS events; + int rv = WSAEnumNetworkEvents(socket_, overlapped_.hEvent, &events); + if (rv == SOCKET_ERROR) { + NOTREACHED(); + result = MapWinsockError(WSAGetLastError()); + } else if (events.lNetworkEvents & FD_CONNECT) { + wait_state_ = NOT_WAITING; + DWORD error_code = static_cast<DWORD>(events.iErrorCode[FD_CONNECT_BIT]); + if (current_ai_->ai_next && ( + error_code == WSAEADDRNOTAVAIL || + error_code == WSAEAFNOSUPPORT || + error_code == WSAECONNREFUSED || + error_code == WSAENETUNREACH || + error_code == WSAEHOSTUNREACH || + error_code == WSAETIMEDOUT)) { + // Try using the next address. + const struct addrinfo* next = current_ai_->ai_next; + Disconnect(); + current_ai_ = next; + result = Connect(callback_); + } else { + result = MapWinsockError(error_code); + } + } else { + NOTREACHED(); + result = ERR_UNEXPECTED; + } + + if (result != ERR_IO_PENDING) + DoCallback(result); +} + +void TCPClientSocket::DidCompleteIO() { + DWORD num_bytes, flags; + BOOL ok = WSAGetOverlappedResult( + socket_, &overlapped_, &num_bytes, FALSE, &flags); + WSAResetEvent(overlapped_.hEvent); + if (wait_state_ == WAITING_READ) { + TRACE_EVENT_END("socket.read", this, StringPrintf("%d bytes", num_bytes)); + } else { + TRACE_EVENT_END("socket.write", this, StringPrintf("%d bytes", num_bytes)); + } + wait_state_ = NOT_WAITING; + DoCallback(ok ? num_bytes : MapWinsockError(WSAGetLastError())); +} + +void TCPClientSocket::OnObjectSignaled(HANDLE object) { + DCHECK(object == overlapped_.hEvent); + + switch (wait_state_) { + case WAITING_CONNECT: + DidCompleteConnect(); + break; + case WAITING_READ: + case WAITING_WRITE: + DidCompleteIO(); + break; + default: + NOTREACHED(); + break; + } +} + +} // namespace net + |