반응형

리플렉션의 MethodInfo.Invoke는 속도가 많이 느리다.

이를 개선하기 위해서는 코드 제너레이터를 통하여 코드를 생성하는 방법과 델리게이트를 이용한 방법이 널리 알려져 있다.

method.CreateDelegate를 통하여 MethodInfo.Invoke 사용할 수도 있지만 이 방법은 객체가 생성이 된 후에 초기화가 가능하다.

.Net MVC에서 Controller는 표현식 트리(Expression Tree) 라는 것을 사용하여 리플렉션의 느린 속도를 개선하였다.

 

표현식 트리는 코드의 구조와 동작을 표현하는 데이터 구조이다. 주로 코드의 구문 분석, 변환, 또는 표현을 위해 사용된다.

 

Expression.Parameter: 메서드나 생성자에서 사용될 파라미터를 정의한다.
Expression.Call: 다른 메서드를 호출하는 표현식을 생성한다.
Expression.Lambda<Action<AA>>(methodCall, aaParam).Compile(): 표현식 트리를 델리게이트로 변환하고 컴파일한다.

이러한 방식으로 구성된 아래 코드는 MethodInfo invoke에 비해 훨씬 빠른 실행 속도를 보여준다.

 

using System;
using System.Diagnostics;
using System.Linq.Expressions;

namespace ConsoleApp
{
    public class AA
    {
        private int _index = 0;
        public void Call()
        {
            //Console.WriteLine($"{_index++}");
        }
    }

    class Program
    {
        delegate void CallDelegate();
        static void Main(string[] args)
        {
            var aaType = typeof(AA);
            var method = aaType.GetMethod("Call");
            Stopwatch sw = new Stopwatch();

            var a = new AA();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                a.Call();
            }
            sw.Stop();
            Console.WriteLine($"Direct call : {sw.Elapsed}");

            var b = new AA();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                method.Invoke(b, null);
            }
            sw.Stop();
            Console.WriteLine($"MethodInfo call : {sw.Elapsed}");

            // Delegate call
            var c = new AA();
            CallDelegate callDelegate = (CallDelegate)method.CreateDelegate(typeof(CallDelegate), c);
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                callDelegate();
            }
            sw.Stop();
            Console.WriteLine($"Delegate call : {sw.Elapsed}");

            var aaParam = Expression.Parameter(aaType, "instance");
            var methodCall = Expression.Call(aaParam, method);
            var actionLambda = Expression.Lambda<Action<AA>>(methodCall, aaParam).Compile();

            var d = new AA();
            sw.Reset();
            sw.Start();
            for (int i = 0; i < 100000; ++i)
            {
                actionLambda(d);
            }
            sw.Stop();
            Console.WriteLine($"Expression call : {sw.Elapsed}");

            Console.ReadLine();
        }
    }
}

예시) 상속받은 클래스의 Event를 리플렉션으로 탐색하고 캐시하여 RaiseEvent 메소드 호출시 자동으로 Event 호출 까지 실행해준다.

public abstract class EventProtocolHandlerBase
{
    private readonly Dictionary<Type, Action<INotifyEventArgs>> _eventHandlers = new Dictionary<Type, Action<INotifyEventArgs>>();
    public EventProtocolHandlerBase()
    {
        RegisterEvents(GetType());
    }
    public void RaiseEvent<T>(T eventArgs) where T : INotifyEventArgs
    {
        if (_eventHandlers.TryGetValue(typeof(T), out var handler))
        {
            if (handler is Action<T> action)
            {
                Add(() => { action(eventArgs); });
            }
        }
        else
        {
            LogHelper.Error($"not found event type! {typeof(T).Name}");
        }
    }
    public void RegisterEvents(Type type)
    {
        foreach (var eventInfo in type.GetEvents())
        {
            var argumentType = eventInfo.EventHandlerType.GetGenericArguments()[0];
            var eventParameter = Expression.Parameter(typeof(INotifyEventArgs), "args");
            var castArg = Expression.TypeAs(eventParameter, argumentType);

            var eventFieldExpr = Expression.Field(Expression.Constant(this), eventInfo.Name);
            var invokeExpr = Expression.Invoke(eventFieldExpr, castArg);

            var notNullCheck = Expression.NotEqual(eventFieldExpr, Expression.Constant(null, typeof(Action<>).MakeGenericType(argumentType)));
            var conditionalExpr = Expression.IfThen(notNullCheck, invokeExpr);
            var lambda = Expression.Lambda<Action<INotifyEventArgs>>(conditionalExpr, eventParameter);
            var action = lambda.Compile();
            RegisterEvent(argumentType, action);
        }
    }
    public void RegisterEvent(Type type, Action<INotifyEventArgs> action)
    {
        _eventHandlers[type] = action;
    }
}
반응형
반응형

System.IO.Pipelines 는 .NET에서 고성능 I/O 를 더 쉽게 수행할 수 있도록 설계된 라이브러리이다.

Kestrel 을 업계에서 가장 빠른 웹 서버 중 하나로 만들기 위해 수행한 작업에서 탄생했다.

