> Dictionary는 기본적으로 키 값이 고유해야 합니다.

C#의 Dictionary는 기본적으로 키 값이 고유해야 하며, 동일한 키를 추가하려고 하면 ArgumentException이 발생합니다. 
키 중복이 발생할 수 있는 시나리오에서 이를 처리하는 방법은 상황에 따라 다릅니다. 
몇 가지 일반적인 해결 방법은 다음과 같습니다:

1. Dictionary의 값에 중복 키 데이터를 저장하기
키 중복을 허용해야 한다면, 값을 컬렉션(예: List 또는 HashSet)으로 변경하여 하나의 키에 여러 값을 저장할 수 있습니다.

2. Lookup 사용
System.Linq의 Lookup 클래스는 중복 키를 자연스럽게 처리할 수 있는 기능을 제공합니다.

3. 커스텀 자료구조로 구현
Dictionary 대신 키 중복을 처리하기 위한 커스텀 자료구조를 사용할 수도 있습니다.

4. 키를 고유하게 변경
중복 키를 허용하지 않는 Dictionary를 사용해야 하는 경우, 키를 고유하게 만들 방법을 고안할 수 있습니다. 
예를 들어, 키에 고유 식별자를 추가하거나 조합하는 방식입니다.

5. 데이터 설계를 검토
만약 중복 키가 자주 발생한다면 데이터 설계의 문제일 수 있습니다. 
데이터를 재구조화하거나 다른 컬렉션 타입(예: List, IGrouping)을 사용하는 것이 더 적합할 수 있습니다.

결론
위 방법들 중 선택은 사용 사례에 따라 다릅니다:

키에 여러 값을 저장해야 한다면 **List를 값으로 사용하거나 Lookup**을 사용하세요.
키를 고유하게 유지하려면 고유 식별자를 추가하세요.
대량의 중복 처리가 빈번하다면 데이터 설계를 다시 고려해보는 것이 좋습니다.

(keys):[아스플로, KR7159010008, 159010, KSQ+KR7159010008]
(RegisterMultiKeyDictionary):[01037/아스플로                                /KR7159010008/159010  ]
(keys):[제로투세븐, KR7159580000, 159580, KSQ+KR7159580000]
(RegisterMultiKeyDictionary):[01038/제로투세븐                              /KR7159580000/159580  ]
(keys):[스킨앤스킨, KR7159910009, 159910, KSQ+KR7159910009]
(RegisterMultiKeyDictionary):[01039/스킨앤스킨                              /KR7159910009/159910  ]
(keys):[NEW, KR7160550000, 160550, KSQ+KR7160550000]
(RegisterMultiKeyDictionary):[01040/NEW                                     /KR7160550000/160550  ]
(keys):[이큐셀, KR7160600003, 160600, KSQ+KR7160600003]
(RegisterMultiKeyDictionary):[01041/이큐셀                                  /KR7160600003/160600  ]
(keys):[싸이맥스, KR7160980009, 160980, KSQ+KR7160980009]
(RegisterMultiKeyDictionary):[01042/싸이맥스                                /KR7160980009/160980  ]
(keys):[THE MIDONG, KR7161570007, 161570, KSQ+KR7161570007]
(RegisterMultiKeyDictionary):[01043/THE MIDONG                              /KR7161570007/161570  ]
(keys):[필옵틱스, KR7161580006, 161580, KSQ+KR7161580006]
(RegisterMultiKeyDictionary):[01044/필옵틱스                                /KR7161580006/161580  ]
(keys):[신스틸, KR7162300008, 162300, KSQ+KR7162300008]
(RegisterMultiKeyDictionary):[01045/신스틸                                  /KR7162300008/162300  ]
(keys):[핑거, KR7163730005, 163730, KSQ+KR7163730005]
(RegisterMultiKeyDictionary):[01046/핑거                                    /KR7163730005/163730  ]
(keys):[이루다, KR7164060006, 164060, KSQ+KR7164060006]
(RegisterMultiKeyDictionary):[01047/이루다                                  /KR7164060006/164060  ]
(keys):[하나머티리얼즈, KR7166090001, 166090, KSQ+KR7166090001]
(RegisterMultiKeyDictionary):[01048/하나머티리얼즈                          /KR7166090001/166090  ]
(keys):[코아스템켐온, KR7166480004, 166480, KSQ+KR7166480004]
(RegisterMultiKeyDictionary):[01049/코아스템켐온                            /KR7166480004/166480  ]

