mongodb collection을 protobuf class로 가져오려고 하던 중에 List 형태를 가져와보려고 많은 시간을 소비해 가며 보던 중에 .proto로 gen할 때 자세히 보니 repeated 값들이 readonly로 생성 되는 것을 보았다.


여기 저기 검색하던 중에 protobuf를 본 외국인의 글을 발견했다.(질문자는 다른 방법이 필요했던 거였지만 아래 더 자세한 설명을 해준 답변자의 답변이 눈에 들어왔다)

링크 : https://stackoverflow.com/questions/16617933/protobuf-net-generated-class-from-proto-is-repeated-field-supposed-to-be-re


This was a new issue for us as well after updating our proto-net executable and related files. It was new behavior we hadn't experienced before.

After a little digging in csharp.xslt, we found the definition for 'repeated' fields:

<xsl:template match="FieldDescriptorProto[label='LABEL_REPEATED']">
    <xsl:variable name="type"><xsl:apply-templates select="." mode="type"/></xsl:variable>
    <xsl:variable name="format"><xsl:apply-templates select="." mode="format"/></xsl:variable>
    <xsl:variable name="field"><xsl:apply-templates select="." mode="field"/></xsl:variable>
    private <xsl:if test="not($optionXml)">readonly</xsl:if> global::System.Collections.Generic.List&lt;<xsl:value-of select="$type" />&gt; <xsl:value-of select="$field"/> = new global::System.Collections.Generic.List&lt;<xsl:value-of select="$type"/>&gt;();
    [<xsl:apply-templates select="." mode="checkDeprecated"/>global::ProtoBuf.ProtoMember(<xsl:value-of select="number"/>, Name=@"<xsl:value-of select="name"/>", DataFormat = global::ProtoBuf.DataFormat.<xsl:value-of select="$format"/><xsl:if test="options/packed='true'">, Options = global::ProtoBuf.MemberSerializationOptions.Packed</xsl:if>)]<!--
    --><xsl:if test="$optionDataContract">
    [global::System.Runtime.Serialization.DataMember(Name=@"<xsl:value-of select="name"/>", Order = <xsl:value-of select="number"/>, IsRequired = false)]
    </xsl:if><xsl:if test="$optionXml">
    [global::System.Xml.Serialization.XmlElement(@"<xsl:value-of select="name"/>", Order = <xsl:value-of select="number"/>)]
    </xsl:if>
    public global::System.Collections.Generic.List&lt;<xsl:value-of select="$type" />&gt; <xsl:call-template name="pascal"/>
    {
      get { return <xsl:value-of select="$field"/>; }<!--
      --><xsl:if test="$optionXml">
      set { <xsl:value-of select="$field"/> = value; }</xsl:if>
    }
  </xsl:template>

I've pulled out the specific parts for the private field and the setter:

private <xsl:if test="not($optionXml)">readonly</xsl:if> ...snip...

public ...snip...
{
  ...snip... 
  <!----><xsl:if test="$optionXml">
  set { <xsl:value-of select="$field"/> = value; }
  </xsl:if>
}

Notice the suspect conditions above for $optionXml. If you just remove those, the field is no longer readonly and the setter is properly generated.

So it then becomes: private ...snip...

public ...snip...
{
  ...snip... 
  set { <xsl:value-of select="$field"/> = value; }
}

Full 'fixed' template:

  <xsl:template match="FieldDescriptorProto[label='LABEL_REPEATED']">
    <xsl:variable name="type"><xsl:apply-templates select="." mode="type"/></xsl:variable>
    <xsl:variable name="format"><xsl:apply-templates select="." mode="format"/></xsl:variable>
    <xsl:variable name="field"><xsl:apply-templates select="." mode="field"/></xsl:variable>
    private global::System.Collections.Generic.List&lt;<xsl:value-of select="$type" />&gt; <xsl:value-of select="$field"/> = new global::System.Collections.Generic.List&lt;<xsl:value-of select="$type"/>&gt;();
    [<xsl:apply-templates select="." mode="checkDeprecated"/>global::ProtoBuf.ProtoMember(<xsl:value-of select="number"/>, Name=@"<xsl:value-of select="name"/>", DataFormat = global::ProtoBuf.DataFormat.<xsl:value-of select="$format"/><xsl:if test="options/packed='true'">, Options = global::ProtoBuf.MemberSerializationOptions.Packed</xsl:if>)]<!--
    --><xsl:if test="$optionDataContract">
    [global::System.Runtime.Serialization.DataMember(Name=@"<xsl:value-of select="name"/>", Order = <xsl:value-of select="number"/>, IsRequired = false)]
    </xsl:if><xsl:if test="$optionXml">
    [global::System.Xml.Serialization.XmlElement(@"<xsl:value-of select="name"/>", Order = <xsl:value-of select="number"/>)]
    </xsl:if>
    public global::System.Collections.Generic.List&lt;<xsl:value-of select="$type" />&gt; <xsl:call-template name="pascal"/>
    {
      get { return <xsl:value-of select="$field"/>; }
      set { <xsl:value-of select="$field"/> = value; }
    }
  </xsl:template>

I played with setting optionXml to false, but it didn't work and you may still want that option enabled anyway.


위와 마지막 fixed template을 보고 protogen.exe 폴더에 있는 csharp.xslt의 내용을 수정하면 될 것이다.

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

HttpListener를 사용하여 개발 중에 서버포트로 연결이 불가하다는 메시지로 Exception이 떨어지는 현상이 발생하여 찾아본 명령어


명령 프롬프트(관리자 권한)를 열고 아래 명령어에 포트만 변경하여 실행하고 실행해보자


netsh http add urlacl url=http://*:8888/ user=Everyone listen=yes 


아래 참고 블로그를 링크 걸어놓는다.


참고 : http://www.windowcmc.com/q.php?q=netsh-http-kr-manual

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

출처 : https://nabacg.wordpress.com/2013/05/13/gc-background-vs-concurrent-mode/


번역이며 오역이 있을 수 있습니다.


저는 최근 .NET에서 Garbage Collection에 대해 많은 것을 읽었습니다. 프로젝트 중 하나에서 메모리 누출을 연구하고 점점 더 많은 MSDN 기사를 읽었습니다. 놀랍게도 지난 번에 주제에 대해 괴롭힘을당한 이후로이 지역에 새로운 개발이 많이있었습니다. 그래서 다른 사람들이 유용하다고 생각할 때 나중에 참조 할 수 있도록 그들을 요약 해 보겠습니다. 이 MSDN 기사 에서 CLR의 현재 GC 상태에 대한 개요를 찾을 수 있으므로 자유롭게 시작할 수 있습니다.

워크 스테이션 대 서버

실제로 두 가지 모드 가비지 콜렉션이 있습니다.이 모드는 구성 파일의 런타임 구성 부분에서 gcServer 태그를 사용하여 제어 할 수 있습니다. 워크 스테이션 가비지 콜렉션은 기본적으로 gcServer = "true"로 설정하더라도 단일 프로세서 시스템에서 항상 사용됩니다.

1
2
4
5
<configuration>
   <runtime>
      <gcServer enabled="true|false"/>
   </runtime>
</configuration>

그 모드는 실제로 꽤 오래되었습니다. gcServer 태그는 .NET 2.0에 도입되었으며 따라서 CLR의 두 번째 버전입니다. 주요 차이점은 서버 모드에서 GC를 수행하는 스레드가 1 개 이상 있고 모두 THREAD_PRIORITY_HIGHEST 우선 순위 수준 에서 실행 된다는 것 입니다. GC 전용 스레드와 각 CPU에 대한 별도의 힙 (일반 및 대형 오브젝트 모두)이 있으며 이들 모두는 동시에 수집됩니다. 여기서 우선 순위가 높은 여러 스레드를 사용하여 최대한 빨리 컬렉션을 만들려고하지만 모든 사용자 스레드가 준비 될 때까지 일시 중지해야한다는 것을 의미합니다. 일반적으로 응답 속도보다 처리량이 높은 서버 응용 프로그램에 더 적합합니다. 이는 일반적인 데스크톱 응용 프로그램의 경우가 아닙니다. 서버 모드는 리소스를 많이 소비 할 수도 있습니다.