웹 소켓 라이브러리인 SignalR 에도 포함이 되어있고 네트워크단에서 처리를 하는 SDK에는 포함이 대부분 되어 있는 것 같다.

해당 벤치마크에서 확인이 가능하다.

 

https://www.techempower.com/benchmarks/#section=data-r21&hw=ph&test=plaintext

 

TechEmpower Framework Benchmarks

 

www.techempower.com

 

소켓 버퍼로부터 유저 어플리케이션단으로 데이터를 가져오기 위해 해당 버퍼 만큼의 메모리를 할당하고 값을 복사하는 작업 등이 필요하고 각 개발자마다 이 부분을 속도나 메모리나 GC 등 단점 등을 보완하기 위해 각자의 방법으로 처리합니다.

 

아래는 패킷의 완성이 \r\n 으로 데이터의 끝을 알리는 MS형님의 예제이다.

  • \r\n을 찾을 때까지 들어오는 데이터를 버퍼링 해야 합니다.
  • 버퍼에 반환된 모든 줄 구문을 분석해야 한다.
  • 보낸 사이즈보다 버퍼가 작아 데이터가 덜 들어오는 경우엔 해당 데이터를 저장을 해야 하고 다음 데이터를 이어 받아야 한다.
async Task ProcessLinesAsync(NetworkStream stream)
{
    var buffer = new byte[1024];
    var bytesBuffered = 0;
    var bytesConsumed = 0;
 
    while (true)
    {
        var bytesRead = await stream.ReadAsync(buffer, bytesBuffered, buffer.Length - bytesBuffered);
        if (bytesRead == 0)
        {
            // EOF
            break;
        }
        // Keep track of the amount of buffered bytes
        bytesBuffered += bytesRead;
         
        var linePosition = -1;
 
        do
        {
            // Look for a EOL in the buffered data
            linePosition = Array.IndexOf(buffer, (byte)'\n', bytesConsumed, bytesBuffered - bytesConsumed);
 
            if (linePosition >= 0)
            {
                // Calculate the length of the line based on the offset
                var lineLength = linePosition - bytesConsumed;
 
                // Process the line
                ProcessLine(buffer, bytesConsumed, lineLength);
 
                // Move the bytesConsumed to skip past the line we consumed (including \n)
                bytesConsumed += lineLength + 1;
            }
        }
        while (linePosition >= 0);
    }
}
  • 버퍼 풀을 이용해서 1024만큼의 버퍼 풀링을 시작했으나 1024 사이즈보다 작은 데이터들이 들어오는 경우 메모리를 더 많이 사용한다.
async Task ProcessLinesAsync(NetworkStream stream)
{
    byte[] buffer = ArrayPool<byte>.Shared.Rent(1024);
    var bytesBuffered = 0;
    var bytesConsumed = 0;
 
    while (true)
    {
        // Calculate the amount of bytes remaining in the buffer
        var bytesRemaining = buffer.Length - bytesBuffered;
 
        if (bytesRemaining == 0)
        {
            // Double the buffer size and copy the previously buffered data into the new buffer
            var newBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length * 2);
            Buffer.BlockCopy(buffer, 0, newBuffer, 0, buffer.Length);
            // Return the old buffer to the pool
            ArrayPool<byte>.Shared.Return(buffer);
            buffer = newBuffer;
            bytesRemaining = buffer.Length - bytesBuffered;
        }
 
        var bytesRead = await stream.ReadAsync(buffer, bytesBuffered, bytesRemaining);
        if (bytesRead == 0)
        {
            // EOF
            break;
        }
         
        // Keep track of the amount of buffered bytes
        bytesBuffered += bytesRead;
         
        do
        {
            // Look for a EOL in the buffered data
            linePosition = Array.IndexOf(buffer, (byte)'\n', bytesConsumed, bytesBuffered - bytesConsumed);
 
            if (linePosition >= 0)
            {
                // Calculate the length of the line based on the offset
                var lineLength = linePosition - bytesConsumed;
 
                // Process the line
                ProcessLine(buffer, bytesConsumed, lineLength);
 
                // Move the bytesConsumed to skip past the line we consumed (including \n)
                bytesConsumed += lineLength + 1;
            }
        }
        while (linePosition >= 0);
    }
}
  • 메모리 낭비를 막기 위해 512보다 작은 경우엔 새로 메모리를 할당한다.
  • 처리량을 늘리기 위해 소켓에 들어온 데이터를 읽는 것과 유저 어플리케이션에 저장된 버퍼를 처리하는 로직을 분산해서 동시에 처리한다.
  • 기타 등등 최적화를 위한 로직이 들어간다면 코드가 복잡해진다.

System.IO.Pipelines가 있는 TCP 서버

async Task ProcessLinesAsync(Socket socket)
{
    var pipe = new Pipe();
    Task writing = FillPipeAsync(socket, pipe.Writer);
    Task reading = ReadPipeAsync(pipe.Reader);
 
    return Task.WhenAll(reading, writing);
}
 
