瀏覽代碼

Add KCP engine for JsonRpcLite.

justin.xing 4 年之前
父節點
當前提交
d358db9a17

+ 93 - 0
JsonRpcLite.Kcp/JsonRpcKcpClientEngine.cs

@@ -0,0 +1,93 @@
+using System;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using JsonRpcLite.Rpc;
+using KcpNet;
+
+namespace JsonRpcLite.Kcp
+{
+    public class JsonRpcKcpClientEngine:IJsonRpcClientEngine
+    {
+        private readonly KcpClient _client;
+
+        public string Name { get; }
+
+
+        public JsonRpcKcpClientEngine(IPAddress address, int port, int connectTimeout = Timeout.Infinite)
+        {
+            Name = nameof(JsonRpcKcpClientEngine);
+            var remoteEndPoint = new IPEndPoint(address, port);
+            _client = new KcpClient();
+            _client.Connect(remoteEndPoint, connectTimeout);
+        }
+
+
+        private async Task<byte[]> ReadBytesAsync(int size, CancellationToken cancellationToken)
+        {
+            if (size > 0)
+            {
+                var bytes = new byte[size];
+                var length = size;
+                var startIndex = 0;
+                while (length > 0)
+                {
+                    var buffer = new byte[length];
+                    var bytesRead = await _client.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+                    if (bytesRead == 0)
+                    {
+                        throw new InvalidOperationException("Connection closed.");
+                    }
+                    if (bytesRead > 0)
+                    {
+                        Array.Copy(buffer, 0, bytes, startIndex, bytesRead);
+                        startIndex += bytesRead;
+                        length -= bytesRead;
+                    }
+                }
+
+                return bytes;
+            }
+
+            return null;
+        }
+
+        private async Task SendStringAsync(string str, CancellationToken cancellationToken)
+        {
+            var strData = Encoding.UTF8.GetBytes(str);
+            var lengthData = BitConverter.GetBytes(strData.Length);
+            await _client.SendAsync(lengthData, cancellationToken).ConfigureAwait(false);
+            await _client.SendAsync(strData, cancellationToken).ConfigureAwait(false);
+        }
+
+        public async Task<string> ProcessAsync(string serviceName, string requestString, CancellationToken cancellationToken)
+        {
+            var requestData = Encoding.UTF8.GetBytes(requestString);
+            var responseData = await ProcessAsync(serviceName, requestData, cancellationToken).ConfigureAwait(false);
+            return Encoding.UTF8.GetString(responseData);
+        }
+
+        public async Task<byte[]> ProcessAsync(string serviceName, byte[] requestData,CancellationToken cancellationToken)
+        {
+            await SendStringAsync(serviceName,cancellationToken).ConfigureAwait(false);
+            
+            var lengthData = BitConverter.GetBytes(requestData.Length);
+            await _client.SendAsync(lengthData ,cancellationToken).ConfigureAwait(false);
+            await _client.SendAsync(requestData, cancellationToken).ConfigureAwait(false);
+
+            lengthData = await ReadBytesAsync(4, cancellationToken).ConfigureAwait(false);
+            var length = BitConverter.ToInt32(lengthData);
+            var responseData = await ReadBytesAsync(length, cancellationToken).ConfigureAwait(false);
+            return responseData;
+        }
+
+        public async Task CloseAsync()
+        {
+            _client.Close();
+            await Task.CompletedTask;
+        }
+
+
+    }
+}

+ 278 - 0
JsonRpcLite.Kcp/JsonRpcKcpServerEngine.cs