동시 vs 백그라운드
서버 vs 워크 스테이션 외에도 동시 및 백그라운드 작업 모드가 있습니다. 둘 다 사용하면 모든 사용자 스레드를 일시 중지하지 않고 전용 스레드가 2 세대를 수집 할 수 있습니다. 0 세대와 1 세대는 모든 사용자 스레드를 일시 중지해야하지만 항상 빠릅니다. 이는 물론 애플리케이션이 제공 할 수있는 대응 수준을 높입니다. 실제 모드는 다양하며 서버 대 워크 스테이션 유형의 GC에 따라 달라질 수 있습니다. 다음 섹션에서 자세히 설명합니다. 조금 혼란 스러울 수 있으므로 아래에서 가능한 모든 조합을 찾을 수 있습니다.

워크 스테이션 가비지 수집은 다음과 같습니다.

  • 병발 사정
  • 배경
  • 비 동시성

서버 가비지 수집 옵션은 다음과 같습니다.

  • 배경
  • 비 동시성

동시 모드
Workstation GC의 기본 모드이며 다중 프로세서 시스템에서도 GC를 수행하는 전용 스레드를 제공합니다. gcConcurrent 태그 를 사용하여 끌 수 있습니다 .

1
2
4
5
<configuration>
   <runtime>
      <gcConcurrent enabled="true|false"/>
   </runtime>
</configuration>

이 모드는 병행 GC 동안 제한된 할당 기능을 희생시켜 가장 많은 시간을 소비하는 Gen2 수집이 동시에 수행되므로 사용자 스레드 일시 중지가 훨씬 짧아집니다. Gen2 수집이 진행되는 동안 다른 스레드는 새로운 임시 메모리 세그먼트를 할당 할 수 없기 때문에 현재 임시 세그먼트의 한도까지만 할당 할 수 있습니다. 현재 세그먼트에서 프로세스가 제대로 실행되지 않으면 모든 스레드를 일시 중지하고 동시 수집이 완료 될 때까지 기다려야합니다. 동시 GC가 아직 진행 중일 때 Gen0 및 Gen1 컬렉션을 수행 할 수 없기 때문입니다. 또한 동시 GC에는 약간 더 높은 메모리 요구 사항이 있습니다.

백그라운드 모드 .NET 4.0
에는 새로운 백그라운드 모드가 도입되었습니다.이 모드는 동시 모드와 비슷한 개념을 가지고 있지만, 동시 모드를 대체해야하기 때문에 기본적으로 설정되어 있습니다. Concurrent는 Workstation에서만 사용할 수있는 반면 Workstation 및 Server 모드에서도 사용할 수 있습니다. 배경 모드가 실제로 Gen2 및 Gen1 컬렉션을 수행하면서 동시에 Gen2를 수행 할 수 있다는 큰 개선점이 있습니다. 이제 gen0, gen1 모음을 전경 모음이라고합니다. 사용자 스레드가 실행 중일 때 별도의 스레드에 의해 2 세대 콜렉션 만 수행되고, 포 그라운드 콜렉션은 모든 사용자 스레드를 일시 중지해야합니다. 또한 전경 컬렉션에는 배경 컬렉션에서 일시 중지가 필요하며, 그래서 그들은 다양한 안전 포인트를 통해 서로 상호 작용합니다. 백그라운드 컬렉션은 서버 GC에서 사용할 수 있으며 .NET 4.5로 시작하는 기본 모드입니다. 서버와 워크 스테이션 백그라운드 모드의 주요 차이점은 백그라운드 GC를 수행하는 스레드의 수입니다. 워크 스테이션에서는 항상 단일 스레드이고 서버 GC에서는 CPU 당 전용 스레드가 있습니다.

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

변수명 찾기 

http://stackoverflow.com/questions/9801624/get-name-of-a-variable-or-parameter

변수명 찾기, 변수 값 가져오기

https://blogs.msdn.microsoft.com/csharpfaq/2010/03/11/how-can-i-get-objects-and-property-values-from-expression-trees/

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

출처 : https://msdn.microsoft.com/ko-kr/library/bb384067.aspx

 