async Task FillPipeAsync(Socket socket, PipeWriter writer)
{
    const int minimumBufferSize = 512;
 
    while (true)
    {
        // Allocate at least 512 bytes from the PipeWriter
        Memory<byte> memory = writer.GetMemory(minimumBufferSize);
        try
        {
            int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
            if (bytesRead == 0)
            {
                break;
            }
            // Tell the PipeWriter how much was read from the Socket
            writer.Advance(bytesRead);
        }
        catch (Exception ex)
        {
            LogError(ex);
            break;
        }
 
        // Make the data available to the PipeReader
        FlushResult result = await writer.FlushAsync();
 
        if (result.IsCompleted)
        {
            break;
        }
    }
 
    // Tell the PipeReader that there's no more data coming
    writer.Complete();
}
 
async Task ReadPipeAsync(PipeReader reader)
{
    while (true)
    {
        ReadResult result = await reader.ReadAsync();
 
        ReadOnlySequence<byte> buffer = result.Buffer;
        SequencePosition? position = null;
 
        do
        {
            // Look for a EOL in the buffer
            position = buffer.PositionOf((byte)'\n');
 
            if (position != null)
            {
                // Process the line
                ProcessLine(buffer.Slice(0, position.Value));
                 
                // Skip the line + the \n character (basically position)
                buffer = buffer.Slice(buffer.GetPosition(1, position.Value));
            }
        }
        while (position != null);
 
        // Tell the PipeReader how much of the buffer we have consumed
        reader.AdvanceTo(buffer.Start, buffer.End);
 
        // Stop reading if there's no more data coming
        if (result.IsCompleted)
        {
            break;
        }
    }
 
    // Mark the PipeReader as complete
    reader.Complete();
}
  1. https://github.com/dotnet/runtime/blob/57bfe474518ab5b7cfe6bf7424a79ce3af9d6657/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/BufferSegmentStack.cs
  2. https://github.com/dotnet/runtime/blob/main/src/libraries/System.IO.Pipelines/src/System/IO/Pipelines/Pipe.cs
    1. 읽기용 데이터의 시작 인덱스 _readHeadIndex 와 읽기용 데이터의 끝 _readTailIndex 
    2. 쓰기용 데이터의 시작 인덱스 _writingHead 와 바이트의 수 _writingHeadBytesBuffered
    3. 이 인덱스들이 내부에서 왔다갔다 하면서 카피 대신에 해당하는 버퍼의 index들의 데이터들을 스택에 올려 반환하면서 처리를 하게 된다.
    4. 스택을 사용함으로써 GC가 돌지 않게 만들 수 있다.

References

https://learn.microsoft.com/en-us/dotnet/standard/io/pipelines?irgwc=1&OCID=AID2200057_aff_7593_1243925&tduid=(ir__qusfgwwd1wkfbzylzocc1exah32xcv00chrqwasx00)(7593)(1243925)(je6NUbpObpQ-QF0.r_GFsgNy_qAr0H6row)()&irclickid=_qusfgwwd1wkfbzylzocc1exah32xcv00chrqwasx00#pipe-basic-usage?ranMID=24542&ranEAID=je6NUbpObpQ&ranSiteID=je6NUbpObpQ-QF0.r_GFsgNy_qAr0H6row&epi=je6NUbpObpQ-QF0.r_GFsgNy_qAr0H6row

https://github.com/davidfowl/TcpEcho

https://blog.naver.com/oidoman/221674992672

https://habr.com/en/post/466137/

반응형
반응형
//spin lock
public void Add(T item)
{
    bool locked = false;
    _spinLock.Enter(ref locked);
    _vector.Add(item);
    _spinLock.Exit();
}

//lock
public void Add(T item)
{
    lock(this.SyncRoot)
    {
        _vector.Add(item);
    }
}

사용가능한 쓰레드를 모두 사용했을때 결과 

lock을 사용한 결과 2.310, 1.876 ms

spin lock을 사용한 결과 47.241 ms, 62.261ms

 

public class Benchmark
{
    [Benchmark]
    public void ThreadSpinLock()
    {
        Kosher.Collections.SynchronizedVector2<int> synchronizedVector = new SynchronizedVector2<int>(10000);
        Parallel.For(0, 10000, (i) =>
        {
            synchronizedVector.Add(i);
        });
    }

    [Benchmark]
    public void ThreadLock()
    {
        Kosher.Collections.SynchronizedVector<int> synchronizedVector = new SynchronizedVector<int>(10000);
        Parallel.For(0, 10000, (i) =>
        {
            synchronizedVector.Add(i);
        });
    }
}

쓰레드의 경합이 많아질수록 SpinLock의 처리속도는 늦어진다.

public class Benchmark
{
        [Benchmark]
        public void ThreadSpinLock()
        {
            Kosher.Collections.SynchronizedVector2<int> synchronizedVector = new SynchronizedVector2<int>(10000);

            var option = new ParallelOptions();
            option.MaxDegreeOfParallelism = 2;
            Parallel.For(0, 10000, option, (i) =>
            {
                synchronizedVector.Add(i);
            });
        }