CodePagesEncodingProvider 사용 예제

CodePagesEncodingProvider를 사용하는 방법은 다음과 같습니다. 이 방법은 EUC-KR을 포함한 다양한 코드 페이지를 지원합니다.

  • EUC-KR 인코딩: C#에서는 Encoding.GetEncoding("euc-kr")를 통해 EUC-KR 인코딩을 사용할 수 있습니다. 그러나, euc-kr이 지원되지 않는 환경에서는 다른 방법을 사용해야 할 수 있습니다. 이 경우, CodePagesEncodingProvider를 추가하여 EUC-KR을 지원할 수 있습니다.
  • 버퍼 크기: UTF-8 인코딩은 가변 길이 문자 인코딩이므로, 변환 후 버퍼의 크기를 적절히 관리해야 합니다.
using System;
using System.Text;

class Program
{
    static void Main()
    {
        // EUC-KR 인코딩 제공자를 등록
        Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

        // 예제 EUC-KR 인코딩된 바이트 데이터
        byte[] eucKrBytes = new byte[] { /* EUC-KR로 인코딩된 바이트 데이터 */ };

        // EUC-KR 인코딩으로 바이트 배열을 문자열로 변환
        Encoding eucKrEncoding = Encoding.GetEncoding("euc-kr");
        string eucKrString = eucKrEncoding.GetString(eucKrBytes);

        // 문자열을 UTF-8 인코딩으로 변환
        Encoding utf8Encoding = Encoding.UTF8;
        byte[] utf8Bytes = utf8Encoding.GetBytes(eucKrString);

        // UTF-8 바이트 배열을 문자열로 변환
        string utf8String = utf8Encoding.GetString(utf8Bytes);

        // 결과 출력
        Console.WriteLine("EUC-KR String: " + eucKrString);
        Console.WriteLine("UTF-8 String: " + utf8String);
    }
}




KRX.TCP.DATA - EUC-KR로 전달되어짐
c#이 서버역할을 할떄에 EUC-KR필드만 따로 분리해서 처리해야 한다.(예, 한글종목코드)

GetInstance를 활용한 List<string> 예제,ㅡㅡㅡ,ㅡㅡㅡ
GetInstance를 활용한 List<string> 예제,ㅡㅡㅡ,ㅡㅡㅡ

public List<string> redisSenderQueueNM

using System;
using System.Collections.Generic;

public class APMMemory
{
    private static APMMemory apmMemory;
    private List<string> redissenderqueuenm = new List<string>();

    public List<string> redisSenderQueueNM
    {
        get { return redissenderqueuenm; }
        set { redissenderqueuenm = value; }
    }

    private APMMemory() { }

    public static APMMemory GetInstance
    {
        get
        {
            if (apmMemory == null)
                apmMemory = new APMMemory();
            return apmMemory;
        }
    }

    // 문자열을 추가할 때 중복을 체크하는 메서드
    public void AddQueueName(string queueName)
    {
        if (!redissenderqueuenm.Contains(queueName))
        {
            redissenderqueuenm.Add(queueName);
            Console.WriteLine($"Added: {queueName}");
        }
        else
        {
            Console.WriteLine($"Queue name '{queueName}' already exists and was not added.");
        }
    }
}

public class Program
{
    public static void Main(string[] args)
    {
        // Singleton instance 가져오기
        APMMemory apmMemory = APMMemory.GetInstance;

        // 중복되지 않게 문자열을 리스트에 추가
        apmMemory.AddQueueName("Queue1");
        apmMemory.AddQueueName("Queue2");
        apmMemory.AddQueueName("Queue3");

        // 중복되는 문자열 추가 시도
        apmMemory.AddQueueName("Queue2");  // 이미 존재하는 문자열
        apmMemory.AddQueueName("Queue4");

        // redisSenderQueueNM 리스트 출력
        Console.WriteLine("\nFinal Redis Sender Queue Names:");
        foreach (string queueName in apmMemory.redisSenderQueueNM)
        {
            Console.WriteLine(queueName);
        }
    }
}