@@ -0,0 +1,278 @@
+using System;
+using System.Net;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using JsonRpcLite.Log;
+using JsonRpcLite.Network;
+using JsonRpcLite.Rpc;
+using JsonRpcLite.Services;
+using JsonRpcLite.Utilities;
+using KcpNet;
+
+namespace JsonRpcLite.Kcp
+{
+    public class JsonRpcKcpServerEngine : IJsonRpcServerEngine
+    {
+        private readonly IPAddress _address;
+        private readonly int _port;
+
+        private IJsonRpcRouter _router;
+        private bool _stopped;
+        private KcpListener _listener;
+
+        public string Name { get; }
+
+        public JsonRpcKcpServerEngine(IPAddress address, int port)
+        {
+            Name = nameof(JsonRpcKcpServerEngine);
+            _address = address;
+            _port = port;
+        }
+
+        public void Start(IJsonRpcRouter router)
+        {
+            _router = router;
+            _stopped = false;
+            _listener = new KcpListener(new IPEndPoint(_address, _port));
+            _listener.Start();
+            Task.Factory.StartNew(async () =>
+            {
+                while (!_stopped)
+                {
+                    try
+                    {
+                        var connection = await _listener.GetConnectionAsync().ConfigureAwait(false);
+                        HandleConnection(connection, CancellationToken.None);
+                    }
+                    catch (Exception ex)
+                    {
+                        Logger.WriteError($"GetConnection error:{ex.Message}");
+                    }
+                }
+            }, TaskCreationOptions.LongRunning);
+            Logger.WriteInfo("JsonRpc http server engine started.");
+        }
+
+        public void Stop()
+        {
+            _listener.Close();
+        }
+
+        private async Task<byte[]> ReadBytesAsync(KcpConnection connection, int size, CancellationToken cancellationToken)
+        {
+            var bytes = new byte[size];
+            var length = size;
+            var startIndex = 0;
+            while (length > 0)
+            {
+                var buffer = new byte[length];
+                var bytesRead = await connection.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
+                if (bytesRead == 0)
+                {
+                    throw new InvalidOperationException("Connection could be closed.");
+                }
+                Array.Copy(buffer, 0, bytes, startIndex, bytesRead);
+                startIndex += bytesRead;
+                length -= bytesRead;
+            }
+
+            return bytes;
+        }
+
+
+        private async Task<int> ReadIntAsync(KcpConnection connection, CancellationToken cancellationToken)
+        {
+            var intData = await ReadBytesAsync(connection, 4, cancellationToken).ConfigureAwait(false);
+            return BitConverter.ToInt32(intData);
+        }
+
+        private async Task<string> ReadStringAsync(KcpConnection connection, CancellationToken cancellationToken)
+        {
+            var dataLength = await ReadIntAsync(connection, cancellationToken).ConfigureAwait(false);
+            var data = await ReadBytesAsync(connection, dataLength, cancellationToken).ConfigureAwait(false);
+            return Encoding.UTF8.GetString(data);
+        }
+
+
+        /// <summary>
+        /// Dispatch request to specified service.
+        /// </summary>
+        /// <param name="connection">The KcpConnection</param>
+        /// <param name="router">The router to handle the rpc request</param>
+        /// <param name="serviceName">The name of the service</param>
+        /// <param name="cancellationToken">The cancellation token which can cancel this method</param>
+        /// <returns>Void</returns>
+        private async Task DispatchAsync(KcpConnection connection, IJsonRpcRouter router, string serviceName, CancellationToken cancellationToken)
+        {
+            var dataLength = await ReadIntAsync(connection,cancellationToken).ConfigureAwait(false);
+            var requestData = await ReadBytesAsync(connection, dataLength,cancellationToken).ConfigureAwait(false);
+            if (Logger.DebugMode)
+            {
+                var requestString = Encoding.UTF8.GetString(requestData);
+                Logger.WriteDebug($"Receive request data:{requestString}");
+            }
+            var requests = await JsonRpcCodec.DecodeRequestsAsync(requestData, cancellationToken, dataLength).ConfigureAwait(false);
+            var responses = await router.DispatchRequestsAsync(serviceName, requests, cancellationToken).ConfigureAwait(false);
+            await WriteRpcResponsesAsync(connection, responses, cancellationToken).ConfigureAwait(false);
+        }
+
+
+        private void HandleConnection(KcpConnection connection, CancellationToken cancellationToken)
+        {
+            Task.Run(async () =>
+            {
+                try
+                {
+                    while (true)
+                    {
+                        try
+                        {
+                            var serviceName = await ReadStringAsync(connection, cancellationToken).ConfigureAwait(false);
+                            if (string.IsNullOrEmpty(serviceName))
+                            {
+                                Logger.WriteWarning($"Service {serviceName} not found.");
+                                throw new ServerErrorException("Service does not exist.", $"Service [{serviceName}] does not exist.");
+                            }
+                            if (!_router.ServiceExists(serviceName))
+                            {
+                                Logger.WriteWarning($"Service {serviceName} not found.");
+                                throw new ServerErrorException("Service does not exist.", $"Service [{serviceName}] does not exist.");
+                            }
+
+                            try
+                            {
+                                await DispatchAsync(connection, _router, serviceName, cancellationToken).ConfigureAwait(false);
+                            }
+                            catch (Exception ex)
+                            {
+                                if (ex is RpcException)
+                                {
+                                    throw;
+                                }
+
+                                throw new ServerErrorException("Internal server error.", ex.Message);
+                            }
+
+                        }
+                        catch (Exception ex)
+                        {
+                            Logger.WriteError($"Handle request error: {ex.Message}");
+                            if (ex is HttpException httpException)
+                            {
+                                await WriteExceptionAsync(connection, httpException, cancellationToken).ConfigureAwait(false);
+                            }
+                            else
+                            {
+                                var response = new JsonRpcResponse();
+                                if (ex is RpcException rpcException)
+                                {
+                                    response.WriteResult(rpcException);
+                                }
+                                else
+                                {
+                                    var serverError = new InternalErrorException($"Handle request error: {ex.Message}");
+                                    response.WriteResult(serverError);
+                                }
+
+                                await WriteRpcResponsesAsync(connection, new[] { response }, cancellationToken).ConfigureAwait(false);
+                            }
+                        }
+                    }
+                }
+                catch (Exception)
+                {
+                    //
+                }
+            }, cancellationToken);
+        }
+
+
+        /// <summary>
+        /// Write exception back to the client.
+        /// </summary>
+        /// <param name="connection">The KcpConnection</param>
+        /// <param name="exception">The exception to write back.</param>
+        /// <param name="cancellationToken">The cancel token which will cancel this method.</param>
+        /// <returns>Void</returns>
+        private async Task WriteExceptionAsync(KcpConnection connection, HttpException exception, CancellationToken cancellationToken)
+        {
+            try
+            {
+                await WriteResultAsync(connection, exception.Message, cancellationToken).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                Logger.WriteWarning($"Write http exception back to client error:{ex}");
+            }
+        }
+
+
+        /// <summary>
+        /// Write http message to remote side.
+        /// </summary>
+        /// <param name="connection">The KcpConnection.</param>
+        /// <param name="message">The message to write back.</param>
+        /// <param name="cancellationToken">The cancellation token which will cancel this method.</param>
+        /// <returns>Void</returns>
+        private async Task WriteResultAsync(KcpConnection connection, string message, CancellationToken cancellationToken)
+        {
+            var data = Encoding.UTF8.GetBytes(message);
+            var lengthData = BitConverter.GetBytes(data.Length);
+            await connection.SendAsync(lengthData, cancellationToken).ConfigureAwait(false);
+            await connection.SendAsync(data, cancellationToken).ConfigureAwait(false);
+            if (Logger.DebugMode)
+            {
+                Logger.WriteDebug($"Response data sent:{message}");
+            }
+        }
+
+        /// <summary>
+        /// Write rpc responses back to the client.
+        /// </summary>
+        /// <param name="connection">The KcpConnection</param>
+        /// <param name="responses">The responses to write back.</param>
+        /// <param name="cancellationToken">The cancel token which will cancel this method.</param>
+        /// <returns>Void</returns>
+        private async Task WriteRpcResponsesAsync(KcpConnection connection, JsonRpcResponse[] responses, CancellationToken cancellationToken)
+        {
+            try
+            {
+                var resultData = await JsonRpcCodec.EncodeResponsesAsync(responses, cancellationToken).ConfigureAwait(false);
+                await WriteRpcResultAsync(connection, resultData, cancellationToken).ConfigureAwait(false);
+            }
+            catch (Exception ex)
+            {
+                Logger.WriteWarning($"Write rpc response back to client error:{ex}");
+            }
+        }
+
+
+        /// <summary>
+        /// Write rpc result struct data to remote side.
+        /// </summary>
+        /// <param name="connection">The KcpConnection.</param>
+        /// <param name="result">The result data to write</param>
+        /// <param name="cancellationToken">The cancellation which can cancel this method</param>
+        /// <returns>Void</returns>
+        private async Task WriteRpcResultAsync(KcpConnection connection, byte[] result, CancellationToken cancellationToken)
+        {
+            if (result != null)
+            {
+                var data = result;
+                var lengthData = BitConverter.GetBytes(data.Length);
+                await connection.SendAsync(lengthData, cancellationToken).ConfigureAwait(false);
+                await connection.SendAsync(data, cancellationToken).ConfigureAwait(false);
+            }
+            if (Logger.DebugMode)
+            {
+                if (result != null)
+                {
+                    var resultString = Encoding.UTF8.GetString(result);
+                    Logger.WriteDebug($"Response data sent:{resultString}");
+                }
+            }
+        }
+
+    }
+}

