/* * Copyright (c) 2016, The OpenThread Authors. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holder nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "pch.h" #include #include "StreamClientContext.h" using namespace ot; using namespace Concurrency; using namespace Platform; using namespace Windows::ApplicationModel::Core; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::Networking::Sockets; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Controls::Primitives; using namespace Windows::UI::Xaml::Data; using namespace Windows::UI::Xaml::Input; using namespace Windows::UI::Xaml::Media; using namespace Windows::UI::Xaml::Navigation; StreamClientContext::StreamClientContext( IAsyncThreadNotify^ notify, StreamSocket^ client, ClientArgs^ args) : _notify{ std::move(notify) }, _client{ std::move(client) }, _args{ std::move(args) } { } StreamClientContext::~StreamClientContext() { // A Client can be closed in two ways: // - explicitly: using the 'delete' keyword (client is closed even if there are outstanding references to it). // - implicitly: removing the last reference to it (i.e., falling out-of-scope). // // When a Socket is closed implicitly, it can take several seconds for the local port being used // by it to be freed/reclaimed by the lower networking layers. During that time, other sockets on the machine // will not be able to use the port. Thus, it is strongly recommended that Socket instances be explicitly // closed before they go out of scope(e.g., before application exit). The call below explicitly closes the socket. if (_client != nullptr) { delete _client; _client = nullptr; } } void StreamClientContext::Connect_Click( Object^ sender, RoutedEventArgs^ e) { task removeContext; if (CoreApplication::Properties->HasKey("clientContext")) { auto clientContext = dynamic_cast( CoreApplication::Properties->Lookup("clientContext")); if (clientContext == nullptr) { throw ref new FailureException(L"No clientContext"); } removeContext = create_task(clientContext->CancelIO()).then( []() { CoreApplication::Properties->Remove("clientContext"); }); } else { removeContext = create_task([]() {}); } removeContext.then([this](task prevTask) { try { // Try getting an exception. prevTask.get(); // Events cannot be hooked up directly to the ScenarioInput2 object, as the object can fall out-of-scope and be // deleted. This would render any event hooked up to the object ineffective. The ClientContext guarantees that // both the socket and object that serves its events have the same lifetime. CoreApplication::Properties->Insert("clientContext", this); } catch (Exception^ ex) { _notify->NotifyFromAsyncThread( "Remove clientContext error: " + ex->Message, NotifyType::Error); } catch (task_canceled&) { } }).then([this]() { auto endpointPair = ref new EndpointPair(_args->ClientHostName, _args->ClientPort, _args->ServerHostName, _args->ServerPort); _notify->NotifyFromAsyncThread("Start connecting", NotifyType::Status); create_task(_client->ConnectAsync(endpointPair)).then( [this, endpointPair](task prevTask) { try { // Try getting an exception. prevTask.get(); _notify->NotifyFromAsyncThread( "Connect from " + endpointPair->LocalHostName->CanonicalName + " to " + endpointPair->RemoteHostName->CanonicalName, NotifyType::Status); OnConnection(_client); } catch (Exception^ ex) { _notify->NotifyFromAsyncThread( "Start binding failed with error: " + ex->Message, NotifyType::Error); CoreApplication::Properties->Remove("clientContext"); } catch (task_canceled&) { CoreApplication::Properties->Remove("clientContext"); } }); }); } void ot::StreamClientContext::Send_Click( Platform::Object^ sender, Windows::UI::Xaml::RoutedEventArgs^ e, Platform::String^ input) { SendMessage(GetDataWriter(), input); } IAsyncAction^ ot::StreamClientContext::CancelIO() { return _client->CancelIOAsync(); } void StreamClientContext::OnConnection( StreamSocket^ streamSocket) { SetConnected(true); ReceiveLoop(streamSocket, GetDataReader()); } void StreamClientContext::SetConnected( bool connected) { _connected = connected; } bool StreamClientContext::IsConnected() const { return _connected; } void StreamClientContext::ReceiveLoop( StreamSocket^ streamSocket, DataReader^ dataReader) { // Read first 4 bytes (length of the subsequent string). create_task(dataReader->LoadAsync(sizeof(UINT32))).then( [this, dataReader](unsigned int size) { if (size < sizeof(UINT32)) { // The underlying socket was closed before we were able to read the whole data. cancel_current_task(); } auto strLen = dataReader->ReadUInt32(); return create_task(dataReader->LoadAsync(strLen)).then( [this, dataReader, strLen](unsigned int actualStrLen) { if (actualStrLen != strLen) { // The underlying socket was closed before we were able to read the whole data. cancel_current_task(); } Receive(dataReader, strLen); }); }).then([this, streamSocket, dataReader](task previousTask) { try { // Try getting all exceptions from the continuation chain above this point. previousTask.get(); // Everything went ok, so try to receive another string. The receive will continue until the stream is // broken (i.e. peer closed the socket). ReceiveLoop(streamSocket, dataReader); } catch (Exception^ ex) { _notify->NotifyFromAsyncThread("Read stream failed with error: " + ex->Message, NotifyType::Error); // Explicitly close the socket. delete streamSocket; } catch (task_canceled&) { // Do not print anything here - this will usually happen because user closed the client socket. // Explicitly close the socket. delete streamSocket; } }); } void StreamClientContext::Receive( DataReader^ dataReader, unsigned int strLen) { if (!strLen) { return; } auto msg = dataReader->ReadString(strLen); _notify->NotifyFromAsyncThread("Received data from server: \"" + msg + "\"", NotifyType::Status); } void StreamClientContext::SendMessage( DataWriter^ dataWriter, String^ msg) { if (!IsConnected()) { _notify->NotifyFromAsyncThread("This socket is not yet connected.", NotifyType::Error); return; } try { dataWriter->WriteUInt32(msg->Length()); dataWriter->WriteString(msg); _notify->NotifyFromAsyncThread("Sending - " + msg, NotifyType::Status); } catch (Exception^ ex) { _notify->NotifyFromAsyncThread("Sending failed with error: " + ex->Message, NotifyType::Error); } // Write the locally buffered data to the network. Please note that write operation will succeed // even if the server is not listening. create_task(dataWriter->StoreAsync()).then( [this](task writeTask) { try { // Try getting an exception. writeTask.get(); } catch (Exception^ ex) { _notify->NotifyFromAsyncThread("Send failed with error: " + ex->Message, NotifyType::Error); } }); } Windows::Storage::Streams::DataReader^ StreamClientContext::GetDataReader() { if (_dataReader == nullptr) { _dataReader = ref new DataReader(_client->InputStream); } return _dataReader; } Windows::Storage::Streams::DataWriter^ StreamClientContext::GetDataWriter() { if (_dataWriter == nullptr) { _dataWriter = ref new DataWriter(_client->OutputStream); } return _dataWriter; }