dotnet new wpf -n sampleNM

error CS0246: 'Newtonsoft' 형식또는 네임스페이스 이름을 찾을 수 없습니다. using 지시문 또는 어셈블리 참조가 있는지 확인하세요.
>dotnet add package Newtonsoft.Json

error CS0246: 'ServiceStack' 형식 또는 네임스페이스 이름을 찾을 수 없습니다. using 지시문 또는 어셈블리 참조가 있는지 확인하세요.
>dotnet add package ServiceStack

error CS0246: 'RedisClient' 형식 또는 네임스페이스 이름을 찾을 수 없습니다. using 지시문 또는 어셈블리 참조가 있는지 확인하세요.
>dotnet add package ServiceStack.Redis

KeyValuePair<TKey, TValue>는 C#에서 제공하는 구조체(struct)로, 키와 값을 쌍으로 묶어서 관리할 수 있게 해줍니다. 주로 컬렉션에서 특정 항목의 키와 값을 함께 다룰 때 사용됩니다.

주요 특징

  1. 제네릭 타입: KeyValuePair는 제네릭 타입으로, 키와 값의 데이터 타입을 지정할 수 있습니다. 예를 들어, KeyValuePair<int, string>는 정수형 키와 문자열 값을 가지는 쌍을 의미합니다.
  2. 구성: KeyValuePair는 두 개의 읽기 전용 속성, Key와 Value,으로 구성됩니다. Key는 쌍의 키를, Value는 쌍의 값을 나타냅니다.
  3. 불변성: KeyValuePair의 키와 값은 읽기 전용 속성으로 정의되어 있으며, 한 번 생성된 후에는 변경할 수 없습니다.

사용 예제

다음은 KeyValuePair를 사용하는 몇 가지 예제입니다.

1. 기본 사용

// KeyValuePair 생성 KeyValuePair<int, string> kvp = new KeyValuePair<int, string>(1, "Value"); // 키와 값에 접근 Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}");

2. Dictionary에서 사용

Dictionary 클래스는 내부적으로 KeyValuePair를 사용하여 키와 값을 저장합니다.

Dictionary<string, int> dictionary = new Dictionary<string, int>(); // 키와 값을 추가 dictionary.Add("Apple", 1); dictionary.Add("Banana", 2); // Dictionary의 모든 항목을 반복 foreach (KeyValuePair<string, int> kvp in dictionary) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); }

3. LINQ와 함께 사용

LINQ를 사용할 때 KeyValuePair를 반환하는 메서드를 사용할 수 있습니다.

var list = new List<KeyValuePair<string, int>>() { new KeyValuePair<string, int>("Apple", 1), new KeyValuePair<string, int>("Banana", 2), }; var result = list.Where(kvp => kvp.Value > 1); foreach (var kvp in result) { Console.WriteLine($"Key: {kvp.Key}, Value: {kvp.Value}"); }

이와 같이 KeyValuePair는 다양한 상황에서 유용하게 사용될 수 있으며, 특히 컬렉션과 관련된 작업에서 많이 활용됩니다.




1. 

포트 21111은 로컬 호스트(127.0.0.1)에서만 수신 대기 상태입니다. 
이는 이 포트가 현재 시스템 내의 로컬 네트워크 인터페이스에서만 접근 가능하다는 것을 의미합니다. 
외부 네트워크에서 이 포트에 접근할 수 없습니다.
root@sinfo:~# netstat -an | grep 21111
tcp        0      0 127.0.0.1:21111         0.0.0.0:*               LISTEN


2. 