where(제네릭 형식 제약 조건)(C# 참조)

제네릭 형식 정의에서 where 절은 제네릭 선언에 정의된 형식 매개 변수의 인수로 사용할 수 있는 형식에 대해 제약 조건을 지정하는 데 사용됩니다. 예를 들어, 다음과 같이 형식 매개 변수 TIComparable<T> 인터페이스를 구현하도록 제네릭 클래스 MyGenericClass를 선언할 수 있습니다.
public class MyGenericClass<T> where T:IComparable { }
System_CAPS_note참고

쿼리 식의 where 절에 대한 자세한 내용은 where 절(C# 참조)을 참조하십시오.

where 절에는 인터페이스 제약 조건 외에 기본 클래스 제약 조건도 포함될 수 있습니다. 이 제약 조건은 지정된 클래스를 기본 클래스로 갖거나 지정된 클래스 자체인 형식만 해당 제네릭 형식의 형식 인수로 사용할 수 있도록 제한합니다. 이러한 제약 조건을 사용할 경우에는 해당 형식 매개 변수에 대한 다른 모든 제약 조건 앞에 사용해야 합니다.  

class MyClass<T, U>
    where T : class
    where U : struct
{ }

where 절에는 생성자 제약 조건도 포함될 수 있습니다. new 연산자를 사용하여 형식 매개 변수의 인스턴스를 만들 수 있지만, 이렇게 하려면 형식 매개 변수를 생성자 제약 조건 new()로 제한해야 합니다. new() 제약 조건을 사용하면 컴파일러에서는 제공된 모든 형식 인수가 액세스 가능하고 매개 변수 없는(또는 기본) 생성자를 가져야 한다는 것을 알 수 있습니다. 예를 들면 다음과 같습니다.

public class MyGenericClass<T> where T : IComparable, new()
{
    // The following line is not possible without new() constraint:
    T item = new T();
}

new() 제약 조건은 where 절의 마지막에 나와 있습니다.

형식 매개 변수가 여러 개이면 다음 예제와 같이 where 절을 각 형식 매개 변수마다 하나씩 사용합니다.

interface IMyInterface
{
}

class Dictionary<TKey, TVal>
    where TKey : IComparable, IEnumerable
    where TVal : IMyInterface
{
    public void Add(TKey key, TVal val)
    {
    }
}
다음과 같이 제네릭 메서드의 형식 매개 변수에 제약 조건을 연결할 수도 있습니다.

public bool MyMethod<T>(T t) where T : IMyInterface { }

 

 

대리자에 대한 형식 매개 변수 제약 조건을 설명하기 위한 구문은 메서드에 사용하는 구문과 동일합니다.

delegate T MyDelegate<T>() where T : new()

제네릭 대리자에 대한 자세한 내용은 제네릭 대리자를 참조하십시오.

제약 조건의 구문 및 사용 방법에 대한 자세한 내용은 형식 매개 변수에 대한 제약 조건을 참조하십시오.

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

SslStream 클래스

https://msdn.microsoft.com/ko-kr/library/system.net.security.sslstream(v=vs.110).aspx

참고하기(채팅) : http://it-jerryfamily.tistory.com/entry/Program-C-SslStream-%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%ED%86%B5%EC%8B%A0-%EB%B0%A9%EB%B2%95


샘플 코드를 Mono를 이용해서 Debian에 올릴 일이 있어서 테스를 진행하다 아래와 같은 오류를 해결하려고 애쓰고 있었습니다..

System.IO.IOException: The authentication or decryption has failed. ---> Mono.Security.Protocol.Tls.TlsException: Server certificate Private Key unavailable. 


SSL 파일도 바꿔보고

암호도 바꿔보고 발급자, 발급 대상에 특수문자가 있어서 테스트 인증서를 재발급 해보고...

구글을 한참 두지다가 발견한 것을 공유합니다. 


너무 간단해서....

민망하지만 일단 인증서 발급부터 쭉 진행하겠습니다.


1. 인증서 발급

 makecert -r -pe -n "CN=test" -b 01/01/2016 -e 01/01/2030 -sky exchange -ss my

!! makecert.exe 파일의 경로는 C:\Program Files (x86)\Windows Kits\10\bin\x64 입니다.

전 윈도우 10이라 C:\Program Files (x86)\Windows Kits\10폴더에 있는 녀석으로 했습니다.

윈도우 버전 별로 폴더가 있습니다. 윈도우 10은 8.0, 8.1, 10 이렇게 있네요


2. 인증서 등록

시작 - 실행 - certmgr(윈도우 10은 검색에 "사용자 인증서 관리")

아래의 경로에 인증서 발급 내역이 있습니다. Test 인증서가 보입니다.


인증서를 선택하고 더블클릭으로 정보를 볼 수 있습니다.

일반 탭과 인증 경로 탭에 빨간 상자를 보시면 인증서가 신뢰를 얻지 못했다고 나옵니다.


올바른 인증서를 만들기 위해서 아래를 따라 진행하시면 됩니다.

"test"로 등록된 인증서를 선택하고 오른쪽 마우스로 Drag ->신뢰할 수 있는 루트 인증 기관 - 인증서에 놓기 - 여기에 복사


경고창이 뜹니다.

확인으로 복사를 해줍니다.


완료되었다면 아래와 같은 인증서 정보를 확인 가능합니다.

이제 인증서는 완료되었습니다.


2. 인증서 파일로 저장하기

위에서 인증서를 만들어 보았습니다.

이제 인증서를 파일로 만들어야 합니다.

자! 시작하죠

인증서를 선택하고 오른쪽 마우스 클릭! -> 모든 작업 -> 내보내기


인증서 내보내기 마법사

순서대로 진행합니다.

암호는 필수로 입력하고 기억하세요!


내보내기가 완료되었습니다.


3. 소스

이제 소스 작성을 해봅시다.

윈도우에서 테스트를 해보실 분은 솔루션을 만들고 프로젝트를 두개 만듭니다.


SslServer.cs

 using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Net.Security;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;


namespace Examples.System.Net
{
    public sealed class SslTcpServer
    {
        static X509Certificate2 serverCertificate = null;
        // The certificate parameter specifies the name of the file
        // containing the machine certificate.
        public static void RunServer(string certificate)
        {
            serverCertificate = new X509Certificate2(certificate, "password");
            // Create a TCP/IP (IPv4) socket and listen for incoming connections.
            IPAddress ipAddress = IPAddress.Parse("0.0.0.0");
            IPEndPoint ipLocalEndPoint = new IPEndPoint(ipAddress, 8888);

            TcpListener listener = new TcpListener(ipLocalEndPoint);

            listener.Start();
            while (true)
            {
                Console.WriteLine("Waiting for a client to connect...");
                // Application blocks while waiting for an incoming connection.
                // Type CNTL-C to terminate the server.
                TcpClient client = listener.AcceptTcpClient();
                ProcessClient(client);
            }
        }
        static void ProcessClient(TcpClient client)
        {
            // A client has connected. Create the
            // SslStream using the client's network stream.
            SslStream sslStream = new SslStream(
                client.GetStream(), false);
            // Authenticate the server but don't require the client to authenticate.
            try
            {
                sslStream.AuthenticateAsServer(serverCertificate,
                    false, SslProtocols.Tls, true);
                // Display the properties and settings for the authenticated stream.
                DisplaySecurityLevel(sslStream);
                DisplaySecurityServices(sslStream);
                DisplayCertificateInformation(sslStream);
                DisplayStreamProperties(sslStream);

                // Set timeouts for the read and write to 5 seconds.
                sslStream.ReadTimeout = 5000;
                sslStream.WriteTimeout = 5000;
                // Read a message from the client.  
                Console.WriteLine("Waiting for client message...");
                string messageData = ReadMessage(sslStream);
                Console.WriteLine("Received: {0}", messageData);

                // Write a message to the client.
                byte[] message = Encoding.UTF8.GetBytes("Hello from the server.<EOF>");
                Console.WriteLine("Sending hello message.");
                sslStream.Write(message);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine("Exception: {0}", e.Message);
                if (e.InnerException != null)
                {
                    Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
                }
                Console.WriteLine("Authentication failed - closing the connection.");
                sslStream.Close();
                client.Close();
                return;
            }
            finally
            {
                // The client stream will be closed with the sslStream
                // because we specified this behavior when creating
                // the sslStream.
                sslStream.Close();
                client.Close();
            }
        }
        static string ReadMessage(SslStream sslStream)
        {
            // Read the  message sent by the client.
            // The client signals the end of the message using the
            // "<EOF>" marker.
            byte[] buffer = new byte[2048];
            StringBuilder messageData = new StringBuilder();
            int bytes = -1;
            do
            {
                // Read the client's test message.
                bytes = sslStream.Read(buffer, 0, buffer.Length);

                // Use Decoder class to convert from bytes to UTF8
                // in case a character spans two buffers.
                Decoder decoder = Encoding.UTF8.GetDecoder();
                char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
                decoder.GetChars(buffer, 0, bytes, chars, 0);
                messageData.Append(chars);
                // Check for EOF or an empty message.
                if (messageData.ToString().IndexOf("<EOF>") != -1)
                {
                    break;
                }
            } while (bytes != 0);

            return messageData.ToString();
        }
        static void DisplaySecurityLevel(SslStream stream)
        {
            Console.WriteLine("Cipher: {0} strength {1}", stream.CipherAlgorithm, stream.CipherStrength);
            Console.WriteLine("Hash: {0} strength {1}", stream.HashAlgorithm, stream.HashStrength);
            Console.WriteLine("Key exchange: {0} strength {1}", stream.KeyExchangeAlgorithm, stream.KeyExchangeStrength);
            Console.WriteLine("Protocol: {0}", stream.SslProtocol);
        }
        static void DisplaySecurityServices(SslStream stream)
        {
            Console.WriteLine("Is authenticated: {0} as server? {1}", stream.IsAuthenticated, stream.IsServer);
            Console.WriteLine("IsSigned: {0}", stream.IsSigned);
            Console.WriteLine("Is Encrypted: {0}", stream.IsEncrypted);
        }
        static void DisplayStreamProperties(SslStream stream)
        {
            Console.WriteLine("Can read: {0}, write {1}", stream.CanRead, stream.CanWrite);
            Console.WriteLine("Can timeout: {0}", stream.CanTimeout);
        }
        static void DisplayCertificateInformation(SslStream stream)
        {
            Console.WriteLine("Certificate revocation list checked: {0}", stream.CheckCertRevocationStatus);

            X509Certificate localCertificate = stream.LocalCertificate;
            if (stream.LocalCertificate != null)
            {
                Console.WriteLine("Local cert was issued to {0} and is valid from {1} until {2}.",
                    localCertificate.Subject,
                    localCertificate.GetEffectiveDateString(),
                    localCertificate.GetExpirationDateString());
            }
            else
            {
                Console.WriteLine("Local certificate is null.");
            }
            // Display the properties of the client's certificate.
            X509Certificate remoteCertificate = stream.RemoteCertificate;
            if (stream.RemoteCertificate != null)
            {
                Console.WriteLine("Remote cert was issued to {0} and is valid from {1} until {2}.",
                    remoteCertificate.Subject,
                    remoteCertificate.GetEffectiveDateString(),
                    remoteCertificate.GetExpirationDateString());
            }
            else
            {
                Console.WriteLine("Remote certificate is null.");
            }
        }
        private static void DisplayUsage()
        {
            Console.WriteLine("To start the server specify:");
            Console.WriteLine("serverSync certificateFile.cer");
            Environment.Exit(1);
        }
        public static int Main(string[] args)
        {
            string certificate = null;
            if (args == null || args.Length < 1)
            {
                DisplayUsage();
            }
            certificate = args[0];
            //certificate = @"Test.pfx";   //윈도우에서 테스트로 진행할 때 사용함 위에 args 파라미터 체크는 주석
            SslTcpServer.RunServer(certificate);
            return 0;
        }
    }
}


SslClient.cs

 using System;
using System.Collections;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.IO;

namespace Examples.System.Net
{
    public class SslTcpClient
    {
        private static Hashtable certificateErrors = new Hashtable();

        // The following method is invoked by the RemoteCertificateValidationDelegate.
        public static bool ValidateServerCertificate(
              object sender,
              X509Certificate certificate,
              X509Chain chain,
              SslPolicyErrors sslPolicyErrors)
        {
            if (sslPolicyErrors == SslPolicyErrors.None)
                return true;

            Console.WriteLine("Certificate error: {0}", sslPolicyErrors);

            // Do not allow this client to communicate with unauthenticated servers.
            return false;
        }
        public static void RunClient(string machineName, string serverName)
        {
            // Create a TCP/IP client socket.
            // machineName is the host running the server application.
            TcpClient client = new TcpClient(machineName, 8888);
            Console.WriteLine("Client connected.");
            // Create an SSL stream that will close the client's stream.
            SslStream sslStream = new SslStream(
                client.GetStream(),
                false,
                new RemoteCertificateValidationCallback(ValidateServerCertificate),
                null
                );
            // The server name must match the name on the server certificate.
            try
            {
                sslStream.AuthenticateAsClient(serverName);
            }
            catch (AuthenticationException e)
            {
                Console.WriteLine("Exception: {0}", e.Message);
                if (e.InnerException != null)
                {
                    Console.WriteLine("Inner exception: {0}", e.InnerException.Message);
                }
                Console.WriteLine("Authentication failed - closing the connection.");
                client.Close();
                return;
            }
            // Encode a test message into a byte array.
            // Signal the end of the message using the "<EOF>".
            byte[] messsage = Encoding.UTF8.GetBytes("Hello from the client.<EOF>");
            // Send hello message to the server.
            sslStream.Write(messsage);
            sslStream.Flush();
            // Read message from the server.
            string serverMessage = ReadMessage(sslStream);
            Console.WriteLine("Server says: {0}", serverMessage);
            // Close the client connection.
            client.Close();
            Console.WriteLine("Client closed.");
        }
        static string ReadMessage(SslStream sslStream)
        {
            // Read the  message sent by the server.
            // The end of the message is signaled using the
            // "<EOF>" marker.
            byte[] buffer = new byte[2048];
            StringBuilder messageData = new StringBuilder();
            int bytes = -1;
            do
            {
                bytes = sslStream.Read(buffer, 0, buffer.Length);

                // Use Decoder class to convert from bytes to UTF8
                // in case a character spans two buffers.
                Decoder decoder = Encoding.UTF8.GetDecoder();
                char[] chars = new char[decoder.GetCharCount(buffer, 0, bytes)];
                decoder.GetChars(buffer, 0, bytes, chars, 0);
                messageData.Append(chars);
                // Check for EOF.
                if (messageData.ToString().IndexOf("<EOF>") != -1)
                {
                    break;
                }
            } while (bytes != 0);

            return messageData.ToString();
        }
        private static void DisplayUsage()
        {
            Console.WriteLine("To start the client specify:");
            Console.WriteLine("clientSync machineName [serverName]");
            Environment.Exit(1);
        }
        public static int Main(string[] args)
        {
            string serverCertificateName = null;
            string machineName = null;
            if (args == null || args.Length < 1)
            {
                DisplayUsage();
            }
            // User can specify the machine name and server name.
            // Server name must match the name on the server's certificate. 
            machineName = args[0];

            if (args.Length < 2)
            {
                serverCertificateName = machineName;
            }
            else
            {
                serverCertificateName = args[1];
            }
            //serverCertificateName = "Test";
            //machineName = "192.168.0.20";
            SslTcpClient.RunClient(machineName, serverCertificateName);
            return 0;
        }
    }
}


4. 리눅스에 소스 반영 및 빌드 후 실행

소스가 준비가 되었다면 리눅스 사이트에 접속합니다.


FTP로 파일을 올려봅니다.

FileZilla를 다운 받아 서버에 접속 후 적당한 경로에 파일을 올립니다. (꼭 FileZilla가 아니어도 됩니다.)


Putty나 기타 SSH툴로 접속합니다.

실행을 위해 빌드 해보겠습니다.


SslServer.cs

mcs SslServer.cs


SslClient.cs

mcs SslClient.cs 


Putty를 두개 띄워줍니다.

한쪽에는 서버 한쪽에는 클라이언트를 실행합니다.


Server

 mono SslServer.exe Test.pfx


Client

 mono SslClient.exe


서버에 클라이언트가 잘 붙고 Log가 잘 찍히나요?

제대로 안 되신다면 댓글 달아주시면 알려드리겠습니다.


참! 여기에서 포인트는 화이트페이퍼에 나온 코드로는 정상 작동이 안됩니다.

해결 방법은 X509Certificate X509Certificate2로 변경하시면 됩니다!!!




블로그 이미지

레몬도리 LemonDory

개발자의 이야기

출처 : http://tattoocoder.azurewebsites.net/building-vnext-web-api-using-mvc-6-mongodb-azure/

GIT : https://github.com/spboyer/mongomvc

 

This is a quick walkthrough on using ASP.NET 5 to build a Web API layer using MongoDB. The overall concept is not too dissimilar from previous examples you may have seen using X type of database, however there are some areas covered that are either new in MVC 6 that you may find you didn't know are there.

Topics Covered

  • ConfigurationModel
    • config.json
    • Environment Variables
  • OptionsModel
  • Dependency Injection (IServiceCollection)
  • Forcing JSON or Removing XML Formatter, Content Negotiation
  • MongoDB
    • Installation (OSX & Windows)
    • mongocsharpdriver (nuget package)
  • Testing endpoints w/ Postman or Fiddler

Project Creation

Depending on your environment (Windows or OS X) or preference you have a choice on how you can create the ASP.NET project.

Visual Studio 2015 Preview

Launch Visual Studio 2015 Preview and File > New Project > Web > ASP.NET 5 Empty.
New Project Dialog

ASP.NET Yeoman Project Generator

(see walkthrough here) - note that the MVC Project is the best choice for this example, there is not an "Empty" template.

$ yo aspnet

     _-----_
    |       |    .--------------------------.
    |--(o)--|    |      Welcome to the      |
   `---------´   |   marvellous ASP.NET 5   |
    ( _´U`_ )    |        generator!        |
    /___A___\    '--------------------------'
     |  ~  |     
   __'.___.'__   
 ´   `  |° ´ Y ` 

? What type of application do you want to create? MVC Application
? What's the name of your ASP.NET application? (MvcApplication) mongomvc

Setting up the dependencies

Open the project.json file and add the following to the dependencies node.

"dependencies": {  
        "Microsoft.AspNet.Server.IIS":"1.0.0-beta1",
        "Microsoft.AspNet.Mvc": "6.0.0-beta1",
        "Microsoft.AspNet.Hosting": "1.0.0-beta1",
        "Microsoft.AspNet.Diagnostics": "1.0.0-beta1",
        "Microsoft.AspNet.Server.WebListener": "1.0.0-beta1",
        "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-beta1",
        "Kestrel": "1.0.0-beta1",
        "mongocsharpdriver": "1.8.3"
    }
  • Microsoft.AspNet.Server.IIS : Server Implemenation for ASP.NET 5
  • Microsoft.AspNet.Hosting : Core hosting infrastructure for ASP.NET 5
  • Microsoft.AspNet.Diagnostics : Middleware for error pages, diagnostics etc.
  • Microsoft.AspNet.Server.WebListener : Self host web server
  • Microsoft.AspNet.Mvc : ASP.NET MVC Framework
  • Microsoft.Framework.ConfigurationModel.Json : JSON configuration source for the ConfigurationModel
  • Kestrel : Server for hosting on OS X, Linux
  • mongocsharpdriver : MongoDB Driver

Each of these should be self explanatory with the exception of maybe the ConfigurationModel.Json package, which will be covered when we get into the settings portion of the walkthrough.

Once you save the file, in Visual Studio, the packages will be restored from nuget. If you are using a non Visual Studio editor, then execute kpm restore from the command line to get the packages.

Startup.cs

Startup.cs is the entry point for your application; this is where the configuration, services, dependency injectection etc. is configured.

In the following snippet, the ConfigureServices method is used to add our services into the pipleline and expose the IServiceCollection for dependeny injection. It also sets the Configure method to tell the app which services in the pipeline to use on start.

public class Startup  
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMvc();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseMvc();
        app.UseWelcomePage();
    }
}