+ 18 - 0
JsonRpcLite.Kcp/JsonRpcLite.Kcp.csproj

@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net5.0</TargetFramework>
+    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+    <Authors>IUS, VINNO</Authors>
+    <Company>VINNO Technology (Suzhou) Co.,Ltd.</Company>
+    <PackageProjectUrl>http://git.ius.plus:88/Project-Wing/JsonRpcLite</PackageProjectUrl>
+    <RepositoryUrl>http://git.ius.plus:88/Project-Wing/JsonRpcLite</RepositoryUrl>
+    <Description>KCP engine for JsonRpcLite.</Description>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="JsonRpcLite" Version="1.0.0" />
+    <PackageReference Include="KcpNet" Version="1.0.0" />
+  </ItemGroup>
+
+</Project>

+ 12 - 0
JsonRpcLite.Kcp/Properties/PublishProfiles/FolderProfile.pubxml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+https://go.microsoft.com/fwlink/?LinkID=208121. 
+-->
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration>Release</Configuration>
+    <Platform>Any CPU</Platform>
+    <PublishDir>bin\Release\net5.0\publish\</PublishDir>
+    <PublishProtocol>FileSystem</PublishProtocol>
+  </PropertyGroup>
+</Project>

+ 6 - 0
JsonRpcLite.sln

