diff --git a/CMakeLists.txt b/CMakeLists.txt index 9db4ebf..3eab8c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -286,5 +286,6 @@ if(CPPDAP_BUILD_EXAMPLES) endfunction(build_example) build_example(hello_debugger) + build_example(simple_net_client_server) endif(CPPDAP_BUILD_EXAMPLES) diff --git a/examples/simple_net_client_server.cpp b/examples/simple_net_client_server.cpp new file mode 100644 index 0000000..2e5380e --- /dev/null +++ b/examples/simple_net_client_server.cpp @@ -0,0 +1,104 @@ +// Copyright 2022 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// simple_net_client_server demonstrates a minimal DAP network connection +// between a server and client. + +#include "dap/io.h" +#include "dap/network.h" +#include "dap/protocol.h" +#include "dap/session.h" + +#include +#include + +int main(int, char*[]) { + constexpr int kPort = 19021; + + // Callback handler for a socket connection to the server + auto onClientConnected = + [&](const std::shared_ptr& socket) { + auto session = dap::Session::create(); + session->bind(socket); + + // The Initialize request is the first message sent from the client and + // the response reports debugger capabilities. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Initialize + session->registerHandler([&](const dap::InitializeRequest&) { + dap::InitializeResponse response; + printf("Server received initialize request from client\n"); + return response; + }); + + // Signal used to terminate the server session when a DisconnectRequest + // is made by the client. + bool terminate = false; + std::condition_variable cv; + std::mutex mutex; // guards 'terminate' + + // The Disconnect request is made by the client before it disconnects + // from the server. + // https://microsoft.github.io/debug-adapter-protocol/specification#Requests_Disconnect + session->registerHandler([&](const dap::DisconnectRequest&) { + // Client wants to disconnect. Set terminate to true, and signal the + // condition variable to unblock the server thread. + std::unique_lock lock(mutex); + terminate = true; + cv.notify_one(); + return dap::DisconnectResponse{}; + }); + + // Wait for the client to disconnect (or reach a 5 second timeout) + // before releasing the session and disconnecting the socket to the + // client. + std::unique_lock lock(mutex); + cv.wait_for(lock, std::chrono::seconds(5), [&] { return terminate; }); + printf("Server closing connection\n"); + }; + + // Error handler + auto onError = [&](const char* msg) { printf("Server error: %s\n", msg); }; + + // Create the network server + auto server = dap::net::Server::create(); + // Start listening on kPort. + // onClientConnected will be called when a client wants to connect. + // onError will be called on any connection errors. + server->start(kPort, onClientConnected, onError); + + // Create a socket to the server. This will be used for the client side of the + // connection. + auto client = dap::net::connect("localhost", kPort); + if (!client) { + printf("Couldn't connect to server\n"); + return 1; + } + + // Attach a session to the client socket. + auto session = dap::Session::create(); + session->bind(client); + + // Set an initialize request to the server. + auto future = session->send(dap::InitializeRequest{}); + printf("Client sent initialize request to server\n"); + printf("Waiting for response from server...\n"); + // Wait on the response. + auto response = future.get(); + printf("Response received from server\n"); + printf("Disconnecting...\n"); + // Disconnect. + session->send(dap::DisconnectRequest{}); + + return 0; +}