At this point you can F5 (VS2015), k web (Win) or k kestrel (Linux or OSX) to run the app and see the Welcome Page.

Welcome Page

Let's add a few items to this to round out the bootstrapping of the application.

Remove XML Formatters - if you have done any ASP.NET Web API development in the past, the format of the output is dependant on the Content-Type header that is passed when a GET request is made. By default the response type will be JSON, however if the client send application/xml that is what will be returned. You can force JSON results for all returns by adding the following to the ConfigureServices() method:

 services.Configure<MvcOptions>(options =>
                                     options
                                     .OutputFormatters
                                     .RemoveAll(formatter => formatter.Instance is XmlDataContractSerializerOutputFormatter)
                                           );

If you wish to control this at a more finite level, please see Content Negotiation in MVC 5 or How Can I Just Write JSON

Configuration

Gone are the days of web.config, XML transforms for deploying to environments. Now, as with many things in ASP.NET "vNext, you get more choice.

There are multiple file formats supported; XML, JSON, and INI are all supported out of the box. The other option is Environment variables, a characteristic of node.js as well as other frameworks.

Here is an example of how you can use the new ConfigurationModel to take advantage of the new confuration files.

config.json  
{
    "key" : "value"
}

config.ini  
key = value

config.xml  
<key>  
    <value>value</value>
</key>

Environment variables in Azure
Azure Env Variables