포트 21111은 모든 네트워크 인터페이스(0.0.0.0)와 로컬 IPv6 주소(::1)에서 수신 대기 상태입니다. 
이는 포트 21111 가 외부 네트워크와 로컬 네트워크 모두에서 접근 가능하다는 것을 의미합니다. 
root@sinfo:~# netstat -an | grep 21111
tcp        1      0 0.0.0.0:21111           0.0.0.0:*               LISTEN
tcp6       0      0 :::21111                :::*                    LISTEN
root@sinfo:~#

1번과 2번의 소스차이는(?)

#if(false)
        private void Init()
        {
            //소켓 객체 생성 (TCP 소켓)
            listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            //엔드포인트에 소켓 바인드(Bind)
            IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 21111);
            listener.Bind(localEndPoint);

            //Listen소켓의 대기열의 길이를 설정합니다.
            listener.Listen(10);

            PublicApiNM.SendToDebug(3, "()(Init)(Listen)");

            while (true)
            {
                try
                {
                    Socket handler = listener.Accept();

                    PublicApiNM.SendToDebug(3, "()(Init)(Accept)");

                    //richTextBox1.Text = richTextBox1.Text + "Socket handler = listener.Accept() OK" + "\n";

                    Thread RealSocModel = new Thread(new ParameterizedThreadStart(SocketRunHandler));
                    RealSocModel.Start(handler);
                }
                catch (Exception ex)
                {
                    //
                }
            }
            listener.Close();
        }
#endif
#if(true)
private void Init()
{
    // IPv4와 IPv6 소켓 객체 생성 (TCP 소켓)
    ipv4Listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    ipv6Listener = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);

    // IPv4와 IPv6 엔드포인트 설정
    IPEndPoint ipv4EndPoint = new IPEndPoint(IPAddress.Any, 21111); // 모든 IPv4 주소에서 수신 대기
    IPEndPoint ipv6EndPoint = new IPEndPoint(IPAddress.IPv6Any, 21111); // 모든 IPv6 주소에서 수신 대기

    // 소켓을 엔드포인트에 바인드
    ipv4Listener.Bind(ipv4EndPoint);
    ipv6Listener.Bind(ipv6EndPoint);

    // 소켓의 대기열 길이 설정
    ipv4Listener.Listen(10);
    ipv6Listener.Listen(10);

    PublicApiNM.SendToDebug(3, "()(Init)(Listen)");

    while (!_shouldStop)
    {
try
{
    // 소켓이 닫혔는지 확인하고, 닫혔으면 루프 종료
    if (ipv4Listener.Poll(1000, SelectMode.SelectRead) && ipv4Listener.Available == 0)
    {
break;
    }

    if (ipv6Listener.Poll(1000, SelectMode.SelectRead) && ipv6Listener.Available == 0)
    {
break;
    }

    // 클라이언트 연결 수신 대기
    Socket ipv4Handler = ipv4Listener.Accept();
    Socket ipv6Handler = ipv6Listener.Accept();

    PublicApiNM.SendToDebug(3, "()(Init)(Accept)");

    // 새 스레드에서 클라이언트 처리
    Thread ipv4Thread = new Thread(new ParameterizedThreadStart(SocketRunHandler));
    ipv4Thread.Start(ipv4Handler);

    Thread ipv6Thread = new Thread(new ParameterizedThreadStart(SocketRunHandler));
    ipv6Thread.Start(ipv6Handler);
}
catch (SocketException ex)
{
    if (_shouldStop)
    {
// _shouldStop이 true일 경우, listener가 닫히면서 예외 발생할 수 있음
break;
    }

    // 그 외의 소켓 예외 처리
    PublicApiNM.SendToDebug(3, "()(Init)(Exception):" + ex.Message);
}
catch (Exception ex)
{
    // 기타 예외 처리
    PublicApiNM.SendToDebug(3, "()(Init)(Exception):" + ex.Message);
}
    }

    // 소켓 닫기
    ipv4Listener.Close();
    ipv6Listener.Close();
    PublicApiNM.SendToDebug(3, "()(Init)(Stopped)");
}

public void StopInitThread()
{
    _shouldStop = true;

    // 소켓을 닫아 블로킹 호출에서 예외를 발생시키도록 함
    if (ipv4Listener != null)
    {
ipv4Listener.Close();
    }
    if (ipv6Listener != null)
    {
ipv6Listener.Close();
    }
}
#endif

 