        [Benchmark]
        public void ThreadLock()
        {
            var option = new ParallelOptions();
            option.MaxDegreeOfParallelism = 2;
            Kosher.Collections.SynchronizedVector<int> synchronizedVector = new SynchronizedVector<int>(10000);
            Parallel.For(0, 10000, (i) =>
            {
                synchronizedVector.Add(i);
            });
        }
    }

다만 쓰레드의 경합이 없는 경우 Thread Context Switching 이 없기에 Spin Lock의 속도가 더 잘나온다.

반응형
반응형

https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.http.httprequestrewindextensions.enablebuffering?view=aspnetcore-2.2 

 

HttpRequestRewindExtensions.EnableBuffering Method (Microsoft.AspNetCore.Http)

Ensure the requestBody can be read multiple times. Normally buffers request bodies in memory; writes requests larger than 30K bytes to disk.

docs.microsoft.com

 

더 큰 요청에 대한 임시 파일은 ASPNETCORE_TEMP환경 변수(있는 경우)에 명명된 위치에 기록됩니다. 해당 환경 변수가 정의되지 않은 경우 이러한 파일은 현재 사용자의 임시 폴더에 기록됩니다. 파일은 연결된 요청이 끝나면 자동으로 삭제됩니다.

 

해당 경로에 폴더가 생성이 안되는 경우 패킷을 받지 못하는 이슈가 발생한다.

반응형

'개발관련 > C#' 카테고리의 다른 글

System.IO.Pipelines  (0) 2023.02.08
Thread Synchronization spinlock vs lock performance  (0) 2022.12.20
소켓 비정상 종료 처리 TcpKeepAlive  (0) 2018.09.13
C++/CLI를 통한 C++ 클래스 마샬링  (0) 2018.08.11
sql compact 4.0 설정  (2) 2018.07.30
반응형

비정상 종료로 인하여 고스트 세션이 발생하였을 때 처리 방법으론 자체적으로 HeartBeat를 만들어 주기적으로 Send를 하거나


소켓 옵션을 통하여 처리하면 된다.


window 서버 2010 이상, Window 8.1 이상 지원


C# => IOControl


WinSock => WSAIoctl을 사용하면 된다.


소켓을 IOCP에 물렸을 경우 GetQueuedCompletionStatus()에서 False와 ERROR_SEM_TIMEOUT Error로 떨어짐.


C#


public class TcpKeepAlive

{

public uint OnOff { get; set; }

public uint KeepAliveTime { get; set; }

public uint KeepAliveInterval { get; set; }

public byte[] GetBytes()

{

return BitConverter.GetBytes(OnOff).Concat(BitConverter.GetBytes(KeepAliveTime)).Concat(BitConverter.GetBytes(KeepAliveInterval)).ToArray();

}

}


var option = new TcpKeepAlive

{

OnOff = 1,

KeepAliveTime = 5000,

KeepAliveInterval = 1000

};

state.Socket.IOControl(IOControlCode.KeepAliveValues, option.GetBytes(), null);


C++ 

#include <mstcpip.h>


tcp_keepalive keepAlive;

keepAlive.onoff = 1;

keepAlive.keepalivetime = 5000;

keepAlive.keepaliveinterval = 1000;

DWORD option;


WSAIoctl(stateObject->Socket(), SIO_KEEPALIVE_VALS, &keepAlive, sizeof(keepAlive), 0, 0, &option, NULL, NULL);


if (!GetQueuedCompletionStatus(_completionPort, &bytesTrans, &stateObject, (LPOVERLAPPED *)&overlapped, INFINITE))

{

int error = WSAGetLastError();

printf("Error : %d\n", error);

auto pHandler = reinterpret_cast<StateObject*>(stateObject);

switch (error)

{

case ERROR_SEM_TIMEOUT:

case ERROR_NETNAME_DELETED:

ClosePeer(pHandler);

default:

break;

}

 continue;


}

반응형
반응형


Managed C++/CLI 프로젝트 설정


공용 언어 런타임 지원




code : https://github.com/EomTaeWook/Cpp-CLI-Marshalling


Native -> Managed C++/CLI 메시지를 전파를 할시엔 OutGoingMessage를 사용


Managed C++/CLI -> C# event 로 Notify가 나감


C# -> Managed C++/CLI -> Native InCommingMessage으로 사용


Dll Loading을 통한 Runtime Class 생성 예제


Managed C++/CLI 


CppInterface.h


#pragma once

namespace Cpp

{

public interface class CppInterface

{

public:

delegate void OnNotifyHandler(System::Object^ sender, System::String^ message);

public:

void Init();

void Run();

void Stop();

void InCommingMessage(System::String^ inCommingMessage);

event OnNotifyHandler^ OnNotify;

};

}

CppInterface.cpp

#include "CppInterface.h"


Dll을 뽑기위해 Cpp 파일은 헤더 추가만


Managed C++/CLI


CppWrapper.h