public void Configure(IApplicationBuilder app)  
    {
        // Setup configuration sources
        var configuration = new Configuration();
        configuration.AddJsonFile(“config.json”);
        configuration.AddIniFile(“config.ini”);
        configuration.AddXmlFile(“config.xml”);
        configuration.AddEnvironmentVariables();

        // …
    }

Accessing the values

var configuration = new Configuration();  
configuration.AddJsonFile("config.json);  
configuration.Get("key");  

A couple notes concerning configuration:

  • order matters, last one wins
  • not just a key/value pair. Complex structures supported

So how does this apply to this project? Let's add it to the Startup.cs.

Add the following to constructor

public Startup()  
{
    Configuration = new Configuration()
        .AddJsonFile("config.json")
        .AddEnvironmentVariables();
}

Next, add the IConfiguration interface so we can take advantage of DI

public IConfiguration Configuration { get; set; }  

Another new item we have is the the IOptions interface which allows us to bind the configuration file to a class. This is available in the Microsoft.Framework.OptionsModel namespace. Given that, let's create a new class call Settings.cs to hold the values from the config.json file we'll be creating shortly.

public class Settings  
{
    public string Database { get; set; }
    public string MongoConnection { get; set; }
}

now create the config.json file

{
    "mongoconnection": "mongodb://localhost:27017",
    "database":  "mongomvc"
}

Now, add the code to the ConfigureServices() method

First, change the services.AddMvc() to include our configuration

services.addMvc(Configuration)  

now add this line to configure the settings to be configured and bind based on the configuration

services.Configure<Settings>(Configuration);  

Now that the application is bootstrapped for Configuration, Dependency Injection, JSON formatting; let's get the datastore setup in MongoDB and then we'll come back to round out the app with the Controller, Model and DataRepository.

MongoDB

There are a few reasons I chose MongoDB for this example.

  • its a popular NoSQL datastore
  • has a nuget package for C#
  • supported on Azure
  • equal tooling support on OSX and Windows.

Installation

Installation is a breeze. If you are on OSX you can use HomeBrew to install by executing $ brew install mongodb, and if you are using Windows I would highly suggest using Chocolately to do the installation. Chocolately installs MongoDB, the shell and the Windows service and is equivalent to the OSX install with HomeBrew.

> choco install mongodb

Tools

If you prefer GUI interfaces, the best one I have found is RoboMongo. It is cross platform and provides a great way to explore your collections. You can also connect to you local stores as well as remote stores such as MongoLab(Azure provider).

Test Data

Once you have installed MongoDB, open terminal and connect to the database by the following command

$ mongo mongomvc

mongo starts the terminal command, and the second paramter connects or creates the store. Next copy and paste the test data into the terminal and hit enter.

db.speakers.insert({  
    first: 'Shayne',
    last: 'Boyer',
    twitter: '@spboyer',
    title: 'Developer Guy',
    blog: 'tattoocoder.com'
});

db.speakers.insert({  
    first: 'Scott',
    last: 'Hanselman',
    twitter: '@shanselman',
    title: 'Teacher Blogger Guy',
    blog: 'hanselman.com'
});

db.speakers.insert({  
    first: 'John',
    last: 'Papa',
    twitter: '@john_papa',
    title: 'JavaScript Angular Guy',
    blog: 'johnpapa.net'
});

db.speakers.insert({  
    first: 'Mads',
    last: 'Kristensen',
    twitter: '@mkristensen',
    title: 'Web Essentials Guy',
    blog: 'about.me/madskristensen'
});

db.speakers.insert({  
    first: 'Damian',
    last: 'Edwards',
    twitter: '@DamianEdwards',
    title: 'ASP.NET Demo Guy',
    blog: 'damianedwards.wordpress.com'
});

db.speakers.insert({  
    first: 'Jon',
    last: 'Galloway',
    twitter: '@jongalloway',
    title: 'ASP.NET Community Guy',
    blog: 'weblogs.asp.net/jongalloway'
});

This creates all of the data in the mongomvc datastore under the speakers collection. You can see the data by querying in terminal or viewing in RoboMongo.

$ db.speakers.find()

RoboMongo - OSX

Adding the Model

Add the Models folder under the root, and a Speaker.cs file.

using the yeoman generator generator-aspnet you can add a class with

$ yo aspnet:Class Speaker

    public class Speaker
    {
        public ObjectId Id { get; set; }

        [BsonElement("first")]
        public string First { get; set; }

        [BsonElement("last")]
        public string Last { get; set; }

        [BsonElement("twitter")]
        public string Twitter { get; set; }

        [BsonElement("title")]
        public string Title { get; set; }

        [BsonElement("blog")]
        public string Blog { get; set; }

    }

A few things to point out in this class is the attribute annotations

[BsonElement("first")]

These are neccessary for the binding because the mongo nuget package binding serialization is case sensitive.

Adding the Repository

Add the SpeakerRepository.cs class, in this case just under the root.

using the yeoman generator generator-aspnet you can add a class with

$ yo aspnet:Class SpeakerRepository

The interface is simple

public interface ISpeakerRespository  
{
    IEnumerable<Speaker> AllSpeakers();

    Speaker GetById(ObjectId id);

    void Add(Speaker speaker);

    void Update(Speaker speaker);

    bool Remove(ObjectId id);
}

And implement the interface. In the constructor, we'll take advantage of DI and get the Settings class that contains the connection information for the MongoDB instance. This is accessed from IOptions

public SpeakerRepository(IOptions<Settings> settings)  
{
    _settings = settings.Options;
    _database = Connect();
}

Here is the complete class implementation.

public class SpeakerRepository : ISpeakerRespository  
{
  private readonly Settings _settings;
  private readonly MongoDatabase _database;

  public SpeakerRepository(IOptions<Settings> settings)
  {
    _settings = settings.Options;
    _database = Connect();
  }

  public void Add(Speaker speaker)
  {
      _database.GetCollection<Speaker>("speakers").Save(speaker);
  }

  public IEnumerable<Speaker> AllSpeakers()
  {
    var speakers = _database.GetCollection<Speaker>("speakers").FindAll();
    return speakers;
  }

  public Speaker GetById(ObjectId id)
  {
    var query = Query<Speaker>.EQ(e => e.Id, id);
    var speaker = _database.GetCollection<Speaker>("speakers").FindOne(query);

    return speaker;
  }

  public bool Remove(ObjectId id)
  {
    var query = Query<Speaker>.EQ(e => e.Id, id);
    var result = _database.GetCollection<Speaker>("speakers").Remove(query);

    return GetById(id) == null;
  }

  public void Update(Speaker speaker)
  {
    var query = Query<Speaker>.EQ(e => e.Id, speaker.Id);
    var update = Update<Speaker>.Replace(speaker); // update modifiers
    _database.GetCollection<Speaker>("speakers").Update(query, update);
  }

  private MongoDatabase Connect()
  {
    var client = new MongoClient(_settings.MongoConnection);
    var server = client.GetServer();
    var database = server.GetDatabase(_settings.Database);

    return database;
  }
}
Add the SpeakerRepository to ServiceCollection

Open Startup.cs and add the following to the ConfigureServices() method to setup the ISpeakerRepository for DI so it can be injected into the SpeakerController.

services.AddSingleton<ISpeakerRespository, SpeakerRepository>();  

options on Dependency Injection

  • .AddTransient - whenever I ask for it give me a new one
  • .AddLifetime - scoped to the current request, regardless of how many times it's asked for
  • .AddSingleton - one ever

Adding the Controller

Add the Controllers folder under the root, and the add a MVC Controller named SpeakerController.cs

using the yeoman generator generator-aspnet you can add a MVC Controller with

$ yo aspnet:MvcController SpeakerController

In the constructor, add the ISpeakerRepository so we have access to the datastore calls.

readonly ISpeakerRespository _speakerRepository;  
public SpeakerController(ISpeakerRespository speakerRepository)  
{
    _speakerRepository = speakerRepository;
}

Add a default GET to return all of the speakers

[HttpGet]
public IEnumerable<Speaker> GetAll()  
{
    var speakers = _speakerRepository.AllSpeakers();
    return speakers;
}

Add a GET to return a specific speaker. In this case the ObjectId string, the default key from MongoDB, is used. As such as route constraint is added to validate the length of the string being passed. {id:length(24)} and a RouteName is added so we can call this by route by name using Url.RouteUrl().

[HttpGet("{id:length(24)}", Name = "GetByIdRoute")]
public IActionResult GetById(string id)  
{
    var item = _speakerRepository.GetById(new ObjectId(id));
    if (item == null)
    {
        return HttpNotFound();
    }

    return new ObjectResult(item);
}

Add a Create method, with a [FromBody] attribute on the argument to indicate that the object should be serialized from the body of the message.

[HttpPost]
public void CreateSpeaker([FromBody] Speaker speaker)  
{
  if (!ModelState.IsValid)
  {
      Context.Response.StatusCode = 400;
  }
  else
  {
      _speakerRepository.Add(speaker);

      string url = Url.RouteUrl("GetByIdRoute", new { id = speaker.Id.ToString() }, Request.Scheme, Request.Host.ToUriComponent());
      Context.Response.StatusCode = 201;
      Context.Response.Headers["Location"] = url;
  }
}

Finally, the Delete to round out the repository functions.

[HttpDelete("{id:length(24)}")]
public IActionResult DeleteSpeaker(string id)  
{
  if (_speakerRepository.Remove(new ObjectId(id)))
  {
      return new HttpStatusCodeResult(204); // 204 No Content
  }
  else
  {
      return HttpNotFound();
  }
}

Testing

Some Various tools are available for testing HTTP services. Fiddler from Telerik is a favorite if you are on Windows, I also like the Postman Chrome Add-in. I happen to like either, Postman saves the urls you hit which is nice when are testing over and over or complex endpoints.

So, if you run the application now you can use the tool of your choice and hit the endpoint:

postman GET

Select a specifid "Id" and add it the url, i.e.

http://localhost:5004/api/speaker/54b7d51140c10266ffa3b04d and a single item is returned.

{
    "Id": "54b7d51140c10266ffa3b04d",
    "First": "Shayne",
    "Last": "Boyer",
    "Twitter": "@spboyer",
    "Title": "Developer Guy",
    "Blog": "tattoocoder.com"
}

to test a POST or the Create endpoint, change the drop down to POST, select the RAW option and add the following data.

Also add the Content-Type: application/json header.

{
    "First": "Sayed",
    "Last": "Hashimi",
    "Twitter": "@sayedihashimi",
    "Title": "MSBuild Guy",
    "Blog": "sedodream.com"
}

Postman POST

After executing the POST you can either query the /speaker endpoint or look directly in the datastore and see the new speaker inserted.

RoboMongo Windows

Full source for this example is available on GitHub -> http://github.com/spboyer/mongomvc

Resources

C# and .NET MongoDB Driver - (docs.mongodb.org)

Content Negotiation in MVC 5 or How Can I Just Write JSON - (blogs.msdn.com)

ASP.NET Yeoman Generator Project Scaffolding - (tattoocoder.com)

ASP.NET - (asp.net)

ASP.NET Github - (github.com/aspnet/home)

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

출처 : http://bulkdisk.tistory.com/78

public delegate void ShowButtonDelegate(bool IsShow);
public void ShowButtonCaller(bool IsShow)
{
    if (canvasButtonGroup.Dispatcher.CheckAccess())
        ShowButtonBody(IsShow);
    else
        Dispatcher.Invoke(new ShowButtonDelegate(ShowButtonBody), IsShow);
}
private void ShowButtonBody(bool IsShow)
{
    DebugWrite("ShowButtonBody =" + IsShow.ToString());
}
------------------------------------
윈폼 ----------------------------
this.Invoke(new MethodInvoker(delegate()
{
    mnuConnectServer.Enabled = true;
    mnuStartServer.Enabled = true;
}));
------------------------------------
WPF ----------------------------
MainUI.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, 
new DispatcherOperationCallback(delegate {     MainUI.ResetGame(user.m_nID);     return null
}), null);  


