326 lines
10 KiB
C++
326 lines
10 KiB
C++
/*
|
|
* 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 <utility>
|
|
#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<void> removeContext;
|
|
|
|
if (CoreApplication::Properties->HasKey("clientContext"))
|
|
{
|
|
auto clientContext = dynamic_cast<IClientContext^>(
|
|
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<void> 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<void> 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<void> 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<unsigned int> 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;
|
|
}
|