#pragma once
#include "../Native/Native.h"
//C++/Cli .net 환경에서 구동되는 C++
public ref class CppWrapper : Cpp::CppInterface
{
public:
//Cpp Native 단으로 함수 포인터를 넘겨주기 위한 대리자
//OutGoingMessage(std::string message);과 매칭된다.
delegate void OnNotifyCallback(std::string message);
public:
CppWrapper();
virtual ~CppWrapper();
private:
void OutGoingMessage(std::string outGoingMessage);
private:
Native* _native;
public:
// CppInterface을(를) 통해 상속됨
virtual event Cpp::CppInterface::OnNotifyHandler ^ OnNotify;
virtual void Init();
virtual void Run();
virtual void Stop();
virtual void InCommingMessage(System::String ^inCommingMessage);

};

inline CppWrapper::CppWrapper()
{
_native = new Native();
}
inline CppWrapper::~CppWrapper()
{
delete _native;
}

CppWrapper.cpp
#include "CppWrapper.h"
#include <msclr/marshal_cppstd.h>
void CppWrapper::Run()
{
}
void CppWrapper::Stop()
{
_native->Stop();
}
void CppWrapper::InCommingMessage(System::String ^inGoingMessage)
{
std::string message = msclr::interop::marshal_as<std::string>(inGoingMessage);
_native->InCommingMessage(message);
}
//Native 단에서 넘겨받은 Message를 구독하고 있는 모든 애들한테 Notify
void CppWrapper::OutGoingMessage(std::string outGoingMessage)
{
System::String^ message = gcnew System::String(outGoingMessage.c_str());
OnNotify(this, message);
}

void CppWrapper::Init()
{
auto cb = gcnew OnNotifyCallback(this, &CppWrapper::OutGoingMessage);
System::IntPtr ptr = System::Runtime::InteropServices::Marshal::GetFunctionPointerForDelegate(cb);
_native->Init(static_cast<Native::OutGoingCallback>(ptr.ToPointer()));
}


C ++ Native Code

Native.h


#pragma once

#include <string>

//C++ Native 이며 Lib로 빌드가 되어야함.

class Native

{

public:

typedef void(__stdcall *OutGoingCallback)(std::string);

public:

Native();

~Native();

private:

//Wrapping 되는 C++/cli 함수 포인터 저장

OutGoingCallback _onNotify;

public:

void Init(OutGoingCallback onNotify);

void InCommingMessage(std::string inCommingMessage);

void Stop();

private:

void OutGoingMessage(std::string outGoingMessage);

};


Native.cpp


#include "Native.h"

#include <stdio.h>

Native::Native()

{

}

Native::~Native()

{

}

void Native::Init(OutGoingCallback onNotify)

{

_onNotify = onNotify;

OutGoingMessage("Cpp UnManaged class Init Complete");

}

void Native::OutGoingMessage(std::string outGoingMessage)

{

//printf("Cpp UnManaged class -> Managed class -> C# Outgoing Message\n");

_onNotify(outGoingMessage);

}

void Native::InCommingMessage(std::string inCommingMessage)

{

//printf("C#-> Managed class -> Cpp UnManaged class ->  inComming Message\n");

printf("Cpp Notify InCommingMessage:: %s\n", inCommingMessage.c_str());

}

void Native::Stop()

{

printf("Cpp Native Stop\n");

}


C#


Entry.cs


using System;

using System.Collections.Concurrent;

using System.Diagnostics;

using System.IO;

using System.Linq;

using System.Reflection;

using System.Threading;


namespace ProtoType

{

    public class Entry :IDisposable

    {

        private ConcurrentDictionary<string, ProcessObject> _processes;

        private bool _disposed;

        public Entry()

        {

            _processes = new ConcurrentDictionary<string, ProcessObject>();

        }

        protected void Dispose(bool isDispose)

        {

            foreach(var p in _processes)

            {

                p.Value.CancellationToken.Cancel();

            }

            _processes.Clear();

            _disposed = isDispose;

        }

        public void Dispose()

        {

            if (_disposed)

                return;

            Dispose(true);

        }

        //각 DLL로 부터 Notify가 들어오는 구간

        private void OnNotify(object sender, string message)

        {

            Console.WriteLine($"Center Notify Recive : {message}");

            //등록되어 있는 Dll 클래스에게 Notify 전달

            foreach (var item in _processes)

            {

                var method = item.Value.DllProcess.GetType().GetMethod("InCommingMessage", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);

                method?.Invoke(item.Value.DllProcess, new object[] { message });

            }

        }

        private void ProcessStart(object process)