------------------------------------
Invoke : 동기
BeginInvoke : 비동기  

------------------------------------

 

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

http://www.gamasutra.com/blogs/WendelinReich/20131109/203841/C_Memory_Management_for_Unity_Developers_part_1_of_3.php

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

C/C++를 사용하면서 포인터 때문에 괴로워 해본 적이 있는가? 그렇다면 C#에 관심을 가져보는 것이 좋다. C#은 다음과 같은 특징들을 제공하기 때문이다.

 

- 메모리 해제에 신경 쓰지 않아도 된다.
- 이미 삭제된 메모리에 접근하는 실수를 방지해준다.
- 잘못된 캐스팅으로 엉뚱한 메모리에 접근하지 않게 한다.
- 배열 크기보다 큰 메모리에 접근하지 못한다.
- 메모리 단편화에 대해 신경 쓰지 않아도 된다.

 

 

편한 C#, 마구잡이로 사용하면 낭패
골치 아픈 메모리 관리를 신경 쓰지 않아도 된다는 점은 사용자들에게 무척 편리하게 다가온다. 하지만 C#에서도 메모리를 다루기 위해서는 세심한 주의가 필요하다. 마음 놓고 개발하다 당황했던 과거 필자의 경험을 살펴보도록 하자.

개발 초창기, 게임 플레이 중에 주기적으로 랙이 발생했다. 로직을 확인해 봤지만 특별히 로딩 중이거나 초기화된 부분이 없어 의아했다. 유니티 엔진에서 제공하는 프로파일러로 한 프레임에 걸리는 시간을 측정해봤다. 측정 결과, System.GC.Collect() 호출에 오랜 시간이 걸린다는 점이 발견됐다. 플레이 중에 프레임마다 소모되는 시간을 그래프로 보여주는 <그림 1>을 보면 System.GC.Collect() 호출 시 그래프가 크게 튀어 오른 모습이 확인된다. C#에서 사용하지 않는 메모리를 정리하면서 가비지 컬렉션(Garbage collection) 랙이 발생한 것이다.

 

<그림 1> 프로파일러 이미지

 

이때는 가비지 컬렉션이 동작하는 횟수를 줄여서 랙 발생을 줄이면 된다. 가비지 발생을 줄이면 가비지 컬렉션이 호출되는 시간을 늦출 수 있어 동작 횟수가 줄어든다. 가비지란 프로그램이 실행되면서 어디에서든 더 이상 참조되지 않는 메모리를 의미하므로 가능한 한 메모리를 할당했다 금방 버려지는 상황을 만들지 않는 것이 좋다. 몇 가지 사례들을 살펴보자.

‘+’ operator를 통한 문자열 조합
C#은 문자열 조합이 쉽다. <리스트 1>에 보이는 것처럼 ‘+’로 연결하면 간단히 문자열 조합이 이뤄진다. 모든 객체가 ToString()을 지원하기 때문에 문자열끼리만 조합되는 게 아니라 int, float 등의 값도 알아서 문자열로 변환·조합된다.

 

<리스트 1> ‘+’로 연결한 문자열 조합

class Names
{
public string[] name = new string[100];
public void Print()
{
for (int index = 0; index < name.Length; index++)
{
string output = "[" + index + "]" + name;
Console.WriteLine(output);
}
}
}

 

문제는 <리스트 1>에서 가비지가 많이 발생한다는 점이다. ‘+’ 연산자로 두 값을 연결할 때마다 새로운 string 인스턴스가 생성된다. 연이어 ‘+’ 연산자가 나오기 때문에 다시금 새로운 string 인스턴스가 생성되고, 이전에 만들어진 string 인스턴스는 가비지가 된다. string 조합을 위해 ‘+’ operator 호출이 많아질수록 많은 가비지가 만들어지는 것이다.

그래서 문자열을 조합하는 동안 새로운 객체를 생성하지 않는 System.Text.StringBuilder 객체를 소개한다. ‘+’ operator가 아닌 Append() 메소드를 통해 문자열을 추가하며, string 객체를 만들어내는 게 아니라 이미 잡아놓은 메모리 공간에 문자열만 복사해 뒀다가 한번에 ToString()으로 string 객체를 생성해낸다.

 

<리스트 2> System.Text.StringBuilder 객체 사용

class NewNames
{
public string[] name = new string[100];
private StringBuilder sb = new StringBuilder();

public void Print()
{
sb.Clear(); // sb.Length = 0;
for (int index = 0; index < name.Length; index++)
{
sb.Append("[");
sb.Append(index);
sb.Append("] ");
sb.Append(name);
sb.AppendLine();
}
Console.WriteLine(sb.ToString());
}
}

과다한 Append() 메소드 호출이 필요해 ‘+’ 코드보다 깔끔하지 못하다고 생각된다면 AppendFormat()을 사용하는 것도 좋다.

<리스트 3> AppendFormat() 활용