@@ -13,6 +13,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "JsonRpcLite.Kestrel", "Json
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestClient", "TestClient\TestClient.csproj", "{2D661961-2C18-4DCD-859A-B166833AAF13}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "JsonRpcLite.Kcp", "JsonRpcLite.Kcp\JsonRpcLite.Kcp.csproj", "{3C0FAB7D-76A9-45C2-A566-48FE6D801DB2}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -39,6 +41,10 @@ Global
 		{2D661961-2C18-4DCD-859A-B166833AAF13}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{2D661961-2C18-4DCD-859A-B166833AAF13}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{2D661961-2C18-4DCD-859A-B166833AAF13}.Release|Any CPU.Build.0 = Release|Any CPU
+		{3C0FAB7D-76A9-45C2-A566-48FE6D801DB2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{3C0FAB7D-76A9-45C2-A566-48FE6D801DB2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{3C0FAB7D-76A9-45C2-A566-48FE6D801DB2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{3C0FAB7D-76A9-45C2-A566-48FE6D801DB2}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 2 - 2
JsonRpcLite/Utilities/JsonRpcCodec.cs

@@ -9,7 +9,7 @@ using System.Threading.Tasks;
 
 namespace JsonRpcLite.Utilities
 {
-    internal class JsonRpcCodec
+    public class JsonRpcCodec
     {
         /// <summary>
         /// Convert one JsonElement to a JsonRpcRequest
@@ -272,7 +272,7 @@ namespace JsonRpcLite.Utilities
         /// <param name="parameters">The parameters of the calling method.</param>
         /// <param name="cancellationToken">The cancellation token which will cancel this method.</param>
         /// <returns>The converted arguments.</returns>
-        public static async Task<object[]> DecodeArgumentsAsync(string paramString, IReadOnlyList<JsonRpcCallParameter> parameters, CancellationToken cancellationToken)
+        internal static async Task<object[]> DecodeArgumentsAsync(string paramString, IReadOnlyList<JsonRpcCallParameter> parameters, CancellationToken cancellationToken)
         {
             if (parameters.Count == 0)
             {

+ 33 - 15
TestClient/Program.cs

@@ -2,8 +2,10 @@
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.Linq;
+using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
+using JsonRpcLite.Kcp;
 using JsonRpcLite.Network;
 using JsonRpcLite.Rpc;
 
@@ -12,7 +14,7 @@ namespace TestClient
     class Program
     {
         private static readonly string[] TestData = {
-            "{\"jsonrpc\": \"2.0\", \"method\":\"add\",\"params\":[1,2],\"id\":1}",
+            "{\"jsonrpc\": \"2.0\", \"method\":\"internal.echo\",\"params\":\"aaaa\",\"id\":1}",
             "{\"jsonrpc\": \"2.0\", \"method\":\"addInt\",\"params\":[1,7],\"id\":2}",
             "{\"jsonrpc\": \"2.0\", \"method\":\"NullableFloatToNullableFloat\",\"params\":[1.23],\"id\":3}",
             "{\"jsonrpc\": \"2.0\", \"method\":\"Test2\",\"params\":[3.456],\"id\":4}",
@@ -28,27 +30,43 @@ namespace TestClient
                 remoteUrl = args[0];
             }
             var client = new JsonRpcClient();
-            IJsonRpcClientEngine clientEngine = remoteUrl.StartsWith("http")? 
-                new JsonRpcHttpClientEngine(remoteUrl):
-                new JsonRpcWebSocketClientEngine(remoteUrl);
+            IJsonRpcClientEngine clientEngine = null;
+            //if (remoteUrl.StartsWith("http"))
+            //{
+            //    clientEngine = new JsonRpcHttpClientEngine(remoteUrl);
+            //}
+            //else if(remoteUrl.StartsWith("ws"))
+            //{
+            //   clientEngine = new JsonRpcWebSocketClientEngine(remoteUrl);
+            //}
+            if (clientEngine == null)
+            {
+                clientEngine = new JsonRpcKcpClientEngine(IPAddress.Parse("127.0.0.1"), 6000);
+            }
 
             bool ws = clientEngine is JsonRpcWebSocketClientEngine;
 
             client.UseEngine(clientEngine);
 
-            var testCount = 3;
-            if (args.Contains("-benchmark"))
-            {
-                testCount = 100;
-            }
-            var statisticsList = new List<int>();
-            for (var i = 0; i < testCount; i++)
+            //var testCount = 3;
+            //if (args.Contains("-benchmark"))
+            //{
+            //    testCount = 100;
+            //}
+            //var statisticsList = new List<int>();
+            //for (var i = 0; i < testCount; i++)
+            //{
+            //    statisticsList.Add(Benchmark(client, TestData, ws));
+            //    Console.WriteLine();
+            //}
+            for (var i = 0; i < 100; i++)
             {
-                statisticsList.Add(Benchmark(client, TestData, ws));
-                Console.WriteLine();
+                var result = client.ProcessAsync("test1", TestData[0]).Result;
+
+                Console.WriteLine(result);
             }
-            Console.WriteLine();
-            Console.WriteLine($"Best: {statisticsList.Max()} rpc/sec, \t Average: {(int)statisticsList.Average()} rpc/sec, \t Worst: {statisticsList.Min()} rpc/sec");
+
+            //Console.WriteLine($"Best: {statisticsList.Max()} rpc/sec, \t Average: {(int)statisticsList.Average()} rpc/sec, \t Worst: {statisticsList.Min()} rpc/sec");
             Console.ReadLine();
         }
 

+ 1 - 1
TestClient/TestClient.csproj

@@ -6,7 +6,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\JsonRpcLite\JsonRpcLite.csproj" />
+    <ProjectReference Include="..\JsonRpcLite.Kcp\JsonRpcLite.Kcp.csproj" />
   </ItemGroup>
 
 </Project>

+ 26 - 19
TestServer/Program.cs

@@ -6,7 +6,8 @@ using System.Net;
 using System.Threading;
 using System.Threading.Tasks;
 using JsonRpcLite.InProcess;
-using JsonRpcLite.Kestrel;
+using JsonRpcLite.Kcp;
+//using JsonRpcLite.Kestrel;
 using JsonRpcLite.Log;
 using JsonRpcLite.Network;
 using JsonRpcLite.Rpc;
@@ -54,28 +55,34 @@ namespace TestServer
             else
             {
                 IJsonRpcServerEngine serverEngine;
-                if (args.Contains("-websocket"))
+                //if (args.Contains("-kcp"))
                 {
-                    serverEngine = new JsonRpcWebSocketServerEngine("http://*:8090/");
-                    server.UseEngine(serverEngine);
-                }
-                else if(args.Contains("-websocket-kestrel"))
-                {
-                    serverEngine = new JsonRpcKestrelWebSocketServerEngine(IPAddress.Any, 8090);
-                    server.UseEngine(serverEngine);
-                }
-                else
-                if (args.Contains("-http-kestrel"))
-                {
-                    serverEngine = new JsonRpcKestrelHttpServerEngine(IPAddress.Any, 8090);
-                    server.UseEngine(serverEngine);
-                }
-                else
-                {
-                    serverEngine = new JsonRpcHttpServerEngine("http://*:8090/");
+                    serverEngine = new JsonRpcKcpServerEngine(IPAddress.Any, 6000);
                     server.UseEngine(serverEngine);
                 }
 
+                //if (args.Contains("-websocket"))
+                //{
+                //    serverEngine = new JsonRpcWebSocketServerEngine("http://*:8090/");
+                //    server.UseEngine(serverEngine);
+                //}
+                //else if(args.Contains("-websocket-kestrel"))
+                //{
+                //    serverEngine = new JsonRpcKestrelWebSocketServerEngine(IPAddress.Any, 8090);
+                //    server.UseEngine(serverEngine);
+                //}
+                //else
+                //if (args.Contains("-http-kestrel"))
+                //{
+                //    serverEngine = new JsonRpcKestrelHttpServerEngine(IPAddress.Any, 8090);
+                //    server.UseEngine(serverEngine);
+                //}
+                //else
+                //{
+                //    serverEngine = new JsonRpcHttpServerEngine("http://*:8090/");
+                //    server.UseEngine(serverEngine);
+                //}
+
                 server.Start();
                 Console.WriteLine($"JsonRpc Server Started with engine: {serverEngine.Name}.");
             }

+ 0 - 1
TestServer/Properties/launchSettings.json

@@ -2,7 +2,6 @@
   "profiles": {
     "TestServer": {
       "commandName": "Project",
-      "commandLineArgs": "-benchmark",
       "nativeDebugging": true
     }
   }

+ 1 - 2
TestServer/TestServer.csproj

@@ -11,8 +11,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <ProjectReference Include="..\JsonRpcLite.Kestrel\JsonRpcLite.Kestrel.csproj" />
-    <ProjectReference Include="..\JsonRpcLite\JsonRpcLite.csproj" />
+    <ProjectReference Include="..\JsonRpcLite.Kcp\JsonRpcLite.Kcp.csproj" />
   </ItemGroup>
 
 </Project>