        {

            ProcessObject state = process as ProcessObject;

            var stopMethod = state.DllProcess.GetType().GetMethod("Stop", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

            while (!state.CancellationToken.IsCancellationRequested)

            {

                try

                {

                    if (state.Started)

                        continue;


                    var initMethod = state.DllProcess.GetType().GetMethod("Init", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

                    initMethod?.Invoke(state.DllProcess, null);


                    var runMethod = state.DllProcess.GetType().GetMethod("Run", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);

                    runMethod?.Invoke(state.DllProcess, null);


                    state.Started = true;

                }

                catch(Exception ex)

                {

                    stopMethod?.Invoke(state.DllProcess, null);

                    state.Started = false;

                    Trace.WriteLine(ex.Message);

                }

                finally

                {

                    Thread.Sleep(1000);

                }

            }

            if (state.DllProcess is CS.CSInterface)

            {

                var p = state.DllProcess as CS.CSInterface;

                p.OnNotify -= OnNotify;

            }

            else if (state.DllProcess is Cpp.CppInterface)

            {

                var p = state.DllProcess as Cpp.CppInterface;

                p.OnNotify -= OnNotify;

            }

            stopMethod? .Invoke(state.DllProcess, null);

            Console.WriteLine($"Thread Close");

        }

        public void Start()

        {

            var files = Directory.GetFiles(Environment.CurrentDirectory).Where(r=>r.EndsWith("dll")).ToList();

            foreach(var file in files)

            {

                var assem = Assembly.LoadFile(file);

                var types = assem.GetExportedTypes().Where(r => r.IsClass &&

                                                                r.GetInterfaces().Any(i => i.Equals(typeof(Cpp.CppInterface))) ||

                                                                r.GetInterfaces().Any(i => i.Equals(typeof(CS.CSInterface)))

                                                                ).ToList();

                foreach (var type in types)

                {

                    var process = new ProcessObject();

                    process.CancellationToken = new CancellationTokenSource();


                    process.DllProcess = Activator.CreateInstance(type);

                    if (process.DllProcess is CS.CSInterface)

                    {

                        var p = process.DllProcess as CS.CSInterface;

                        p.OnNotify += OnNotify; //옵저버 패턴 생각하면 됨

                    }

                    else if (process.DllProcess is Cpp.CppInterface)

                    {

                        var p = process.DllProcess as Cpp.CppInterface;

                        p.OnNotify += OnNotify; //옵저버 패턴 생각하면 됨

                    }

                    _processes.TryAdd(type.Name, process);

                }

            }

            foreach (var process in _processes)

            {

                ThreadPool.QueueUserWorkItem(ProcessStart, process.Value);

            }

        }

    }

}


결과






반응형
반응형

Nuget을 통하여 System.Data.SqlServerCe를 받을 시에 


Unable to load the native components of SQL Server Compact corresponding to the ADO.NET provider of version 8080. Install the correct version of SQL Server Compact. Refer to KB article 974247 for more details.


에러 발생함.


C:\Program Files\Microsoft SQL Server Compact Edition\v4.0\Private


C:\Program Files (x86)\Microsoft SQL Server Compact Edition\v4.0\Private


경로의 


System.Data.SqlServerCe.dll 를 참조 걸고 로컬 복사


amd64 폴더 전체를 프로젝트에 넣어서 항상 복사로 설정


x86 폴더 전체를 프로젝트에 넣어서 항상 복사로 설정


폴더 포함하여 파일 내용 

sqlceca40.dll,

sqlcecompact40.dll,

sqlceer40EN.dll,

sqlceer40KO.dll,

sqlceme40.dll,

sqlceqp40.dll,

sqlcese40.dll


bin 폴더에 

System.Data.SqlServerCe.dll 와 amd64 폴더 x86 폴더가 존재하면 에러 없이 실행됨.


반응형
반응형

code : https://github.com/EomTaeWook/CS-API.Core.WebSocket


작업 중인 소스 .Net Core에 익숙해지기 위해 Core로 작성


http://blogs.microsoft.co.il/applisec/2014/03/12/signalr-message-format/


현재 웹 소켓을 사용할 프로젝트가 있는데


SignalR C++이 카사블랑카(VS2013, VS2015)에서 구동 + SignalR(C++11에서 사용하는 문법(람다식 등))이 존재


현재 클라이언트 프로젝트가 VS2010 C++단에서 돌아가는 환경이라(좀 바꾸자...) SignalR 을 사용하지 못한다.


.Net 환경의 기본 API Websocket 으로 서버를 구성해야 해야 할 판이라 SignalR의 통신 구조가 너무 맘에 드는 관계로


SignalR 의 형태로 제작하려고 한다. (서버 -> 클라이언트 함수 호출, 클라이언트 -> 서버 함수 호출)



SignalR 의 흐름


(C->S)Negotiation -> (S->C)토큰 생성. Response


(C->S)Connect Transports를 선택(sse, longpooling... 자동 선택) -> (S->C) HearBeat 생성. -> Response


(C->S)Start->(S) 인증 및 Hub 확인.


이후 Send url을 달고이후 밑의 포맷대로 송수신 하는 것 같다. (웹 소켓 제외)


소스 분석은 흐름만 파악하고 이 형태로 개발하기 위함이므로 자세히 파보지는 않았음으로 틀렸을 수도 있다.


http://localhost:8080/signalr/send?clientProtocol=1.4&transport=serverSentEvents&connectionData=[%7B%22Name%22:%22Chat%22%7D]&connectionToken=AQAAANCMnd8BFdERjHoAwE%2FCl%2BsBAAAAoZNjIPEGw02gtEuCVyuU9wAAAAACAAAAAAAQZgAAAAEAACAAAAC48hm5UjuOAIFv3EV3wVKDOrARoODHn7%2FnszwnFA%2FqowAAAAAOgAAAAAIAACAAAAB8yo7vtBUCftMubFVHydoqqFMOihURRECXFJ45iRUOaTAAAAAUnG1QclrL4bpwPsAdt4EtdubVjHEdggFY1QtxTmZWBZ2N60yHlioXbTIMvymSaktAAAAALHWD0Zm0RQkRz07dnnTgLdYhmJVTY8SeylYY3G8NEo2X4Qx93dROFU88aHg%2BJslcKNAakDTtjnHmHkw57E9YIA%3D%3D


SSE Foramt

{"C":"d-E73E9074-B,7|C,0|D,1","M":[{"H":"Chat","M":"send","A":["test"]}]}


WebSocket Foramt

Value에 {"H":"Chat","M":"send","A":["test"]}가 들어감


{

  "Source": "f11a643d-914a-4951-9c1f-ab7c1f6fb0ef",

  "Key": "h-Chat",

  "Value": [

    123,

    34,

    72,

    34,

    58,

    34,

    67,

    104,

    97,

    116,

    34,

    44,

    34,

    77,

    34,

    58,

    34,

    115,

    101,

    110,

    100,

    34,

    44,

    34,

    65,

    34,

    58,

    91,

    34,

    116,

    101,

    115,

    116,

    34,

    93,

    125

  ],

  "CommandId": null,

  "WaitForAck": false,

  "IsAck": false,

  "Filter": "",

  "Encoding": {

    "BodyName": "utf-8",

    "EncodingName": "Unicode (UTF-8)",

    "HeaderName": "utf-8",

    "WebName": "utf-8",

    "WindowsCodePage": 1200,

    "IsBrowserDisplay": true,

    "IsBrowserSave": true,

    "IsMailNewsDisplay": true,

    "IsMailNewsSave": true,

    "IsSingleByte": false,

    "EncoderFallback": {

      "DefaultString": "�",

      "MaxCharCount": 1

    },

    "DecoderFallback": {

      "DefaultString": "�",

      "MaxCharCount": 1

    },

    "IsReadOnly": true,

    "CodePage": 65001

  },

  "MappingId": 0,

  "StreamIndex": 0,

  "IsCommand": false

}




Hub Message Format

Hub Request format ("send" request)


Data={"I":index,"H":"samplingstreaminghub","M":"ConnectToStream","A":["StreamUri"]}


Hubs messages format (server to client)


– {"C":"messageId value", "M":[{"H":"HubName","M":"HandlerName","A": ["argument list as json"]}]}


– {"C":"messageId value", "G": ["groupName"],"g": ["groupName"],"T":1,

    "M":[{"H":"HubName","M":"HandlerName","A": ["argument list as json"]}]}


Hub Payloads format ("send" request or inside "M" element of Hub message)


- {"H":"HubName","M":"HandlerName","A": [argument list as json],                                    "S":{state as json},"I":index}

- {"H":"HubName","M":"HandlerName","A": [argument list as json],"I":index} 

Hub Response examples ("send" response)

- {"I":"0","R":{"return object as json}}

- {"I": 0} -> no result

- {"I": 0, "S":{"x":1},"R":1} –> result

- {"I": 0, "E":"This is an error"} –> error

- {"I": 0, "E":"This is an error", "T": "Some stack trace here"} -> error + stack

The following is a complete list of the messages elements:

Hub Messages:


C – Cursor

M – Messages

T – Timeout (only if true) value is 1

D – Disconnect (only if true) value is 1

R – All Groups (Client groups should be reset to match this list exactly)

G – Groups added

g – Groups removed



Hub payload:


I – Callback Operation index

H – Hub name

M – method name

A – arguments

S – state (if not null)



Hub Method return value:


I – Operation index

R – Result

S – State

E – Error

T – stack trace

D – Error Data

반응형

'개발관련 > C#' 카테고리의 다른 글

C++/CLI를 통한 C++ 클래스 마샬링  (0) 2018.08.11
sql compact 4.0 설정  (2) 2018.07.30
네트워크 공유 폴더 접근  (0) 2018.05.09
Winform Control 안쪽 여백 제거  (0) 2018.04.08
쿼드 트리  (0) 2018.03.28
반응형

https://code.msdn.microsoft.com/windowsapps/Connecting-to-Remote-f69b9346/sourcecode?fileId=25381&pathId=564469262


NetworkConnection.cs


using System;

using System.IO;

using System.Net;

using System.Runtime.InteropServices;


namespace Connect.Base

{

    public class NetworkConnection : IDisposable

    {

        protected string _connectString;

        protected NetworkCredential _credential;

        public NetworkConnection(string connectString, NetworkCredential credential)

        {

            _connectString = connectString;

            _credential = credential;

        }

        public void Open()

        {

            var netResource = new NetResource

            {

                Scope = ResourceScope.GlobalNetwork,

                ResourceType = ResourceType.Disk,

                DisplayType = ResourceDisplaytype.Share,

                RemoteName = _connectString

            };


            var result = WNetAddConnection2(netResource, _credential.Password, _credential.UserName, 0);


            if (result != 0)

            {

                throw new IOException("Error connecting to remote share", result);

            }

        }

        public string ConnectString { get => _connectString; }

        public void Close()

        {

            WNetCancelConnection2(_connectString, 0, true);

        }

        ~NetworkConnection()

        {

            Dispose(false);

        }

        public void Dispose()

        {

            Dispose(true);

        }

        protected void Dispose(bool disposing)

        {

            WNetCancelConnection2(_connectString, 0, true);

            GC.SuppressFinalize(this);

        }

        [DllImport("mpr.dll")]

        private static extern int WNetAddConnection2(NetResource netResource, string password, string username, int flags);

        [DllImport("mpr.dll")]

        private static extern int WNetCancelConnection2(string name, int flags, bool force);


        [StructLayout(LayoutKind.Sequential)]

        private class NetResource

        {

            public ResourceScope Scope;

            public ResourceType ResourceType;

            public ResourceDisplaytype DisplayType;

            public int Usage;

            public string LocalName;

            public string RemoteName;

            public string Comment;

            public string Provider;

        }


        private enum ResourceScope : int

        {

            Connected = 1,

            GlobalNetwork,

            Remembered,

            Recent,

            Context

        }


        private enum ResourceType : int

        {

            Any = 0,

            Disk = 1,

            Print = 2,

            Reserved = 8

        }


        private enum ResourceDisplaytype : int

        {

            Generic = 0x0,

            Domain = 0x1,

            Server = 0x2,

            Share = 0x3,

            File = 0x4,

            Group = 0x5,

            Network = 0x6,

            Root = 0x7,

            Shareadmin = 0x8,

            Directory = 0x9,

            Tree = 0xa,

            Ndscontainer = 0xb

        }

    }

}


IBase 추상 클래스


RDBMS(MSSQL, ORACLE, MYSQL)용과 파일 네트워크 연결용 클래스를 범용하게 사용하기 위해


Init, Connect, Dispose 메소드를 가짐


FileConnection.cs


using Connect.Base;

using System.Net;


namespace Connect

{

    public class FileConnection : IBase

    {

        NetworkConnection _netConnect;

        public override void Init(string connectString, string account = null, string password = null)

        {

            NetworkCredential _credential = new NetworkCredential()

            {

                UserName = account,

                Password = password

            };

            _netConnect = new NetworkConnection(connectString, _credential);

        }

        public string ConnectString { get => _netConnect.ConnectString; }

        public override IBase Connect()

        {

            _netConnect.Open();

            return this;

        }

        public override void Dispose()

        {

            _netConnect.Close();

            _netConnect.Dispose();

        }

    }

}


사용예제


_targetContext.Init(... , ...);


using (var conn = _targetContext.Connect())

{

   var filePath = Directory.GetFiles((connect as FileConnection).ConnectString)              

}




반응형

'개발관련 > C#' 카테고리의 다른 글

sql compact 4.0 설정  (2) 2018.07.30
SignalR MessageFormat 및 구동 시퀸스  (0) 2018.07.14
Winform Control 안쪽 여백 제거  (0) 2018.04.08
쿼드 트리  (0) 2018.03.28
DLL 동적 로딩  (0) 2017.12.18
반응형

namespace UI

{

    class BorderGroupBox : GroupBox

    {

        private Color _borderColor = Color.Black;

        private Rectangle _displayRectangle;

        protected override void OnResize(EventArgs e)

        {

            base.OnResize(e);

            _displayRectangle = base.ClientRectangle;

            _displayRectangle.Y = this.Padding.Top;

            _displayRectangle.X = this.Padding.Left;

            _displayRectangle.Height -= this.Padding.Top;

            _displayRectangle.Width -= this.Padding.Left;

            this.PerformLayout();

        }

        protected override void OnPaint(PaintEventArgs e)

        {

            Size tSize = TextRenderer.MeasureText(this.Text, this.Font);

            Rectangle borderRect = e.ClipRectangle;

            borderRect.Y = (borderRect.Y + (tSize.Height / 2));

            borderRect.Height = (borderRect.Height - (tSize.Height / 2));

            ControlPaint.DrawBorder(e.Graphics, borderRect, this._borderColor, ButtonBorderStyle.Solid);

            Rectangle textRect = e.ClipRectangle;

            textRect.X = (textRect.X + 6);

            textRect.Width = tSize.Width;

            textRect.Height = tSize.Height;

            e.Graphics.FillRectangle(new SolidBrush(this.BackColor), textRect);

            e.Graphics.DrawString(this.Text, this.Font, new SolidBrush(this.ForeColor), textRect);

        }

        public override Rectangle DisplayRectangle

        {

            get => this._displayRectangle;

        }

        [Browsable(true), EditorBrowsable(EditorBrowsableState.Always)]

        public Color BorderColor

        {

            get { return this._borderColor; }

            set { this._borderColor = value; }

        }

    }

}



반응형

'개발관련 > C#' 카테고리의 다른 글

SignalR MessageFormat 및 구동 시퀸스  (0) 2018.07.14
네트워크 공유 폴더 접근  (0) 2018.05.09
쿼드 트리  (0) 2018.03.28
DLL 동적 로딩  (0) 2017.12.18
Dll 리소스 포함해서 exe 배포  (0) 2017.11.10

+ Recent posts