class NewNames
{
public string[] name = new string[100];
private StringBuilder sb = new StringBuilder();

public void Print()
{
sb.Clear(); // sb.Length = 0;
for (int index = 0; index < name.Length; index++)
{
sb.AppendFormat("[{0}] {1}", index, name.ToString());
}
Console.WriteLine(sb.ToString());
}
}

string처럼 Immutable pattern을 사용한 객체들의 값에 접근할 때는 기존 메모리를 수정하지 않고 새로운 메모리를 만들어 반환하거나 입력받으므로 사용 시 주의가 필요하다.

 

메소드 안에서 생성한 객체
C#은 C++과 달리 클래스를 인스턴싱하려면 반드시 new를 해줘야 한다. 이때 heap에서 메모리 할당이 일어난다.

<리스트 4>와 같이 메소드 안에서 new로 생성된 인스턴스는 메소드를 빠져나오면 더 이상 사용하지 않게 돼 가비지로 처리된다. 이런 패턴의 메소드가 자주 호출될수록 가비지도 많이 발생한다.

 

<리스트 4> new로 생성된 인스턴스

public class MyVector
{
public float x, y;
public MyVector(float x, float y) { this.x = x; this.y = y; }
public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
public static void PrintVectorLength(float x, float y)
{
MyVector v = new MyVector(x, y);
Console.WriteLine("Vector=({0},{1}), lenght={2}", x, y, v.Length());
}
}

Vector 클래스를 구조체로 바꿔보면, new 연산자로 인스턴스를 만들어도 heap 영역에 메모리가 할당되지 않는다. 구조체 역시 Value type이기 때문에 stack 영역에 메모리가 할당되며, 메소드를 빠져나갈 경우 자동으로 삭제된다. 물론 heap 영역에 생성된 메모리가 아니기 때문에 가비지 컬렉션의 대상이 되지도 않는다.

<리스트 5> Vector 클래스를 구조체로 변환

public struct MyVector
{
public float x, y;
public MyVector(float x, float y) { this.x = x; this.y = y; }
public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
public static void PrintVectorLength(float x, float y)
{
MyVector v = new MyVector(x, y);
Console.WriteLine("Vector=({0},{1}), lenght={2}", x, y, v.Length());
}
}

구조체로 바꿀 수 없다면, <리스트 6>처럼 멤버변수 사용을 권장한다.

<리스트 6> 멤버변수 사용

public class MyVector
{
public float x, y;
public MyVector() { x = .0f; y = .0f; }
public MyVector(float x, float y) { this.x = x; this.y = y; }
public double Length() { return System.Math.Sqrt(x * x + y * y); }
}

static class TestMyVector
{
private static MyVector m_cachedVector = new MyVector();
public static void PrintVectorLength(float x, float y)
{
m_cachedVector.x = x;
m_cachedVector.y = y;

Console.WriteLine("Vector=({0},{1}), lenght={2}",
x, y, m_cachedVector.Length());
}
}

 

속도 저하가 큰 Boxing
Boxing이란 Value type 객체를 Reference type 객체로 포장하는 과정을 뜻한다. C#의 모든 객체는 object로부터 상속되는데, 심지어 상속받지 못하는 int, float 등의 Value type 조차도 object로부터 상속된 것처럼 사용할 수 있다. 하지만 가비지 컬렉션에 의한 부하 못지않게 boxing으로 인한 부하도 크다. 무심코 만든 코드에서 boxing 과정이 일어나는 경우가 있으니 잘 이해하고 사용해야 한다.

<리스트 7>을 보면 리스트에 서로 다른 type의 값을 추가했지만, loop 안에서 추가 값을 object type으로 받아 하나의 코드로 처리할 수 있음을 알 수 있다.

 

<리스트 7> 서로 다른 type 값 추가

class MyClass
{
public override string ToString() { return "다섯"; }

static public void Sample()
{
ArrayList list = new ArrayList();
list.Add(1);
list.Add(1.5f);
list.Add(‘3’);
list.Add("four");
list.Add(new MyClass());

foreach (object item in list)
Console.WriteLine(item.ToString());
}
}

<그림 2> <리스트 7>의 실행 결과

 

매력적인 C#이지만 Value type의 값을 Reference type인 object로 바꿔주는 과정에는 많은 시간이 걸리며 변환 시에는 System.Object 내부에 랩핑하고 관리되는 heap에 저장된다. 즉 새로운 객체가 만들어지는 셈이다. MSDN에서 발췌한 <그림 3>을 참조하길 바란다.

<그림 3> boxing과 unboxing의 비교

 

따라서 한 번에 다양한 type을 처리하는 경우가 아니라면 collection에 사용된 값의 type을 명시해주는 Generic collection 사용을 권한다. Generic은 C++의 template와 비슷하다. 그래서 Generic collection들은 C++의 STL container들과 비슷하게 생겼다. <리스트 8>을 참고하자.

<리스트 8> Generic collection

class Example
{
static public void BadCase()
{
ArrayList list = new ArrayList();
int evenSum = 0;
int oddSum = 0;

for (int i = 0; i < 1000000; i++)
list.Add(i);

foreach (object item in list)
{
if (item is int)
{
int num = (int)item;
if(num % 2 ==0) evenSum += num;
else oddSum += num;
}
}

Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
}

static public void GoodCase()
{
List<int> list = new List<int>();
int evenSum = 0;
int oddSum = 0;

for (int i = 0; i < 1000000; i++)
list.Add(i);

foreach (int num in list)
{
if (num % 2 == 0) evenSum += num;
else oddSum += num;
}

Console.WriteLine("EvenSum={0}, OddSum={1}", evenSum, oddSum);
}
}

 

메모리가 계속 늘어나는 또 다른 문제의 발생!
이 글을 시작하며 C#에서 사용자는 메모리 해제에 신경 쓸 필요가 없다고 했지만 의도하지 않게 메모리가 늘어나기도 한다. C#에는 delete 같은 메모리 해제 명령이 없기에 메모리 릭(memory leak) 현상이 발생하면 당혹스러울 수 있다. 여기서 C# 메모리의 특징을 다시 한 번 떠올려보자.

시스템에서 더 이상 참조가 없는 메모리를 알아서 해제하는 것을 우리는 가비지 컬렉션이라 부른다. 가비지는 더 이상 참조가 없는 메모리다. C# 애플리케이션이 메모리가 해제되지 않고 계속 증가되고 있다면 어디선가 의도하지 않는 참조가 일어나고 있다고 보면 된다. 그렇다면 어디에서 의도하지 않은 참조가 일어나는 것일까? 예를 통해 확인해 보자.

 

<그림 4> #1 - 케릭터 매니저에서 케릭터를 생성한다

 

<그림 5> #2 - 누군가 디버깅을 위해 '캐릭터 위치 표시' 객체를 만들고 캐릭터 매니저에 접근해 등록된 캐릭터를 모두 참조한다

 

<그림 6> #3 - 캐릭터 매니저에서 필요없는 캐릭터를 삭제한다

 

<그림 7> #4 - 캐릭터 매니저에서 삭제됏지만 '캐릭터 위치 표시' 객체에서는 여전히 참조 중이다. 가비지가 아니기 때문에 메모리에 계속 남아있으며, 구현에 따라서는 의도하지 않게 화면에 남을 수도 있다.

 

WeakReference로 의도하지 않은 참조를 없애보자
System.WeakReference는 가비지 컬렉션에 의한 객체 회수를 허용하면서 객체를 참조한다. 인스턴스를 참조하려면 Weak Reference.Target으로 접근해야 하는데 원본 인스턴스가 가비지 컬렉터에 의해 회수되면 WeakReference.Target은 null이 반환된다.

 

<리스트 9> WeakReference.Target

public class Sample
{
private class Fruit
{
public Fruit(string name) { this.Name = name; }
public string Name { private set; get; }
}

public static void TestWeakRef()
{
Fruit apple = new Fruit("Apple");
Fruit orange = new Fruit("Orange");

Fruit fruit1 = apple; // strong reference
WeakReference fruit2 = new WeakReference(orange);
Fruit target;

target = fruit2.Target as Fruit;
Console.WriteLine(" (1) Fruit1 = \"{0}\", Fruit2 = \"{1}\"",
fruit1.Name, target == null ? "" : target.Name);

apple = null;
orange = null;

System.GC.Collect(0, GCCollectionMode.Forced);
System.GC.WaitForFullGCComplete();

// fruit1과 fruit2의 값을 바꾼 적은 없지만, fruit2의 결과가 달라진다.
target = fruit2.Target as Fruit;
Console.WriteLine(" (2) Fruit1 = \"{0}\", Fruit2 = \"{1}\"",
fruit1==null ? "" : fruit1.Name,
target == null ? "" : target.Name);
}
}