콘솔프로그램을 작성시에 쓰레드를 발생시키고 종료를 막기위해서 반복문을 사용하는것 외에는
방법이 없는것 같았지만, 아래와 같은 방법을 쓰면 될듯함/

 

using System;
using System.Threading;

class Program
{
    // ManualResetEvent 객체 생성
    private static ManualResetEvent manualResetEvent = new ManualResetEvent(false);

    static void Main()
    {
        Console.WriteLine("Main 스레드 시작");

        // 새로운 스레드 시작
        Thread thread = new Thread(WorkerThread);
        thread.Start();

        // ManualResetEvent를 통해 메인 스레드를 대기시킴
        manualResetEvent.WaitOne();

        Console.WriteLine("Main 스레드 종료");
    }

    static void WorkerThread()
    {
        Console.WriteLine("Worker 스레드 시작");

        // 일부 작업을 수행
        Thread.Sleep(3000); // 예: 3초 동안 작업을 수행한다고 가정

        Console.WriteLine("Worker 스레드 종료");

        // 작업이 완료되면 ManualResetEvent를 설정하여 Main 스레드를 깨움
        manualResetEvent.Set();
    }
}

정보분배수신/SQLite DB저장(file)/(동기식)HttpListener Interface/
정보분배수신/SQLite DB저장(file)/(동기식)HttpListener Interface/
정보분배수신/SQLite DB저장(file)/(동기식)HttpListener Interface/

비고)
키워드를 사용하지 않고 HTTP 서버를 구현하는 방법을 보여드리겠습니다. 
이 경우, HttpListener를 사용하여 비동기 방식이 아닌 동기 방식으로 요청을 처리합니다. 
동기 방식은 코드가 요청을 처리하는 동안 다른 작업을 수행할 수 없으므로 서버의 응답 속도가 느려질 수 있습니다.


NuGet으로 Package를 설치시에, 최신버젼으로 빌드하시면 됩니다.
./packages/Newtonsoft.Json.13.0.3/lib/net45/Newtonsoft.Json.dll
./packages/Stub.Systehttp://m.Data.SQLite.Core.NetFramework.1.0.118.0/build/net45/x64/SQLite.Interop.dll
./packages/Stub.Systehttp://m.Data.SQLite.Core.NetFramework.1.0.118.0/lib/net45/System.Data.SQLite.dll

1. 전체도식을 그려본다

 


- HTTP 인터페이스를 위한 8080포트 Listen
xterm@DESKTOP-VVAIF4D MINGW64 ~/Downloads/tmp/exture_3_0_ticker/mdiwebrowser

$ netstat -an | grep 8080
  TCP    0.0.0.0:8080           0.0.0.0:0              LISTENING
  TCP    [::]:8080              [::]:0                 LISTENING
  TCP    [::1]:8080             [::1]:50463            CLOSE_WAIT
  TCP    [::1]:8080             [::1]:50493            ESTABLISHED
  TCP    [::1]:8080             [::1]:50496            ESTABLISHED
  TCP    [::1]:50463            [::1]:8080             FIN_WAIT_2
  TCP    [::1]:50493            [::1]:8080             ESTABLISHED
  TCP    [::1]:50496            [::1]:8080             ESTABLISHED

- 정보분배 TCP데이타를 받기위한 21111포트 Listen
xterm@DESKTOP-VVAIF4D MINGW64 ~/Downloads/tmp/exture_3_0_ticker/mdiwebrowser
$ netstat -an | grep 2111
  TCP    127.0.0.1:21111        0.0.0.0:0              LISTENING


2. 거래소정보분배 TCP데이타 수신및 저장(SQLite)

3. SQLite Browser에서 나타내봅니다.(상용)

3. 거래소정보분배 TCP데이타 인터페이스(HTTP통신) - 동기식(한번에 하나의 resquest를 처리한다.)

3.1 - Text 표시
3.2 - Json 표시

+ Recent posts