<리스트 9>의 실행으로 <그림 8>을 확인할 수 있다. Fruit2가 참조하고 있던 orange 인스턴스는 가비지 컬렉터에 의해 회수돼 null이 됐다.

<그림 8> <리스트 9>의 실행 결과

 

‘캐릭터 매니저’처럼 객체의 생성·삭제를 직접 관여하는 모듈이 아닌 곳에서는 가능한 WeakRefernce를 사용하는 것이 좋다. ‘객체 위치 표시 객체’처럼 인스턴스를 참조하는 모듈에서 WeakReference를 사용하면, 의도하지 않은 참조로 메모리가 해제되지 않는 실수를 방지할 수 있다. 주의할 점은 Weak Reference.Target 값을 보관하면 안 된다는 것이다. 만약 그대로 보관하고 있으면 강한 참조(strong reference)가 일어나 이를 인식한 가비지 컬렉터는 회수를 실행하지 않게 된다.

C/C++처럼 원하는 시점에 객체를 삭제하고 싶다면
C#에서는 할당된 메모리를 임의로 해제할 수 없다. 컬렉션에 보관된 인스턴스를 제거하거나 인스턴스를 담고 있던 변수에 null을 넣어 더 이상 참조하지 않는 방법이 있지만 실제 인스턴스가 삭제되는 시점은 가비지 컬렉션 동작 이후가 되므로, 언제가 될 지 정확히 알 수 없다. 의도한 시점에 맞춰 정확히 삭제할 수 없다는 점이 그렇게 중요하지는 않다. 하지만 캐릭터 매니저에서 캐릭터를 제거했는데도 여전히 캐릭터 인스턴스가 남아서 화면에 한동안 계속 나타나는 경우가 발생할 수 있다.

 

 

Dispose pattern 소개C#에서는 관리되지 않는 메모리(리소스)를 해제하는 용도로 System.IDisposable이라는 인터페이스를 제공한다. IDisposable 인터페이스를 상속받은 클래스라면 용도에 맞게 Dispose()를 구현해줘야 하는데 이는 FileStream 관련 객체들에서 많이 볼 수 있다.

리소스를 강제로 해제시키려면 직접 Release(), Delete(), Destroy(), Close() 등의 메소드를 만들어 사용하면 되는데 굳이 IDisposable을 사용할 필요가 있을까? 서로 다른 type의 객체여도 IDisposable 인터페이스를 상속받고 있다면, 하나의 코드로 다양한 type의 메모리를 정리할 수 있기 때문에 IDisposable을 사용할 필요가 있다. 또한 Dispose() 메소드만 보고도 “아, 이 클래스는 사용이 끝나면 Dispose()를 호출해서 메모리를 정리해야 하는구나” 라고 금방 알 수 있다.

캐릭터 객체에서 IDisposable 인터페이스를 구현해보자. 업데이트 목록에서도 제외시키고 렌더링 정보도 다 지우자. 캐릭터의 Dipsose()를 호출한 이후에 캐릭터는 어떠한 동작도 하지 못하게 된다. 물론 Dispose()를 호출한다고 캐릭터가 가비지 컬렉터에 의해 메모리 해제되는 것은 아니다.

 

WeakReference과 IDisosalbe의 조합원하는 시점에 메모리를 해제하려면 앞서 설명한 Weak Reference와 IDisposable을 개별적으로 사용하는 것으로는 부족하다. 둘을 함께 사용하는 것이 좋다. <리스트 10>을 보자.

<리스트 10> Disposable 인터페이스를 상속받아 구현된 캐릭터 클래스

namespace MyApp
{
public class SampleChar : IDisposable
{
private IRenderObject m_Render = Renderer.CreateRenderObject();

public void Dispose()
{
SampleCharManager.Remove(this);
m_Render = null;
}

public bool isRemoved { get { return m_Render == null; } }

public void Render()
{
if (m_Render == null) return;
// 렌더링
}

public void Update() { }
}
}

 

예제로 만들어 본 캐릭터 클래스는 Disposable 인터페이스를 상속받아 구현된다. Dispose 후에는 더 이상 업데이트가 되지 않도록 SampleCharManager에서 제거되며, 렌더링 객체를 null로 만들어 화면에 그려지지 않도록 했다.

IRenderObject 인터페이스는 <리스트 11>과 같이 구현된다.

 

<리스트 11> IRenderObject 인터페이스

namespace MyApp
{
public interface IRenderObject
{
void Render();
}

public static class Renderer
{
public static IRenderObject CreateRenderObject()
{
return new DumyRenderObject(); // IRenderObject를 상속받은 더미 객체
}
}
}

<리스트 12>의 캐릭터 매니저 클래스는 등록된 캐릭터들을 일괄적으로 업데이트시키고 렌더링한다.

<리스트 12> 등록 캐릭터 일괄 업데이트 및 렌더링

namespace MyApp
{
static class SampleCharManager
{
private static List<SampleChar> m_list = new List<SampleChar>();

public static void Update()
{
foreach (SampleChar obj in m_list)
obj.Update();
}

public static void Render()
{
foreach (SampleChar obj in m_list)
obj.Render();
}

public static void Add(SampleChar obj)
{
m_list.Add(obj);
}

public static void Remove(SampleChar obj)
{
m_list.Remove(obj);
}
}
}

<리스트 13>의 디버깅을 위한 ‘캐릭터 위치 표시 객체’는 WeakReference를 통해 SampleChar 객체를 참조하도록 구현돼 있고, SampleCharManager에서 캐릭터를 삭제하더라도 안전하게 가비지가 회수된다. 업데이트 시 DisplayCharInfo는 삭제된 캐릭터를 스스로 판단해 목록에서 제거한다.

<리스트 13> 디버깅을 위한 캐릭터 위치 표시 객체

namespace MyDebug
{
static class DisplayCharInfo
{
private static List<WeakReference> m_list = new List<WeakReference>();
private static Queue<WeakReference> m_removeQueue =
new Queue<WeakReference>();

public static void Update()
{
foreach (WeakReference item in m_list)
{
MyApp.SampleChar obj = (item.Target != null) ?
item.Target as MyApp.SampleChar : null;

if (obj == null || obj.isRemoved)
{
m_removeQueue.Enqueue(item);
}
else
{
/- 캐릭터 정보 표시 *-
}
}

while(m_removeQueue.Count > 0)
{
WeakReference item = m_removeQueue.Dequeue();
m_list.Remove(item);
}
}

public static void Add(MyApp.SampleChar obj)
{
m_list.Add(new WeakReference(obj));
}
}
}

C#에서 메모리를 관리하는 데 도움되길 바라며, 지금까지 설명한 내용을 요약하면 다음과 같다.

- string 조합이 많다면, StringBuilder 활용
- Immutable 객체의 값 접근 시 매번 메모리가 생성될 수 있으므로 주의
- 매번 호출되는 메소드 안에서 반복해서 일회성 인스턴스가 생성되지 않도록 주의
- Boxing / unboxing이 가능한 일어나지 않도록 주의
- WeakReference를 사용해서 의도하지 않은 참조 줄이기
- IDisposable 인터페이스를 사용해 사용자가 원하는 시점에 객체 삭제하기

<리스트 14> Value typepublic static class Sample
{
public static void TestValueType()
{
int a = 100;
int b = a;

a = 200;
Console.WriteLine(" a={0}, b={1}", a, b);
}
}

<리스트 14>를 실행하면 <그림 9>와 같은 결과를 확인할 수 있다. a와 b는 서로 다른 메모리 공간을 가지고 있다.


<그림 9> <리스트 14>의 실행 결과

Reference Type은 heap 영역에 할당되며, C/C++의 포인터나 레퍼런스처럼 new로 생성한 인스턴스를 참조한다.

<리스트 15> Reference type

public class MyInt
{
public int Value { get; set; }
public MyInt(int val) { this.Value = val; }

public static void TestReferenceType()
{
MyInt a = new MyInt(100);
MyInt b = a;

a.Value = 200;
Console.WriteLine(" a={0}, b={1}", a.Value, b.Value);
}
}

<리스트 15>의 실행 결과로 <그림 10>을 확인할 수 있다. a와 b는 같은 메모리를 참조한다.


<그림 10> <리스트 15>의 실행 결과

 


출처 Jongk is Programer | Jongk
원문 http://blog.naver.com/njuhb/140165599614

 

블로그 이미지

레몬도리 LemonDory

개발자의 이야기

티스토리 툴바