'ClielLab On Post'에 해당되는 글 575건

Programming/Microsoft SQL Server

The filegroup '???" cannot be removed bacause it is not empty.

 

특정 파일 그룹에 인덱스를 생성한뒤 어떠한 이유에서 해당 인덱스를 삭제한 후 사용중이던 파일그룹더 같이 삭제하려고 하는 경우에 위와 같은 오류를 내면서 파일그룹이 삭제되지 않는 경우가 있습니다. 이런 경우 우선 해당 파일그룹을 사용중인 개체를 확인하도록 합니다.

Select t.name as table_name, i.name as index_name, ds.name as data_space_name, ds.type_desc, ps.name as partition_scheme_name
From sys.tables t join sys.indexes i
On t.object_id = i.object_id
join sys.data_spaces ds
On i.data_space_id = ds.data_space_id
Left Join sys.partition_schemes ps
On ps.data_space_id = ds.data_space_id;

테이블이 확인되면 해당 테이블에서 사용중인 인덱스와 같은 요소를 확인합니다. 인덱스를 삭제했다면 인덱스가 확인되지 않을 텐데 그렇다면 일단 임의의 인덱스를 생성합니다. 그리고 해당 인덱스의 속성을 확인해 Storage부분에서 Filegroup의 값을 확인합니다.

 

 

별도로 파일그룹을 지정하지 않았는데도 해당 인덱스가 삭제하려는 파일그룹에 소속된 상태라면 이걸 다른 파일그룹으로 변경합니다.

 

그리고 다시 파일그룹삭제를 시도하면 정상적으로 삭제됨을 확인할 수 있습니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

파일그룹에 인덱스생성 후 삭제관련 문제  (0) 2019.05.17
인덱스(Index) - 1  (0) 2019.03.26
[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
0 0
Programming/.NET

System.Diagnostics.Stopwatch를 이용하면 시작과 끝사이의 시간차를 알 수 있습니다.

System.Diagnostics.Stopwatch w = new System.Diagnostics.Stopwatch();
w.Start();

int length = 5;
for (int i = 0; i < length; i++)
    Thread.Sleep(1000);

w.Stop();
Console.WriteLine(w.Elapsed.TotalSeconds + " 초 소요됨");

 

'Programming > .NET' 카테고리의 다른 글

[C#] Stopwatch  (0) 2019.05.09
가비지 수집기 : GC (Garbage Collector)  (0) 2019.04.30
[C#] null 조건  (0) 2019.04.16
[ASP.NET] 인증및 접근제어와 로그인  (0) 2019.04.11
[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
0 0
Programming/.NET

GC는 힙에 할당된 객체의 소멸을 자동으로 처리하여 메모리를 관리합니다. 이때 객체가 할당된 힙메모리의 관리는 세대로 구분하여 처리하게 됩니다.

 

예를 들어

static void Main(string[] args)
{
    MyClass m = new MyClass();

    Console.WriteLine(GC.GetGeneration(m));
}

위에서 처럼 new를 통해 최초로 개체를 생성하면 이 개체는 0세대로 분류됩니다. 참고로 GC의 GetGeneration메서드를 사용하면 특정 개체의 세대를 확인할 수 있습니다.

 

이렇게 프로그램 내에서 생성되는 참조형식의 개체들은 0세대로 분류해 계속 힙메모리를 할당하다가 어느정도 용량이 커지게 되면 GC가 자동으로 실행되어 현재 0세대중 사용되지 않는 개체를 골라내어 소멸처리하게 됩니다. GC가 개체의 사용유무를 확인하는 방법은 스택에 개체가 할당된 힙의 주소가 있는지 또는 다른 개체에서 참조하고 있는지의 여부입니다. 이런것들을 루트참조라고 하는데 이 루트참조가 없으면 개체를 메모리에서 제거하게 됩니다.

static void Main(string[] args)
{
    MyClass m = new MyClass();
    MyClass m2 = new MyClass();

    m = null;

    GC.Collect();

    Console.WriteLine(GC.GetGeneration(m2));
}

예제에서 처음 개체 m은 생성후 null을 할당해 스택의 주소를 제거하도록 하였습니다. 그런 후 GC를 실행하면 m은 소멸되고 m2만 남게 되는데 GC의 실행으로 남은 m2는 1세대로 올라가게 됩니다. GC가 세대를 구분하는 기준은 현재 힙공간을 가리키고 있는 내부 포인터의 위치입니다.

 

개체를 힙공간에 할당하기전 최초 포인터는 0세대로 정해지는데 개체를 생성할때마다 힙공간에 개체에 필요한 용량을 할당하게 되고 포인터는 그 주소만큼 증가하여 다음 힙공간을 가리키게 됩니다. 그러다가 일정 용량이상이 커지거나 Collect()메서드호출등의 이유로 GC가 실행되면 지금 현재 포인터가 가리키고 있는 주소부터 그 이전까지 할당된 힙공간을 모두 기존의 0세대에서 1세대로 변경하는 방식으로 세대를 증가시키게 됩니다. 증가하는 세대수는 2세대가 최대이며 2세대 부터는 메모리를 계속 점유하면서 남아있게 됩니다.

 

GC가 세대를 구분하는 이유는 GC동작의 효휼성 때문입니다. GC가 동작할때마다 전세대를 대상으로 한다면 GC입장에서는 부담이 아닐 수 없습니다. 그래서 처음에는 0세대만을 대상으로 GC를 수행하다가 메모리 부족현상이 발생하면 0과 1세대를 대상으로 GC가 동작하게 됩니다. 그러다가 다시 메모리 부족이 발생하면 2세대를 포함한 모든 세대를 대상으로 GC가 수행됩니다. 예제에서 Collect() 메서드를 호출해 GC의 동작을 평가하고 있는데 이때 메서드호출시 0 이나 1 또는 2값을 매개변수로 전달하면 해당 세대에 대해서만 GC가 동작되도록 지정할 수 있습니다.

 

그런데 이 과정에서 특정 개체가 메모리에서 소멸하게 되면 개체가 소멸된 힙공간은 비어있게 되는데 GC는 이를 그대로 두지 않고 뒤에 있는 개체를 당겨와 비어있는 공간을 메우게 됩니다. 그래서 개체가 최초로 생성될때의 메모리 주소와 GC가 실행되고 난 이후의 메모리 주소는 달라질 수 있습니다. 하지만 꼭 그렇지 않은 경우도 있습니다. 용량이 너무 큰 개체(85,000 Byte)의 경우에는 그 개체에 할당된 힙을 LOH(Large Object Heap)로 분류하고 위치를 고정시키는데 너무 큰 개체를 GC가 동작할때마다 메모리 내부에서 이동시키기에는 부담이 너무 크기 때문입니다. LOH는 시작부터 2세대로 들어가며 메모리 위치를 바꾸지 않기에 메모리에 공백이 생길수도 있어서 주의해야 합니다.

 

참고로 GC의 Collect() 메서드는 GC를 수행하는 메서드인데 이 메서드를 호출한다고 해서 무조건 GC가 수행되지는 않고 실제 GC가 필요한지는 CLR이 판단하여 수행여부를 결정합니다.

'Programming > .NET' 카테고리의 다른 글

[C#] Stopwatch  (0) 2019.05.09
가비지 수집기 : GC (Garbage Collector)  (0) 2019.04.30
[C#] null 조건  (0) 2019.04.16
[ASP.NET] 인증및 접근제어와 로그인  (0) 2019.04.11
[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
0 0
Programming/.NET

다음은 참조형 변수가 null인 상황을 확인하는 전형적인 방법입니다.

myClass m = new myClass();

if (m != null) {
    Console.WriteLine(m?.i);
}

하지만 null 조건을 사용하면 아래와 같이 소스코드를 간단히 변경할 수 있습니다.

myClass m = new myClass();
m = null;

Console.WriteLine(m?.i);

만약 m이 null이라면 null을 반환하고 그렇지 않으면 m의 멤버변수 i의 값을 출력할 것입니다.

'Programming > .NET' 카테고리의 다른 글

[C#] Stopwatch  (0) 2019.05.09
가비지 수집기 : GC (Garbage Collector)  (0) 2019.04.30
[C#] null 조건  (0) 2019.04.16
[ASP.NET] 인증및 접근제어와 로그인  (0) 2019.04.11
[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
0 0
Programming/.NET

 1. 인증

 

웹사이트에서 사용하는 대부분의 인증은 Form인증방식입니다. 사이트 자체에서 사용자의 정보를 확인하고 인증을 처리해 주는 방식입니다. 이 방식의 인증을 활용하기 위해서는 우선 web.config의 <system.web>하위에 다음과 같이 인증방식을 설정합니다.

 

<authentication mode="Forms"/>

 

필요에 따라 <forms> 요소를 통해 인증과 관련된 여러가지 설정사항을 등록할 수 있습니다.

 

<authentication mode="Forms">
  <forms name="_auth_" loginUrl="login.aspx" protection="All" timeout="30" requireSSL="false" 

slidingExpiration="true" cookieless="AutoDetect"></forms>
</authentication>

 

ASP.NET은 인증이 완료되면 사용자에게 특정 쿠키값을 제공하는데 name은 이 쿠기값의 이름을 의미합니다. 이름의 기본값은 .ASPXAUTH입니다.

 

사용자가 인증이 필요한 페이지에 접속을 시도하거나 로그인을 시도하는 경우 보여줄 로그인을 페이지는 loginUrl로 지정합니다.

 

인증이 완료되고 나면 쿠키값을 받게 되는데 이때 쿠키의 보호수준을 protection으로 지정합니다. 기본으로 적용되는 All은 쿠키의 암호화와 유효성을 확인하는 옵션이며 Encryption은 암호화만 수행할뿐 유효성검사는 수행하지 않습니다. 반면 Validation은 암호화를 수행하지 않지만 유효성은 확인하며 None은 쿠키보호에 대한 어떤 작업도 하지 않습니다. All값을 권장합니다.

 

쿠키만료시간은 timeout으로 설정합니다. 단위는 '분'이며 기본은 30분입니다.

 

로그인시도시 관련 사용자정보를 전송할때 SSL통신이 필요한지를 requireSSL로 지정합니다.

 

timeout이 30으로 설정되어 있다면 사용자가 로그인을 하고 난 후 아무런 작업도 하지 않은채 30분이 경과할시 쿠키는 만료됩니다.(로그아웃처리) 만약 30분이 되기 전에 어떤 작업이라도 수행해서 웹서버로부터 HTTP통신을 한다면 이 시간은 자동으로 갱신되 다시 30분주기를 갖게 됩니다. 그런데 만일 slidingExpiration을 false로 설정하면 최초로그인부터 쿠키만료시간은 30분간격으로 일정하게 유지됩니다. 사용자가 중간에 어떤 작업을 하더라도 이 시간은 갱신되지 않으며 30분이 지나면 쿠키는 만료될 것입니다.

 

쿠키는 다양한 방법으로 처리될 수 있습니다. cookieless를 UseUri로 하면 URL을 통해 처리되며 UseDeviceProfile은 사용자 웹브라우저 상황을 판단하여 쿠키를 사용할지 결정하게 됩니다. UseCookies는 항상 쿠키를 사용하여 인증처리하도록 하고 AutoDetect는 쿠키전달을 UseUri로 할지 UseCookies로 할지 자동으로 판단합니다.

 

참고로 UseUri는 일단 먼저 쿠키에 값을 저장하고 해당 값을 Url을 통해 전달하는 방식입니다.(쿠키를 사용하지 않는게 아닙니다.)

 

인증에 필요한 설정에서 mode값은 Forms이외에 다음과 같은 값을 가질 수도 있습니다.

 

<authentication mode="Windows"></authentication>

 

Windows는 윈도우서버상에서 사용자를 생성하고 해당 사용자에 대한 접근처리를 수행하는 인증방식입니다. Passport는 장담하건데 쓸일이 없으므로 넘어가도 좋습니다. 그외 None값이 있으며 이는 인증모드를 사용하지 않음을 의미합니다.

 

사용자가 소수로 제한되어 있다면 Web.config의 forms요소 하위에 인증할 수 있는 사용자를 특정할 수도 있습니다.

 

<forms name="_auth_" loginUrl="login.aspx" protection="All" timeout="30" requireSSL="false" slidingExpiration="true" cookieless="AutoDetect">
    <credentials passwordFormat="Clear">
      <user name="cliel" password="12345"/>
    </credentials>
  </forms>

 

credentials 요소에는 passwordFormat 속성으로 암호화 방식을 지정하며 Clear값은 일반 텍스트로 암호를 비교할 것임을 의미합니다. 만일 암호화 형식을 바꾸면 암호도 그에 맞게 지정되어야 합니다.

 

<credentials passwordFormat="SHA1">
  <user name="cliel" password="A6H0A6H70AYT7HTYH359HWNPBWPEJBSJN5IJ6"/>
</credentials>

 

그러나 실제 일반텍스트에서 암호로 변한 문자가 어떻게 될지는 모를일인데 이런 경우 HashPasswordForStringInConfigFile 메소드로 암호를 확인하고 설정하시면 됩니다.

 

string password = FormsAuthentication.HashPasswordForStoringInConfigFile("12345", "SHA1");

 

이렇게 설정한 사용자의 유효성 확인은 '3.로그인' 에서 다시 살펴보겠습니다.

 

참고로 사용자 인증후에는 User개체를 통해 인증과 관련된 몇가지 정보를 조회할 수 있습니다. 사용가능한 항목은 아래 표를 참고해 주십시오.

 

 AuthenticationType

 사용자의 인증타입을 가져옵니다. Basic, NTLM, Forms, Passport 등이 있습니다.

 IsAuthenticated

 현재 사용자가 인증된 상태인지를 확인합니다.

 Name

 사용자의 아이디(이름)을 가져옵니다. 윈도우 인증이라면 도메인명도 같이 가져오게 됩니다.

 

 2. 접근제어

 

웹 사이트의 특정 페이지가 인증된 사용자만이 접근할 수 있는 페이지라면 해당 페이지에 익명사용자는 거부되고 인증된 사용자만이 접근할 수 있도록 설정해야할 필요가 있을 것입니다.

 

이를 위해서는 우선 접근제어가 필요한 웹페이지와 같은 위치에 web.config를 생성하고 <authorization>요소와 <deny>요소를 이용해 익명사용자를 거부하도록 합니다.

 

<?xml version="1.0"?>
<configuration>
    <system.web>
      <authorization>
        <deny users="?"/>
      </authorization>
    </system.web>
</configuration>

 

users에 ?를 설정하는건 인증되지 않은 모든 사용자(익명사용자)를 의미합니다. 또한 특정한 페이지를 따로 지정하지 않는다면 해당 경로(위 설정이 적용된 web.config가 존재하는)에 있는 모든 페이지가 영향을 받게 됩니다. 만약 같은 위치에 익명사용자에 대한 접근거부 페이지와 접근허용페이지가 혼재하는 경우에는 거부되어야할 페이지를 다음과 같이 명시해야 합니다.

 

참고로 이러한 개념은 allow의 경우에도 마찬가지 입니다.

 

<?xml version="1.0"?>
<configuration>
  <system.web>
    <authorization>
      <deny roles="mygroup"/>
    </authorization>
  </system.web>
</configuration>

 

roles는 특정 Group에 대한 접근이나 거부를 설정하고자 할때 사용합니다. Form인증의 경우 역활을, Windows인증의 경우 생성된 그룹명을 여기에 지정할 수 있습니다.

 

<?xml version="1.0"?>
<configuration>
  <system.web>
    <authorization>
      <deny verbs="GET"/>
    </authorization>
  </system.web>
</configuration>

 

verbs는 HTTP요청에 대한 접근제어를 설정하는 것입니다. 위 예제처럼 하면 HTTP의 GET요청은 거부될 것입니다.

 

<?xml version="1.0"?>
<configuration>
  <location path="WebForm.aspx">
    <system.web>
      <authorization>
        <deny users="?"/>
      </authorization>
    </system.web>
  </location>
</configuration>

 

익명 사용자가 WebForm.aspx에 접근을 시도하면 HTTP 404에러가 표시될 것입니다. 특정 사용자에 대한 거부라면

 

<?xml version="1.0"?>
<configuration>
  <location path="WebForm.aspx">
    <system.web>
      <authorization>
        <deny users="username"/>
      </authorization>
    </system.web>
  </location>
</configuration>

 

처럼 사용자명을 지정해 주면 됩니다. 윈도우인증인 경우 '도메인/사용자명'형식으로 지정합니다.

 

접근거부에 대한 결과인데 이를 좀더 자연스럽게 처리하려면 login페이지를 따로 만들고 프로젝트의 web.config <form>요소에 loginUrl속성으로 지정합니다.

 

<authentication mode="Forms">
  <forms loginUrl="login.aspx"></forms>
</authentication>

 

그러면 익명사용자가 인증이 필요한 페이지에 접근할때 기존 에러페이지를 표시하는대신 login.aspx로 이동하게될 것입니다. forms에는 name속성도 지정할 수 있는데 일반적으로 사용자를 식별하기 위한 쿠키이름을 지정합니다. 이름을 지정하지 않으면 .ASPXAUTH라는 이름이 사용됩니다.

 

 3. 로그인

 

익명사용자를 인증사용자로 전환하기 위해서는 로그인과정을 거쳐야 하는데 이를 위해 다음과 같이 Login컨트롤을 페이지에 배치합니다.

 

<form id="form1" runat="server">
<div>
    <asp:Login ID="Login1" runat="server"></asp:Login>
</div>
</form>

 

이걸로 로그인을 위한 준비는 끝났습니다. 하지만 사용자가 존재해야 해당 사용자가 로그인을 할텐데 사용자 생성에 관해서는 아래 글을 참고해 주시기 바랍니다.

 

2016/03/21 - [Programming/ASP.NET] - [ASP.NET] CreateUserWizard와 관련API


이 글은 위 글의 연장선에 있으며 이미 생성된 사용자(anchor37)가 있다고 가정합니다.

 

로그인 컨트롤은 기본적으로 Remember me next time. 체크 박스를 표시하는데 RememberMeText 속성을 원하는 내용으로 설정하면 Remember me next time.를 설정내용으로 바꿀 수 있으며 DisplayRememberMe속성을 false로 하면 아예 이 체크박스가 표시되지 않습니다.


또한 RememberMeSet 속성을 true로 하면 로그인할때 로그인정보를 클라이언트의 쿠키에 담도록 하며 이때 DisplayRememberMe속성을 true로 하면 자동으로 체크박스가 선택된 상태로 표시되지만 false로 하면 사용자가 체크여부를 선택할 수 없으므로 무조건 로그인정보가 쿠키로 저장됩니다.


따라서 별도의 로그아웃을 거지치 않으면 해당 사용자의 로그인은 계속 유지되므로 주의가 필요합니다.

 

사용자가 로그인을 시도할때 계정이 없는 경우 계정을 만들도록 유도하기 위해서는 CreateUserText와 CreateUserUrl속성을 사용합니다.

<asp:Login ID="Login1" runat="server" CreateUserText="회원가입" CreateUserUrl="~/Default.aspx"></asp:Login>

 

마찬가지로 암호를 찾기위한 경우는 PasswordRecoveryText와 PasswordRecoveryUrl속성을, 도움말 표시를 위해서는 HelpPageText, HelpPageUrl 속성을 사용합니다.

 

로그인 컨트롤을 사용하지 않는 경우나 직접 로그인처리를 해야하는 경우라면 FormsAuthentication클래스의 RedirectFromLoginPage메소드를 사용합니다.

 

FormsAuthentication.RedirectFromLoginPage("anchor37", false);

 

이 메소드의 첫번째 인수는 사용자 ID이며 두번째 인수는 해당 사용자의 쿠키를 영구생성할지를 지정합니다. 하지만 이 경우에도 사용자에 대한 정보를 확인하고 해당 사용자가 맞는지에 대한 인증을 거칠 필요가 있는데 이때는 Membership클래스의 ValidateUser메소드를 사용하면 됩니다.

 

if (Membership.ValidateUser("anchor37", "12356"))

                 FormsAuthentication.RedirectFromLoginPage("anchor37", false);

 

ValidateUser메소드는 사용자의 아이디와 비밀번호를 파라메터로 받고 결과를 bool값으로 반환합니다. 만약 Web.config에서 credentials요소를 통해 사용자를 특정한 상태라면 해당 사용자에 대한 유효여부는 Authenticate메소드로 처리할 수 있습니다.

 

if (FormsAuthentication.Authenticate("username", "password"))
                FormsAuthentication.RedirectFromLoginPage("anchor37", false);

 

위 예제에서 처럼 ASP.NET 에서 Form인증과 관련된 대부분의 API처리는 FormsAuthentiation 클래스를 사용하고 있습니다. 예제에서 사용된 메소드나 속성이외에도 FormsAuthentiation 클래스는 다음과 같은 유용한 메소드/속성을 가지고 있습니다.

 

 FormsCookieName

 쿠키이름을 반환합니다.

 FormsCookiePath

 쿠키경로를 반환합니다.

 Initialize

 Web.config 구성을 읽고 FormsAuthentication 클래스를 초기화 합니다.

 SignOut

 로그아웃 처리를 수행합니다.

 Decrypt

 암호화된 쿠키를 복호화합니다.

 Encrypt

 암호화된 쿠키를 생성합니다.

 

어느 웹서비스든 뻘짓하는 사용자가 존재하기 마련인데 특정 아이디에 대한 비밀번호를 반복적으로 입력해 정공법으로 해당 사용자의 계정을 취득하려는 경우가 대표적입니다. ASP.NET에서는 기본적으로 10분동안 로그인 시도횟수를 5번으로 제한하고 있는데 이러한 정책을 수정하려면 Membership공급자를 재정의해야 합니다.

 

<membership>

    <providers>

        <clear/>

        <add name="AspNetSqlMembershipProvider" 

type="System.Web.Security.SqlMembershipProvider, System.Web, Version=4.0.0.0, Culture=neutral, 

PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="sqlprovider" enablePasswordRetrieval="false" 

enablePasswordReset="true" requiresQuestionAndAnswer="true" applicationName="/" 

requiresUniqueEmail="false" passwordFormat="Hashed" maxInvalidPasswordAttempts="5" 

minRequiredPasswordLength="7" minRequiredNonalphanumericCharacters="1" 

passwordAttemptWindow="10" passwordStrengthRegularExpression=""/>

     </providers>

</membership>


우선 공급자의 설정내용을 살펴보면 maxlnvalidPasswordAttempts가 5로 되어 있는 것을 볼 수 있는데 이는 5번의 시도를 의미합니다. passwordAttemptWindow의 값이 10이므로 제한시간은 10분이 됩니다.

 

실제 사용자가 잘못된 비밀번호를 입력하는 경우 개인화 DB의 aspnet_Membership 테이블에 그 기록이 남게 됩니다.

 

테이블의 FailedPasswordAttemptCount는 실패횟수를 FailedPasswordAttemptWindowStart는 실패시간을 의미합니다.

 

사용자가 아이디와 비밀번호를 입력하고 로그인을 시도했는데 위 설정 정책에 따라 10분동안 5번로그인에 실패하면 aspnet_Membership 의 IsLockedOut 컬럼에 true값이 입력되고 계정은 잠기게 됩니다.

 

계정 잠금에 관해서는 MembershipUser객체를 생성해 필요한 내용을 확인하거나 조치를 취하면 됩니다.

 

MembershipUser mu = Membership.GetUser("anchor37");

 

if (mu.IsLockedOut) { //잠긴계정 확인

    Response.Write("귀하의 계정음 잠겼습니다.");

 

    if (mu.UnlockUser()) //계정잠금 풀기

        Response.Write("귀하의 계정이 풀렸습니다.");

}

 

Login 컨트롤을 통해서 직업 사용자명과 비밀번호를 확인하고자 한다면 컨트롤의 OnAuthenticate 이벤트를 활용하면 됩니다.

 

<asp:login runat="server" ID="login1" OnAuthenticate="login1_Authenticate"></asp:login>

protected void login1_Authenticate(object sender, AuthenticateEventArgs e)
{
    if (login1.UserName == "username" && login1.Password == "password") {
    }
}

 

참고로 Remember me next time 설정값은 RememberMeSet 속성으로 확인할 수 있습니다.

'Programming > .NET' 카테고리의 다른 글

가비지 수집기 : GC (Garbage Collector)  (0) 2019.04.30
[C#] null 조건  (0) 2019.04.16
[ASP.NET] 인증및 접근제어와 로그인  (0) 2019.04.11
[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
[C#] HttpWebRequest / HttpWebResponse / WebClient  (0) 2019.03.12
0 0
Programming/.NET

yield는 반복구문안에서 return이나 break에 붙여 쓸 수 있는 예약어입니다.

 

yield return은 처음 yield return이 실행된 코드를 기억했다가 다음번 재호출시 다음 yield return다음의 코드부터 실행하는 특징을 가지고 있습니다.

 

static void Main(string[] args)
{
    foreach (int i in TestYield())
        Console.WriteLine(i.ToString());

    Console.Read();
}

static IEnumerable<intTestYield()
{
    yield return 10;
    yield return 20;
    yield return 30;
}

 

처음 TestYield 실행시 10이 반환됐다가 다시 TestYield가 호출되면 20이 반환되고 또 다시 호출되면 30이 반환됩니다. 마지막으로 yield return이 실행됐던 바로 다음코드부터 실행되기 때문입니다.

 

yield break는 열거를 중지하는 역활을 수행합니다.

 

static IEnumerable<intTestYield()
{
    yield return 10;
    yield break;
    yield return 30;
}

'Programming > .NET' 카테고리의 다른 글

[C#] null 조건  (0) 2019.04.16
[ASP.NET] 인증및 접근제어와 로그인  (0) 2019.04.11
[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
[C#] HttpWebRequest / HttpWebResponse / WebClient  (0) 2019.03.12
[C#] 시프트(Shift) 연산자  (0) 2019.03.05
0 0
Programming/Microsoft SQL Server

1. 인덱스 생성

 

인덱스를 생성하기전 우선 간단한 형태의 테이블을 만들어 보겠습니다.

 

Create Table Items (
    item_no Int,
    item_name nChar(10),
    item_price Decimal(8, 2),
    item_description nVarChar(500)
);

 

인덱스는 다음과 같은 방법으로 생성합니다.

 

Create Clustered Index item_no_idx
On Items (item_no);

 

위 구문은 Items 테이블의 item_no 열에 대해 Clustered 형 인덱스를 생성하도록 합니다. Create 구문에서 Clustered 를 생략하면 기본값인 Nonclustered 인덱스가 생성되며 Unique 를 지정하면 중복값이 없는 인덱스를 생성합니다. 기본값은 중복가능입니다.

 

만약 인덱스를 지정할 열(Column)이 2개 이상이라면 콤마(,)를 통해 열을 구분하면 됩니다.

 

Create NonClustered Index item_no_idx
On Items (item_no, item_name);

 

데이터의 정렬순서를 지정하려면 열(Column)뒤에 Asc나 Desc를 지정하면 됩니다. 기본값은 Asc입니다.

 

Create Clustered Index item_no_idx
On Items (item_no Desc);

 

생성 구문 하위에 다시 On을 지정하여 인덱스를 별도의 파일그룹에 생성할 수 있습니다. 인덱스를 타는 경우 인덱스 테이블과 데이터 테이블에 거의 동시에 접근이 시도되므로 데이터 테이블이 존재하는 디스크 이외에 다른 디스크에 파일그룹을 생성하고 해당 파일그룹에 인덱스를 생성하면 성능상 이점을 얻을 수 있게 됩니다.

 

 

Subgroup이라는 별도의 파일 그룹을 생성합니다.

 

 

새로운 파일을 생성하고 해당 파일에 대한 파일그룹을 위에서 만든 Subgroup에 지정합니다. 이때 해당 파일을 별도의 디스크에 생성하도록 하면 인덱스 테이블과 데이터 테이블을 분리할 수 있게 됩니다.

 

위와 같이 파일 그룹을 생성하고

 

Create Clustered Index item_no_idx
On Items (item_no Desc)
On Subgroup;

 

해당 파일 그룹에 인덱스를 생성하면 분리가 완료됩니다.

 

인덱스를 생성하면 B-Tree구조로 인덱스 페이지가 만들어집니다. 페이지에 데이터가 채워지면 페이지가 다 채워지는 경우 새로운 페이지를 생성하여 데이터를 나누는 페이지 분할이 발생합니다. 해당 테이블이 자주 변경되는 경우라면 페이지 분할도 그만큼 자주 발생하게 되는데 이때 페이지에 데이터가 채워지는 기준을 정해주면 페이지 분할에 발생하는 부하를 조금이나마 감소시켜 줄 수 있습니다.

 

Create Clustered Index item_no_idx
On Items (item_no)
With (PAD_INDEX = On, Fillfactor = 50);

 

PAD_INDEX 옵션을 On으로 설정하고 Fillfactor에 50을 지정하면 인덱스를 생성할때 여유공간 50%를 확보하고 페이지를 생성하도록 합니다. 여유공간이 크면 클 수록 인덱스 페이지는 더 많은 데이터를 담을 수 있기에 페이지 분할횟수는 그 만큼 감소할 수 있습니다.

 

그러나 단점도 존재하는데 우선 여유공간을 많이 확보하면 할 수록 디스크 공간을 더 많이 차지하게 됩니다. 게다가 기존에 인덱스로 구성할 데이터가 많은 경우라면 페이지를 생성할때 여유공간을 확보하기 위해 기존보다 더 많은 페이지를 생성해야 하므로 일반적인 경우보다 검색속도는 느려질 수 있습니다.

 

tempdb가 별도의 디스크에 존재하는 경우 인덱스를 생성할때 다음과 같이 임시파일을 tempdb에 생성하도록 지정하면 인덱스 생성시에 성능을 더 높일 수 있습니다.

 

Create Clustered Index item_no_idx
On Items (item_no)
With (SORT_IN_TEMPDB = On);

 

특정 테이블에 대해 인덱스를 생성하면 인덱스 생성완료시까지 해당 테이블은 잠김상태가 됩니다. 필요하다면 Online 옵션을 지정해 인덱스 생성시에도 데이터 테이블에 접근이 가능하도록 할 수 있습니다.

 

Create Clustered Index item_no_idx
On Items (item_no)
With (Online = On);

 

인덱스를 생성할때 조금이라도 성능을 높이기 위해서 해당 서버의 CPU갯수를 최대한 확보하도록 할 수 있습니다.

 

Create Clustered Index item_no_idx
On Items (item_no)
With (Maxdop = 64);

 

위 예제는 64개의 CPU를 사용하도록 한 것인데 이것이 최대값입니다. 기본값은 0이며 이것은 시스템이 알아서 판단하도록 합니다.

 

인덱스는 생성시에 압축을 수행하여 디스크 공간을 절약하도록 할 수 있습니다.

 

Create Clustered Index item_no_idx
On Items (item_no)
With (Data_compression = Page);

 

Data_compression에 page를 지정하면 Page단위로 압축을 수행합니다. 반면 값을 Row로 하여 테이블의 Row단위로 압축할 수 있도록 지정할 수 있습니다.

 

 

 

2. 인덱스 변경

 

인덱스 변경에는 Alter Index 구문을 사용해 변경할 인덱스를 지정하고 On으로 테이블을 명시합니다.

 

Alter Index item_no_idx
On Items
Rebuild;

 

참고로 특정 인덱스가 아닌 테이블에 있는 모든 인덱스를 명시하고자 하는 경우에는 인덱스명대신 All 키워드를 사용합니다.

 

Alter Index ALL
On Items
Reorganize;

 

Rebuild 옵션은 기존 인덱스를 삭제하고 새로 생성하도록 합니다. 다만 해당 작업이 종료될때까지 테이블은 잠기게 되는데 With (Online=On) 옵션을 추가하면 계속 테이블을 사용할 수 있습니다.

 

Alter Index item_no_idx
On Items
Rebuild
With (Online = On);

 

아래 구문은 인덱스를 다시 구성하도록 합니다.

 

Alter Index item_no_idx
On Items
Reorganize;

 

이게 필요한 경우는 인덱스가 단편화된것을 모으기 위한 것입니다.

 

Select A.index_id, A.avg_fragmentation_in_percent
From sys.dm_db_index_physical_stats (DB_ID(), OBJECT_ID('dbo.items'), Null, Null, Null) As A Inner Join sys.indexes As B
On A.object_id = B.object_id
And A.index_id = B.index_id;

 

위 쿼리는 특정 테이블의 인덱스에 대해 단편화 정도를 확인할 수 있도록 합니다. avg_fragmentation_in_percent 열의 값이 단편화인데 예를 들어 이 값이 30이라면 30%정도의 단편화를 보이고 있다는 뜻입니다.

 

이 단편화 수치가 높으면 Alter Index 구문의 Reorganize 옵션으로 단편화를 줄여줄 수 있습니다.

 

3. 인덱스 삭제

 

인덱스를 삭제하고자 하는 경우 Drop Index 구문을 사용합니다.

 

Drop Index [dbo].[items].item_no_idx;

 

다만 Primary Key나 Unique제약조건을 통해 생성된 인덱스는 Drop Index로 삭제가 불가능하고 Alter Table을 수정해 제약조건을 우선 제거해야 합니다. 그러면 자연스럽게 인덱스도 삭제될 것입니다.

 

인덱스를 삭제할때 클러스터형과 비클러스터형 모두를 삭제하고자 하는 경우 되로록이면 비클러스터형을 우선 삭제하고 그 다음 클러스터형 순으로 삭제하는 것이 좋습니다. 클러스터형을 먼저 삭제하게 되면 클러스터형의 인덱스 페이지를 바라보던 비클러스터형의 리프 페이지들은 기존처럼 정보가 '페이지번호+고유번호'형태로 수정되어야 하는데 이 절차는 어차피 비클러스터형도 삭제할 거라면 거쳐야할 필요가 없기 때문입니다.

 

4. 인덱스 확인

 

생성된 인덱스를 정보를 확인해 보려면 sp_helpIndex 프로시저를 호출합니다.

 

Exec sp_helpIndex [items];

 

인덱스의 페이지수나 용량등 좀더 상세한 정보를 확인하려면 아래 쿼리를 수행하도록 합니다.

 

Select A.name, A.type_desc, C.data_pages, C.data_pages * 8 As 'kb'
From sys.indexes As A Inner Join sys.partitions As B
On A.object_id = B.object_id
And OBJECT_ID('dbo.items') = A.object_id
And A.index_id = B.index_id Inner JoIN sys.allocation_units As C
On C.container_id = B.hobt_id;

 

'Programming > Microsoft SQL Server' 카테고리의 다른 글

파일그룹에 인덱스생성 후 삭제관련 문제  (0) 2019.05.17
인덱스(Index) - 1  (0) 2019.03.26
[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
0 0
Programming/.NET

params는 메서드에 전달할 매개변수가 너무 많거나 몇개가 될지 예측할 수 없는 경우에 유용하게 사용될 수 있습니다.

 

static void Main()
{
     Console.WriteLine("return : {0}", plus(10, 20));
}

private static int plus(int a, int b)
{
     return a + b;
}

 

위 예제에서 현재 plus 메서드는 int형 매개변수 2개를 받도록 되어 있지만 더 많은 대상을 처리하려고 하는 경우

 

private static int plus(int a, int b, , int c, , int d, , int e, , int f....)
{
     return a + b + c + d + e + f....;
}

 

이런 형태로 계속해서 매개변수를 늘리는것은 효휼적이지 못할 것입니다.

 

private static int plus(params int[] num)
{
     int result = 0;

     for (int i = 0; i < num.Length; i++)
          result += num[i];

     return result;
}

 

이 예제는 params를 통해 가변적으로 매개변수를 받을 수 있는 방법을 보여주고 있습니다. 방식은 일반적인 배열과 비슷하게 처리하는 것인데 차이점이라면 메서드를 호출하는 쪽에서 배열전달을 위한 별도의 작업을 해줄 필요가 없고 보통 메서드를 호출하는 방식을 그대로 사용할 수 있다는 것입니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET] 인증및 접근제어와 로그인  (0) 2019.04.11
[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
[C#] HttpWebRequest / HttpWebResponse / WebClient  (0) 2019.03.12
[C#] 시프트(Shift) 연산자  (0) 2019.03.05
[C#] 예외처리(try ~ catch)  (0) 2019.02.19
0 0
Programming/.NET

System.Net.HttpWebRequest 는 HTTP 통신을 위한 요청을 자동으로 만들어 주며 System.Net.HttpWebResponse는 그에 대한 응답을 해석해주는 클래스입니다.

 

HttpWebRequest hwreq = (HttpWebRequest)WebRequest.Create("http://www.naver.com");
HttpWebResponse hwres = (HttpWebResponse)hwreq.GetResponse();

StreamReader sr = new StreamReader(hwres.GetResponseStream());
File.WriteAllText("default.htm", sr.ReadToEnd());
sr.Dispose();

Console.WriteLine("다 됐음");

 

우선 HttpWebRequest로 특성 URL에 대한 요청을 만들어 GetResponse 메서드로 응답을 받습니다. 이때 응답을 HttpWebResponse형식으로 받아 스트림으로 처리하면 응답내용을 확인할 수 있습니다.

 

WebClient는 쉽게 HttpWebRequest 와 HttpWebResponse를 좀더 추상화한 것으로 보면 됩니다. 너무 간단해서 설명조차 필요없을듯 합니다.

 

WebClient wc = new WebClient();
File.WriteAllText("default.htm", wc.DownloadString(http://www.naver.com/));

 

DownloadString 메서드는 요청에 대해 응답해오는 데이터를 문자열로 받아오도록 합니다. WebClient에는 이외에도 HTTP관련 다양한 메소드를 제공하고 있습니다.

'Programming > .NET' 카테고리의 다른 글

[C#] yield  (0) 2019.04.02
[C#] params  (0) 2019.03.19
[C#] HttpWebRequest / HttpWebResponse / WebClient  (0) 2019.03.12
[C#] 시프트(Shift) 연산자  (0) 2019.03.05
[C#] 예외처리(try ~ catch)  (0) 2019.02.19
[C#] ? / Nullable  (0) 2019.02.12
0 0
Programming/.NET

비트 단위의 데이터연산시 사용합니다. 예를 들어 정수 10은 비트값으로 아래와 같습니다.

 

00000000 00001010

 

참고로 32나 64비트에 따라 자리수만큼 표시되어야 하지만 간소화를 위해 16비트로 표시하였습니다.

 

시프트연산자는 이 비트를 오른쪽으로 혹은 왼쪽으로 0을 채워 밀어주는 역활을 수행 합니다.

 

int i = 10;

int leftShift = i << 3;
int rightShift = i >> 3;

Console.WriteLine("Result : {0} | {1}", leftShift, rightShift);

 

<< 표시는 왼쪽 시프트연산자이며 >> 표시는 오른쪽 시프트연산자입니다. 이 연산자에 따라 10 (1010)을 왼쪽으로 3개의 비트를 밀면

 

00000000 01010000

 

이 되고 이 비트를 10진수 정수로 변환하면 80이 됩니다. 반면 오른쪽으로 3개비트를 밀게되면

 

00000000 00000001

 

이 되어 결과는 1이 됩니다.

 

이 연산방식은 왼쪽시프트는 1비트씩 움직일때 마다 2를 곱한것과 같고 오른쪽 시프트는 2로 나눈것과 같은 값이 됩니다.

'Programming > .NET' 카테고리의 다른 글

[C#] params  (0) 2019.03.19
[C#] HttpWebRequest / HttpWebResponse / WebClient  (0) 2019.03.12
[C#] 시프트(Shift) 연산자  (0) 2019.03.05
[C#] 예외처리(try ~ catch)  (0) 2019.02.19
[C#] ? / Nullable  (0) 2019.02.12
[C#] partial  (0) 2019.02.07
0 0
Programming/.NET

C# 에서 예외를 처리하는 가장 기본적인 방법은 try ~ catch 구문을 사용하는 것입니다.

 

string s = null;
string a = s.ToString();

 

위 코드는 고의적으로 예외를 발생시키기 위해 작성된 것으로 위와 같이 try catch를 사용하지 않으면 프로그램은 예외를 발생시키고 강제로 종료될 것입니다.

 

try {
    string s = null;
    string a = s.ToString();
} catch (Exception e) {
    Console.WriteLine(e.Message);
}

 

물론 아래와 같이 명시적으로 예외를 발생시키는 것도 가능합니다.

 

try
{
    ApplicationException e = new ApplicationException("오류발생");
    throw e;
}
catch (Exception e)
{
    Console.WriteLine(e.Message);
}

 

try ~ catch는 해당 구문안에서 예외가 발생하는 경우 처리를 catch하위로 넘기게 됩니다. 이때 예외처리를 담당하게 되는 catch에서는 인자로 Exception 형식의 객체를 받아 이 객체를 통해  Console로 오류메세지를 출력하도록 하고 있습니다.

 

실제 프로그램에서는 초기화 되지 않은 개체의 접근에 대한 System.NullReference(위 예제에서 발생시키는 예외타입에 해당합니다.)나 배열의 잘못된 인덱스지정으로인한 System.IndexOutOfRangeException 등 발생하는 예외의 형식이 다양한데 필요할때 마다 예외형식에 맞는 인스턴스를 활용할 수 있습니다.

 

예제에서 사용된 System.Exception 형식은 가장 최상위의 예외타입에 해당하는 것으로 다른 예외형식들은 모두 System.Exception 으로부터 파생된것이며 예외형식을 직접 구현하는 경우도 System.Exception으로 부터 상속받는 방법으로 예외클래스를 구현해야 합니다. System.Exception를 상속받으면 기본적으로 예외에 대한 설명을 제공하는 Message나 예외를 발생시킨 메서드를 알 수 있도록 하는 StackTrace와 같은 속성을 사용할 수 있습니다.

 

단, StackTrace 속성을 사용하는 경우 정확하게 예외가 발생한 메서드를 파악하려면 해당 프로그램을 Debug로 빌드해야 합니다. Release로 하는 경우 컴파일과정에서 소스코드의 구조가 일부 변경될 수 있기 때문에 StackTrace의 정보는 정확하지 않을 수 있습니다.

 

catch에서 Exception예외를 직접받게되면 try catch안에 발생된 모든 예외를 처리하게 됩니다. 따라서 만약 예외가 발생되는 형태마다 다른 처리를 하고자 한다면 원하는 형식에 맞는 예외타입을 직접 지정해야 합니다.

 

try
{
    string s = "abc";
    string a = s.ToString();

    int[] i = new int[5] { 0, 1, 2, 3, 4 };
    i[5] = 5;
}
catch (NullReferenceException e) //객체참조 오류의 경우 처리부분
{
    Console.WriteLine("참조" + e.Message);
}
catch (IndexOutOfRangeException e) //배열 인덱스 오류의 경우 처리부분
{
    Console.WriteLine("배열" + e.Message);
}
catch (Exception e) {
    Console.WriteLine("그외" + e.Message);
}

 

오류가 발생하든 발생하지 않든 무조건 실행되어야 하는 처리가 필요하다면 finally 를 사용해야 합니다.

 

try
{
    string s = "abc";
    string a = s.ToString();

    int[] i = new int[5] { 0, 1, 2, 3, 4 };
    i[5] = 5;
}
catch (NullReferenceException e) //객체참조 오류의 경우 처리부분
{
    Console.WriteLine("참조" + e.Message);
}
catch (IndexOutOfRangeException e) //배열 인덱스 오류의 경우 처리부분
{
    Console.WriteLine("배열" + e.Message);
}
catch (Exception e)
{
    Console.WriteLine("그외" + e.Message);
}
finally {
    Console.WriteLine("오류발생 유무와 관련없이 무조건 실행");
}

 

지금까지는 이미 정해진 예외(System.NullReferenceException, IndexOutOfRangeException등)를 사용했습니다. 그러나 필요하다면 자신만의 예외타입을 직접 만들 수도 있습니다.

 

class PhoneNumberException : Exception
{
    public PhoneNumberException(string msg) : base(msg) { }
}

 

별도의 예외클래스를 만들고자 하는 경우에는 Exception 으로 부터 클래스를 상속받도록 합니다.

 

try
{
    string s = "010-1234-";

    if (s.Length < 12) {
        PhoneNumberException e = new PhoneNumberException("전화번호 오류입니다.");
        throw e;
    }
}
catch (PhoneNumberException e)
{
    Console.WriteLine(e.Message);
}

 

예제로 만든 예외클래스는 메세지를 전달받아 base 즉, 부모가 되는 Exception에 해당 메세지를 전달하는 단순한 역활만 수행합니다.

 

C# 6.0부터는 이 예외에 조건을 추가할 수 있게 되었습니다.

 

int i = 0;
string input = string.Empty;

try
{
    input = Console.ReadLine();
    i = int.Parse(input);
}
catch (Exception e)
{
    Console.WriteLine($"예외발생!{e.Message}");
}

 

위의 경우 입력값이 변수 i의 타입에 맞지 않으면 예외를 발생시키도록 처리되어 있는데 만약 input이 'abc'일때만 예외를 타도록 하고 싶다면

 

int i = 0;
string input = string.Empty;

try
{
    input = Console.ReadLine();
    i = int.Parse(input);
}
catch (Exception e) when (input.Trim() == "abc")
{
    Console.WriteLine($"abc - 예외발생!{e.Message}");
}
catch (Exception e)
{
    Console.WriteLine($"예외발생!{e.Message}");
}

 

catch뒤에 when을 주고 원하는 조건을 명시하면 됩니다. 이렇게 하면 해당 조건에 부합하는 경우에만 아래 예외로 처리가 이전됩니다. 만약 조건이 복잡하다면 메서드로 분리하여 조건을 지정할 수도 있습니다.

 

static bool check_input(string content)
{
    return (content.Trim() == "abc" ? true : false);
}

 

int i = 0;
string input = string.Empty;

try
{
    input = Console.ReadLine();
    i = int.Parse(input);
}
catch (Exception e) when (check_input(input))
{
    Console.WriteLine($"abc - 예외발생!{e.Message}");
}
catch (Exception e)
{
    Console.WriteLine($"예외발생!{e.Message}");
}

 

또한 catch 나 finally 안에서 await를 활용한 비동기 호출이 가능해졌습니다.

 

try
{
    input = Console.ReadLine();
    i = int.Parse(input);
}
catch (Exception e) when (check_input(input))
{
    Console.WriteLine($"abc - 예외발생!{e.Message}");
}
catch (Exception e)
{
    await Log(e);
}

 

static Task Log(Exception e)
{
    return Task.Factory.StartNew(() => {
        Console.WriteLine(e.Message);
    });
}

'Programming > .NET' 카테고리의 다른 글

[C#] HttpWebRequest / HttpWebResponse / WebClient  (0) 2019.03.12
[C#] 시프트(Shift) 연산자  (0) 2019.03.05
[C#] 예외처리(try ~ catch)  (0) 2019.02.19
[C#] ? / Nullable  (0) 2019.02.12
[C#] partial  (0) 2019.02.07
[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
0 0
Programming/.NET

ture와 false두가지 값을 갖는 bool형식을 생각해 봅시다. 어느 웹사이트에서 이벤트메일을 수신할 것인지를 지정하는 형식으로 다음과 같이 구현했다면

 

class Member
{
    public bool ReceiveMail
    {
        get;
        set;
    }
}

 

이것은 실제 문제가 될 수 있습니다. 왜냐하면 누군가가 메일수신여부를 아예 설정하지 않았다면 그것은 '네' 또는 '아니오'가 아닌 '미설정'이라는 중간 상태를 가지기 때문입니다. 하지만 bool형식에 중간값은 없으므로 이럴때 Nullable이 사용될 수 있습니다.

 

public Nullable<boolReceiveMail
{
    get;
    set;
}

 

Nullable은 null값을 가지게 하기 위한 것으로 본래는 Nullable<T>로 사용됩니다. 따라서 Nullable<int>와 같은 표현도 가능합니다.

 

Member m = new Member();

if (m.ReceiveMail.HasValue)
    Console.WriteLine(m.ReceiveMail.Value);

 

값을 확인하는 방법은 우선 HasValue속성으로 실제 값을 가지고 있는지를 먼저 확인한 후 Value속성을 통해 값을 가져오면 됩니다.

 

?는 Nullable의 요약된 표현입니다.

 

public boolReceiveMail
{
    get;
    set;
}

 

Nullable과 ?는 완전히 같은 역활을 하며 ?로 하면 실제 컴파일과정에서 코드가 Nullable로 바뀌게 됩니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 시프트(Shift) 연산자  (0) 2019.03.05
[C#] 예외처리(try ~ catch)  (0) 2019.02.19
[C#] ? / Nullable  (0) 2019.02.12
[C#] partial  (0) 2019.02.07
[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
Debug / Release  (0) 2019.01.15
0 0
Programming/.NET

partial은 하나의 큰 클래스를 2개의 파일이나 부분으로 나누고자 할때 사용됩니다.

 

partial class myValue1
{
    public int i = 100;
}

partial class myValue1
{
    public int j = 200;
}

 

이렇게 하면 물리적으로 2개의 파일로 나누거나 특정한 단위로 하나의 클래스를 나누어 작성할 수도 있습니다.

 

static void Main(string[] args)
{
    myValue1 mv = new myValue1();
    Console.WriteLine(mv.i);
    Console.WriteLine(mv.j);

    Console.Read();
}

 

'Programming > .NET' 카테고리의 다른 글

[C#] 예외처리(try ~ catch)  (0) 2019.02.19
[C#] ? / Nullable  (0) 2019.02.12
[C#] partial  (0) 2019.02.07
[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
0 0
Programming/.NET

속도가 느린 입출력장치와의 통신이나 혹은 다른 메서드를 호출했을때 처리가 완료되기를 기다리지 않고 현재 스레드에서 곧장 제어를 넘겨받아 작업을 계속진행하는 방식을 비동기 호출(asynchronous call)이라고 합니다.

 

class Program
{
    delegate void callMethod();

    static void Main(string[] args)
    {
        callMethod cm = new callMethod(myMethod);
        IAsyncResult ar = cm.BeginInvoke(null, null);

        Console.WriteLine("현재 스레드 완료");

        cm.EndInvoke(ar);

        Console.Read();
    }

    static void myMethod()
    {
        Console.WriteLine("메서드 실행");

        Thread.Sleep(10000);

        Console.WriteLine("완료");
    }
}

 

위 예제는 메서드의 실행을 호출하되 비동기로 호출하는 방식을 보여주고 있습니다. 우선 메서드의 형식에 대한 델리게이트(delegate)를 선언하고 해당 델리게이트의 BeginInvoke로 메서드를 호출합니다.

 

EndInvoke는 메서드에서 반환값을 받는 경우에 필요하지만 그렇지 않다면 생략이 가능합니다. 다만 반환값이 없더라도 호출해줄것을 권장합니다.

 

BeginInvoke가 호출되면 CPU는 스레드풀에서 대기중인 스레드를 불러와 메서드의 실행을 맡김으로서 비동기를 실현합니다. 그리고 메서드가 종료되면 해당 스레드는 스레드의 상태를 나타내느 상태값을 Signal로 바꾸게 되는데 BeginInvoke에서 반환하는 IAsyncResult타입의 AsyncWaitHandle속성이 스레드의 상태를 대기하는 EventWaitHandle과 같으므로 필요시 이 속성을 사용해 스레드의 실행을 동기화할 수 있습니다.

 

static void Main(string[] args)
{
    callMethod cm = new callMethod(myMethod);
    IAsyncResult ar = cm.BeginInvoke(null, null);
    ar.AsyncWaitHandle.WaitOne();

    Console.WriteLine("현재 스레드 완료");

    cm.EndInvoke(ar);

    Console.Read();
}

 

위와 같이 메서드를 비동기로 호출할때 만약 메서드에 특정 값을 매개변수로 전달해야 한다면 다음과 같이 처리할 수 있습니다.

 

class Program
{
    delegate int callMethod(int i, int j);

    static void Main(string[] args)
    {
        callMethod cm = new callMethod(myMethod);
        IAsyncResult ar = cm.BeginInvoke(10, 20, null, null);

        Console.WriteLine("현재 스레드 완료");

        int sum = cm.EndInvoke(ar);

        Console.WriteLine("결과 : " + sum);
        Console.Read();
    }

    static int myMethod(int i, int j)
    {
        Thread.Sleep(10000);

        return i + j;
    }
}

 

myMethod에서는 2개의 정수값을 받아 이를 합한 결과를 반환하도록 하였습니다. 그에 따라 델리게이트(delegate)도 같은 형식으로 수정하고 BeginInvoke호출시 필요한 매개변수를 추가해줍니다. 결과값은 이전에 말씀드린대로 EndInvoke를 통해 받을 수 있습니다.

 

비동기 메서드 호출이후 별도의 메서드를 호출하는 콜백메서드가 필요하다면 원하는 메서드를 정의하고 BeginInvoke의 세번째 매개변수에 해당 메서드를 지정해주기만 하면 됩니다.

 

class Program
{
    delegate int callMethod(int i, int j);

    static void Main(string[] args)
    {
        callMethod cm = new callMethod(myMethod);
        IAsyncResult ar = cm.BeginInvoke(10, 20, methodComplete, null);

        Console.WriteLine("현재 스레드 완료");

        int sum = cm.EndInvoke(ar);

        Console.WriteLine("결과 : " + sum);
        Console.Read();
    }

    static int myMethod(int i, int j)
    {
        Thread.Sleep(10000);

        return i + j;
    }

    static void methodComplete(IAsyncResult ar)
    {
        Console.WriteLine("완료");
    }
}

 

지금까지는 임의의 사용자 메서드를 어떻게 비동기로 호출하는지를 알아봤는데 .NET의 몇몇클래스에도 비동기로 호출할 수 있는 장치가 마련되어 있습니다. 예를 들어 파일을 스트림(Stream)으로 읽어들이는 FileStream클래스의 경우

 

static void Main(string[] args)
{
    using (FileStream fs = new FileStream("C:\\sampe.txt", FileMode.Open, FileAccess.Read)) {
        byte[] b = new byte[fs.Length];
        fs.Read(b, 0, b.Length);

        string s = Encoding.UTF8.GetString(b);
    }

    Console.Read();
}

 

일반적으로 위와 같이 사용할 수 있지만 파일이 너무 크거나 하는등의 이유로 파일의 내용을 다 읽을때까지 대기할 수 없다면 아래와 같이 비동기로의 호출이 가능합니다.

 

class Program
{
    delegate int callMethod(int i, int j);

    static void Main(string[] args)
    {
        FileStream fs = new FileStream("C:\\sample.txt", FileMode.Open, FileAccess.Read);
        byte[] b = new byte[fs.Length];
        fs.BeginRead(b, 0, b.Length, readComplete, b);

        Console.WriteLine("주 스레드 완료");
        Console.Read();
    }
    static void readComplete(IAsyncResult ar)
    {
        byte[] b = (byte[])ar.AsyncState;
        string s = Encoding.UTF8.GetString(b);
        Console.WriteLine(s);
    }
}

 

대부분의 클래스에서 비동기를 지원하는 경우 메서드는 Begin-- 으로 시작합니다. FileStream도 마찬가지 인데 Read메서드의 비동기 호출이 BeginRead이며 BeginRead 메서드를 호출할때 동작 완료시 사용될 메서드를 지정합니다.

'Programming > .NET' 카테고리의 다른 글

[C#] ? / Nullable  (0) 2019.02.12
[C#] partial  (0) 2019.02.07
[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
0 0
Programming/.NET

프로그램을 개발할때 오류가 발생하면 대부분 Visual Studio의 디버거 프로그램을 통해 문제가 되는 코드를 찾아 수정하는 방법으로 해결이 가능합니다.

 

static void Main(string[] args)
{
    int[] i = new int[] { 0, 1, 2, 3, 4 };
    i[5] = 0;
}

 

이 프로그램은 컴파일시에는 정상적으로 처리되지만 정작 실행할때는 오류가 발생될것입니다. 그러나 다음과 같이 오류가 발생되는 정확한 소스코드 라인을 알려주기에 곧장 문제되는 부분을 찾아 수정하는 것이 가능합니다.

 

 

빌드하고 난 이후의 실행파일을 실행했을 뿐인데 이러한 소스코드상의 정보를 출력해 디버깅이 가능하도록 할 수 있는 이유는 프로젝트 자체를 Debug 모드로 빌드했기 때문입니다. 프로그램을 Debug모드로 빌드하게 되면 컴파일러는 소스코드를 전혀 건드리지 않으면서 소스코드관련 정보를 가지고 있는 PDB(program database)파일을 생성하게 되고, 문제가 발생했을 경우 CLR이 PDB파일을 로드하여 소스코드의 정보를 알려주는 것입니다.

 

그렇다면 소스코드를 건드리지 않는다는건 무슨 뜻일까요?

 

CLR은 프로그램이 실행될때의 성능에 맞게 소스코드 자체에 최적화를 적용하게 되는데 이는 빌드되는 과정에서 '최적화'라는 단계의 작업에 해당합니다. 예를 들어

 

static void Main(string[] args)
{
    int[] i = new int[] { 0, 1, 2, 3, 4 };
    SetArray(i);
}

static void SetArray(int[] i)
{
     i[5] = 0;
}

 

위와 같은 소스코드는 최적화를 거치면 아래와 같이 바뀔것입니다.

 

static void Main(string[] args)
{
    int[] i = new int[] { 0, 1, 2, 3, 4 };
    i[5] = 0;
}

 

이렇게 되는 이유는 굳이 메서드로 분리된 코드를 유지하면 매개변수로 값을 전달하고 해당 메서드를 호출해야 하는 등의 과정이 필요하기 때문입니다. 따라서 성능상 차라리 메서드의 내용을 호출자에 통째로 넣어버리는 것이 더 빠르기 때문에 위와 같은 결과가 발생하는 것입니다.(물론 모든 상황에서 이러한 동작이 수행된다고는 보장할 수 없지만) 결국 코드의 내용이 달라져 버린다는 것이 핵심이고 이런 처리를 수행하는 것은 프로젝트의 빌드모드를 Release로 했을경우에 해당합니다.

 

프로그램을 개발하는 개발단계에서 Release로 빌드하지 않는 이유는 바로 이 때문입니다. 최적화과정에서 소스코드를 변경해 버리면 디버거는 엉뚱한 소스코드의 라인을 알려주며 오류정보를 전달할 수 있기 때문입니다. 설령 PDB파일이 있다고 하더라도 PDB는 최적화 이전의 소스코드정보를 갖고 있기 때문에 별 도움이 되지 않습니다.

 

좀 다른 얘기지만 프로그램을 만들때 특정 조건의 실행여부를 판단하기 위해 종종 Console이나 혹은 MessageBox 클래스를 통해 메세지를 표시하곤 하는데 화면에 원하는 내용을 출력하기 보다는 Debug나 Trace를 사용하면 Visual Studio의 출력창을 통해 좀더 깔끔하게 메세지를 표시할 수 있습니다.

 

static void Main(string[] args)
{
    Debug.WriteLine("실행중.");
    Trace.WriteLine("실행중.");
}

 

간혹 프로그램을 빌드하는 이 2가지 모드가 무시되는 경우가 있으나 개발단계에서는 프로그램의 정확한 디버깅을 위해 Debug모드로, 사용자에게 배포할때는 Release모드로 빌드하는 습관을 들이는 것이 좋습니다.

'Programming > .NET' 카테고리의 다른 글

[C#] partial  (0) 2019.02.07
[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
0 0
Programming/.NET

1. Model 이용하기

 

public class Employee
{
    public string Name;
    public int Age;
    public string Department;
    public DateTime Doe;
}

▶ Model

 

public ActionResult Index()
{
    Employee em = new Employee() { Name = "홍길동"Department = "기획팀"Age = 33, Doe = new DateTime(2016, 12, 05) };

    return View(em);
}

▶ Controller

 

@model test.Models.Employee

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table style="border:solidborder-width:1px;">
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                <tr><td>이름</td><td>@Model.Name</td></tr>
                <tr><td>나이</td><td>@Model.Age</td></tr>
                <tr><td>부서</td><td>@Model.Department</td></tr>
            </tbody>
        </table>
    </div>
</body>
</html>
▶ View

 

2. ViewBag 이용하기

 

public ActionResult Index()
{
    Employee em = new Employee() { Name = "홍길동"Department = "기획팀"Age = 33, Doe = new DateTime(2016, 12, 05) };

    ViewBag.Title = "사원테이블";
    ViewBag.Width = "5px";
    ViewBag.Resignation = false;
    ViewBag.IsMarriage = true;
    ViewBag.IsExecutive = null;

    return View(em);
}

▶ Controller

 

@model test.Models.Employee

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        퇴사 : <input type="checkbox" checked="@ViewBag.Resignation" /><br />
        결혼 : <input type="checkbox" checked="@ViewBag.IsMarriage" /><br />
        임원 : <input type="checkbox" checked="@ViewBag.IsExecutive" />
        <table style="border:solid; border-width:@ViewBag.Width;">
            <caption>@ViewBag.Title</caption>
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                <tr><td>이름</td><td>@Model.Name</td></tr>
                <tr><td>나이</td><td>@Model.Age</td></tr>
                <tr><td>부서</td><td>@Model.Department</td></tr>
            </tbody>
        </table>
    </div>
</body>
</html>
▶ View

 

참고로 checkbox처리의 경우 true만 checked 처리되고 false나 null은 아예 속성처리가 무시됩니다.

 

3. 구문

 

<span>

    @if ((bool)ViewBag.Resignation)
    {
        @: 이직
    }
    else {
        @: 재직
    }
</span>

▶ View

 

Razor 구문은 @문자로 시작합니다. 참고로 문자열은 기본적으로 C#문으로 인식하는데 구문안에서 단순한 문자열을 출력할때는 @: 기호를 사용합니다.

 

public ActionResult Index()
{
    Employee[] em = {
        new Employee() { Name = "홍길동"Department = "기획팀"Age = 33, Doe = new DateTime(2016, 12, 05) },
        new Employee() { Name = "홍길순"Department = "경영팀"Age = 25, Doe = new DateTime(2004, 9, 8) },
        new Employee() { Name = "홍길남"Department = "영업팀"Age = 40, Doe = new DateTime(2012, 4, 17) }
    };

    return View(em);
}

▶ Controller

 

@model test.Models.Employee[]

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table style="border:solid; border-width:@ViewBag.Width;">
            <caption>@ViewBag.Title</caption>
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                @foreach (test.Models.Employee em in Model) {
                    <tr><td>이름</td><td>@em.Name</td></tr>
                    <tr><td>나이</td><td>@em.Age</td></tr>
                    <tr><td>부서</td><td>@em.Department</td></tr>
                }
            </tbody>
        </table>
    </div>
</body>
</html>
▶ View

 

배열과 같은 모델의 데이터를 열거할때는 @model 에서 모델형식을 Employee[] 처럼 배열로 지정해야 합니다.

 

4. Namespace

 

@using test.Models
@model test.Models.Employee[]

@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        <table style="border:solid; border-width:@ViewBag.Width;">
            <caption>@ViewBag.Title</caption>
            <thead>
                <tr><th>항목</th><th></th></tr>
            </thead>
            <tbody>
                @foreach (Employee em in Model) {
                    <tr><td>이름</td><td>@em.Name</td></tr>
                    <tr><td>나이</td><td>@em.Age</td></tr>
                    <tr><td>부서</td><td>@em.Department</td></tr>
                }
            </tbody>
        </table>
    </div>
</body>
</html>
▶View

 

Namespace도 일반 C#구문과 마찬가지로 using을 사용합니다. 다만 Razor작성 규칙에 따라 @를 붙여야 합니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 비동기 호출(asynchronous call)  (0) 2019.01.23
Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
0 0
Programming/javascript jquery
$(document).ready(function(){
    alert('hello');
});


ready는 WEB문서의 DOM이 생성되면 실행되며 순서상 load보다 먼저입니다.


ready를 줄인 아래와 같은 구문도


$(function() {
  // Handler for .ready() called.
});


ready와 동일입니다.


$(window).load(function(){
    alert('hello');
});


모든 DOM이 생성되고 이미지와 같은 DOM안에 포함된 요소들까지 모두 Loading이 완료되고 나면 실행됩니다. 순서상 ready보다 다음입니다.

'Programming > javascript jquery' 카테고리의 다른 글

[jquery] ready와 load의 차이  (0) 2018.12.26
[javascript] 키입력 방지 예제  (0) 2013.12.03
0 0
Programming/.NET

보통 URL을 생성할때는 다음과 같이 하던게 일반적인 방법이었습니다.

 

<a href="/home/default.htm"></a>

 

그런데 ASP.NET MVC에서 URL을 생성할때는 URL을 생성하는 관련 메서드를 호출함으로서 이루어 집니다.

 

@Html.ActionLink("go""myPage")

 

가장 일반적으로는 @Html 의 ActionLink 헬퍼메서드를 사용하는 것입니다. 만약 이 메서드가 Home컨트롤러의 Index뷰에서 사용되었다면

 

<a href="/home/myPage">go</a>

 

위 형태의 링크를 생성할 것입니다. 이 방법이 사용되는 중요한 이유는 어떤 이유에 의해서 URL의 형태가 바뀌게 되면 사람이 직접 기존의 링크를 수정하지 않아도 해당 라우팅정보가 그대로 반영될 수 있기 때문입니다. 예를 들어 기존에 /Home/Index를 가리키던 라우팅정보가

 

routes.MapRoute("myRoute""myUrl/{action}"new { controller = "Home" });

 

이러한 라우트설정에 의해서 바뀌게 되면 ActionLink메서드는 바뀐 라우팅정보를 수용하여 다음처럼 링크경로를 변경하고 표시할 것입니다.

 

<a href="/myurl/myPage">go</a>

 

이와 같은 작동방식은 액션메서드의 속성에 라우트정보가 정의되어 있는 상태에서도

 

[Route("~/Default")]
public ActionResult MyPage()
{
    return View();
}

 

동일하게 라우팅정보를 적용시켜 아래와 같은 형태의 URL을 생성하게 됩니다.

 

<a href="/Default">go</a>

 

ActionLink 메서드는 기본적으로 현재의 컨트롤러내부를 가리키게 되는데 만약 다른 컨트롤러의 액션메서드를 호출하고자 한다면

 

@Html.ActionLink("go""myPage""myControl")

 

예제에서 처럼 직접 컨트롤러를 지정하면 됩니다.

 

<a href="/myControl/myPage">go</a>

 

컨트롤러와 액션이외에 특정 세그먼트와 값을 추가적으로 전달해야할 경우도 있는데 이럴때는 익명 형식을 통해 해당 세그먼트와 그 값을 정의해 두면 됩니다.

 

@Html.ActionLink("go""myPage"new { id = "cliel" })
<a href="/Home/myPage/cliel">go</a>

 

이렇게 처리될 수 있는 이유는 기본적으로 라우트정보가 설정되는 RouteConfig.cs 파일에 다음과 같이 라우트정보가 정의되어 있기 때문입니다.

 

routes.MapRoute(
    name"Default",
    url"{controller}/{action}/{id}",
    defaultsnew { controller = "Home"action = "Index"id = UrlParameter.Optional }
);

 

만약 id 세그먼트에 대한 정의가 라우정보에 존재하지 않았을때 같은 방식으로 ActionLink 메서드를 사용하게 되면 id는 세그먼트가 아닌 단순한 get값을 추가해 줍니다.

 

<a href="/Home/myPage?id=cliel">go</a>

 

ActionLink는 단순히 우리가 필요한 링크를 만들 수 있을 뿐만 아니라 HTML표현에 필요한 여러가지 속성들을 설정할 수도 있습니다.

 

@Html.ActionLink("go""myPage"nullnew { id = "mylink"@class = "mystyle" })
<a class="mystyle" id="mylink" href="/Home/myPage">go</a>

 

익명형식으로 설정할 속성의 이름과 속성값을 지정하고 있는데 참고로 ASP.NET MVC에서는 C#에서 특정한 예약어를 사용해야할때 앞에 @문자를 붙여 쓸 수 있습니다. 예제에서 class의 속성을 설정하기 위해 @class로 표현한것은 바로 이때문입니다.

 

물론 이외에도 다음과 같이 필요한 모든 값을 ActionLink에 전달하면 링크가 생성되는 형태를 세부적으로 다룰 수 있습니다.

 

@Html.ActionLink("go""myPage""Home""https""www.naver.com""target"new { id = "cliel" } ,new { id = "mylink"@class = "mystyle" })
<a class="mystyle" id="mylink" href="https://www.naver.com/Home/myPage/cliel#target">go</a>

 

ActionLink 메서드와 비슷하게 Action 메서드도 존재하는데 이 메서드는 링크를 생성하는게 아닌 http://cliel.com/Home/Index 와 같은 단순한 문자열만을 생성합니다.

 

뿐만 아니라 URL은 특정 라우트를 대상으로 URL을 생성할 수도 있어서 예를 들어 RouteConfig.cs 파일에 기본적으로 다음과 같은 등록된 라우트정보가 있다면

 

@Html.RouteLink("go""Default"new { id = "cliel" })

 

위와 같이 라우트명을 지정해 URL을 해당 라우트를 기반으로 하여 생성할 수 있습니다.

 

/Home/Index/cliel

 

RouteLink 메서드에서는 별도의 컨트롤러와 메서드를 지정하지 않았지만 이는 라우트에 등록된 기본값으로 대체될 수 있고 id세그먼트는 지정된 값으로 대체될 수 있기 때문입니다.

 

이제 까지살펴본 ActionLink 메서드는 뷰에서 사용이 가능한 메서드인데 때로는 컨트롤러내부에서 URL을 생성해야할 필요가 있습니다. 이럴때는 Url 객체의 Action 과 RouteUrl 메서드를 사용할 수 있습니다.

 

string url = Url.Action("Index""Home"new { id = "cliel" });

 

우선 Action 메서드는 ActionLink 메서드와 사용법이 완벽히 일치하므로 별다른 설명이 필요없을 것입니다.

 

string url = Url.RouteUrl(new { controller = "Home"action = "MyPage" });

 

RouteUrl 메서드는 RouteConfig.cs 에서 라우트를 설정할때와 비슷한 개념과 방식으로 URL을 생성할 수 있습니다.

'Programming > .NET' 카테고리의 다른 글

Debug / Release  (0) 2019.01.15
[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
[C#] Thread  (0) 2018.11.06
0 0
Programming/.NET

비트 단위의 논리 연산을 수행합니다.

 

int i = 10;

int bit_and = i & 3;
int bit_or = i | 3;
int bit_xor = i ^ 3;
int bit_not = ~i;

Console.WriteLine("Result : {0} | {1}", bit_xor, bit_not);

 

& 는 and 연산으로서 같은 값인것만 1로 처리합니다. 그래서 10의 값이 1010과 3의 값인 0011을 연산하면

 

1010
0011
----
0010

 

이 되어 결과는 3이 됩니다.

 

| 는 or 연산입니다. 따라서 둘중 어느 하나라도 1이 있으면 결과는 1이 됩니다.

 

1010
0011
----
1011

 

^ 는 Xor 연산으로 둘의 값이 1이든 0이든 서로 틀려야만 1이 됩니다.

 

1010
0011
----
1001

 

마지막으로 ~는 보수입니다. 피연산자의 모든 값을 반대로 뒤집습니다.

 

1010
----
0101

 

다만 이 경우 부호를 나타내는 최상위 비트도 반대로 처리하기 때문에 결과는 -값이 됩니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] Razor  (0) 2019.01.08
[ASP.NET MVC] URL 다루기  (0) 2018.12.18
[C#] 비트(bit) 연산자  (0) 2018.11.20
[ASP.NET MVC] 액션 메서드(Action Method)  (0) 2018.11.13
[C#] Thread  (0) 2018.11.06
[C#] MemoryStream / StreamWriter / StreamReader / BinaryWriter / BinaryReader  (0) 2018.10.31
0 0
Programming/.NET

사용자가 요청한 URL에 따라 컨트롤러 내부에 해당하는 액션메서드(Action Method)를 찾아 사용자에게 보여줍니다.

 

public ActionResult Index()
{
    return View();
}

 

액션메서드를 기본적으로 같은 이름의 뷰를 찾기 때문에 액션 메서드가 Index라면 Index.cshtml과 같은 뷰를 렌더하게 됩니다.

 

[ActionName("myIndex")]
public ActionResult MyPage()
{
    return View("MyPage");
}

 

액션 메서드에 ActionName 속성을 사용하게 되면 설정 값에 따라 액션메서드 이름을 재정의하게 됩니다. 따라서 위 예제에서는 MyIndex로 접근시 MyPage 액션메서드를 실행하게 됩니다. 다만 MyIndex로 요청을 받으면 뷰도 기본적으로 MyIndex뷰를 찾게 되므로 MyIndex라는 뷰를 따로 생성하거나 아니면 View 메서드를 호출할때 보여줄 수 있는 다른 뷰를 설정하는 방법으로 적절히 대처해야 합니다.

 

public ActionResult MyPage()
{
    return View();
}

[HttpPost]
public ActionResult MyPage(string idstring name)
{
    return View();
}

 

같은 이름의 액션메서드라도 요청방식에 따라 다른 액션메서드가 호출될 수 있도록 할 수 있습니다. 예제에서는 [HttpPost] 속성을 설정해 두번째 MyPage 액션 메서드는 Post요청에만 응답할 수 있도록 하고 있습니다. 이 외에도 HttpGet이나 HttpPut과 같은 속성을 설정할 수 있습니다.

 

[NonAction]
public ActionResult MyPage(string idstring name)
{
    return View();
}

 

NonAction 속성은 해당 메서드가 액션 메서드로서 인식될 수 없도록 합니다.

 

만약 요청으로 들어온 URL에서 어떤 액션 메서드를 반환해야 할지 찾을 수 없으면 MVC 프레임워크는 기본적으로 HTTP 404 오류를 반환합니다. 하지만 HandleUnknownAction 메서드를 재정의하면 알 수 없는 요청에 대해 다른 대체적인 방법으로 다음과 같이 처리할 수 있습니다.

 

protected override void HandleUnknownAction(string actionName)
{
    Response.Write("Error!!");
}

0 0
Programming/.NET

실행중인 프로그램은 하나의 프로세스로 볼 수 있고 프로세스는 시작될때 기본적으로 하나의 스레드를 생성하게 됩니다. 스레드가 없는 프로세스는 존재하지 않습니다. 프로세스가 무엇인가 작업을 수행하려면 반드시 해당 작업을 수행하는 스레드를 생성해야 합니다. 스레드는 프로세스의 작업단위입니다. 예를 들어 사용자로부터 특정값을 입력받아 합계를 계산하는 아래와 같은 프로그램이 있습니다.

 

static void Main(string[] args)
{
    Console.Write("첫번째 값 : ");
    string i = Console.ReadLine();

    Console.Write("두번째 값 : ");
    string j = Console.ReadLine();

    int sum = int.Parse(i) + int.Parse(j);

    Console.WriteLine("결과 : {0}", sum);

    Console.Read();
}

 

이 프로그램은 입력과 계산, 결과출력에 대한 일련의 작업이 main 메서드에 정의되어 있고 따라서 메서드 실행을 위한 스레드를 생성해야 하는 것입니다. 프로세스는 반드시 하나이상의 스레드를 생성하며 기본적으로 생성되는 최초의 스레드를 주 스레드(main thread)라고 합니다.

 

CPU는 각 프로세스마다 생성한 스레드를 하나씩 돌아가며 실행합니다. 물론 단일 CPU는 하나의 스레드만을 실행할 수 있었지만 내부에 4코어, 8코어처럼 멀티코어를 가진 CPU라면 코어수만큼의 여러 스레드를 동시에 실행할 수도 있을 것입니다. 만약 4코어라면 4개의 스레드를 동시에 순회하면서 실행하는 것이 가능합니다.  그러나 일반적으로 CPU가 실행해야할 스레드의 수는 수십개나 그 이상이기에 모든 스레드를 동시에 실행할 수는 없습니다. 4개나 8개의 스레드를 동시에 실행한다고 하더라도 이들 스레드를 실행한 뒤 종료될때까지 기다릴 수 없기에 시간을 조금씩 할당해서 대기중인 각 스레드를 모두 돌아가며 실행해야 합니다.

 

스레드안에는 CPU가 읽어 실행될 명령어집합이 존재하는데 이를 스레드문맥(thread context)이라고 합니다. 메서드로 비유하자면 메서드의 시작부터 마지막까지 작성된 일련의 코드에 해당하는 것입니다. CPU는 스레드의 문맥을 읽어 실행하다가 다른 스레드를 실행하기 직전에 현재 실행중인 스레드의 상태를 스레드문맥에 저장합니다. 그런뒤 다른 스레드로 실행을 이동하게 되는데, 현재상태를 문맥에 저장해야 하는 이유는 스레드의 실행계획이 담긴 스케쥴링에 따라 CPU가 다시 해당 스레드를 실행할때 이전에 저장된 상태를 스레드문맥으로부터 읽어들여서 마치 이전부터 계속 실행중이었던것처럼 스레드의 실행을 이어나갈 수 있기 때문입니다.

 

Thread는 기본적으로 현재 실행중인 스레드에 접근할 수 있는 몇가지 속성을 제공하며 필요한 경우 새로운 새로운 스레드를 생성할 수도 있습니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.Start();

    Console.Read();
}

static void myThread()
{
    Console.WriteLine("hahaha");
}

 

스레드를 생성할때는 스레드를 통해 실행할 메서드를 제공해야 합니다. 이때 메서드는 스레드로서 실행하는 하나의 작업단위, 즉. 명령의 집합이라고 볼 수 있습니다.

 

스레드가 생성되 Start() 메서드가 호출되면 이제 하나의 프로세스는 2개의 스레드가 생성된 것이며 CPU에 의해 각 스레드는 별도의 작업으로서 실행될 것입니다.

 

여기서 스레드가 왜 필요한지는 스레드의 작업단위인 메서드를 보면 알 수 있습니다. 현재 메서드의 명령을 그대로 실행하면서 동시에 다른 작업을 해야하는 경우가 발생하는 것입니다. 예를 들어

 

static void Main(string[] args)
{
    while (true) {
        string s = Console.ReadLine();

        if (s.Trim() == "exit")
            return;

        Console.WriteLine(s);
    }
}

 

위와 같은 프로그램의 경우 사용자로부터 값을 입력받아 입력받은 값을 그대로 뿌려주도록 되어 있습니다. 프로그램을 종료하려면 'exit'를 입력하면 됩니다. 그런데 좀 억지스럽지만 이 프로그램에서 3초씩마다 뭔가를 계산하고 출력하는 기능을 추가해야 한다고 가정해 보겠습니다. 그러면서 본래 프로그램이 수행하는 작업은 계속되어야 합니다.

 

static void Main(string[] args)
{
    myThread();

    while (true) {
        string s = Console.ReadLine();

        if (s.Trim() == "exit")
            return;

        Console.WriteLine(s);
    }
}

static void myThread()
{
    while (true) {
        Thread.Sleep(3000);
        Console.WriteLine("처리됨");
    }
}

 

위 예제는 스레드를 사용하지 않고 만든 경우인데 이렇게 해서는 제대로 프로그램을 사용할 수 없게 됩니다. 주 스레드는 첫명령인 myThread() 메서드를 호출하는데 이 메서드는 무한대로 3초마다 무엇인가를 처리하고 결과를 뿌려주기 때문입니다. 주 스레드가 이 작업에 매달리게 되고 결국 주 스레드는 하위의 while문을 실행할 여지가 없어지게 됩니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.Start();

    while (true) {
        string s = Console.ReadLine();

        if (s.Trim() == "exit")
            return;

            Console.WriteLine(s);

    }
}

 

바로 이럴때 스레드가 필요합니다. 주 스레드는 myThread() 메서드 실행을 위한 별도의 스레드를 생성한뒤 자신이 처리해야할 부분을 계속 처리할 수 있게 됩니다.

 

일단 스레드가 생성되 실행되면 해당 스레드를 생성한 프로세스는 모든 스레드가 종료되어야만 종료될 수 있습니다. 이런 형태의 스레드를 전경 스레드(foreground thread)라 하는데 바꾸어 말하면 프로세스가 실행을 종료하기 위해서는 모든 전경 스레드가 종료되어야 하는 것입니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.IsBackground = true;
    t.Start();
           

    Console.Read();
}

 

하지만 스레드의 IsBackground속성을 true로 설정하면 해당 스레드를 배경 스레드(background thread)로서 실행할 수 있게 됩니다. 배경 스레드는 전경 스레드와는 달리 해당 스레드의 종료여부와 상관없이 프로세스가 종료되면 같이 종료됩니다. 결국 프로세스가 종료되기 위한 스레드는 전경 스레드에 한정됩니다.

 

하지만 배경 스레드라 하더라도 다음과 같은 방법으로 스레드종료를 대기할 수 있습니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.IsBackground = true;
    t.Start();

    t.Join();

    Console.Read();
}

 

스레드 처리중 join() 메서드를 만나면 t에 해당하는 스레드가 종료될때까지 t스레드를 호출한 주 스레드는 무한정 대기하게 됩니다.

 

스레드를 통해 메서드를 호출할때 필요하다면 특정 값을 메서드에 전달할 수도 있습니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myThread);
    t.Start(100);

    Console.Read();
}

static void myThread(object o)
{
    Console.WriteLine("입력값 : " + o.ToString());
}

 

전달가능한 매개변수는 object형이어야 하며 단 하나의 매개변수만 가능합니다. 여러개의 매개변수를 전달해야 한다면 배열을 사용하거나 사용자 정의 타입을 구현해 필요한 데이터를 전달하면 됩니다.

 

static void Main(string[] args)
{
    myValue mv = new myValue();
    mv.param1 = 100;
    mv.param2 = 200;

    Thread t = new Thread(myThread);
    t.Start(mv);

    Console.Read();
}

static void myThread(object o)
{
    myValue mv = (myValue)o;
    Console.WriteLine("입력값 : " + mv.param2);
}

 

0 0
Programming/.NET

MemoryStream은 일련의 바이트 데이터를 메모리에 쓰고 읽는 기능을 제공합니다.

 

byte[] cb = BitConverter.GetBytes('a');
byte[] ib = BitConverter.GetBytes(1000);

MemoryStream ms = new MemoryStream();

 

ms.Write(cb, 0, cb.Length);
ms.Write(ib, 0, ib.Length);

 

예제에서는 문자형 a와 정수형 1000데이터를 바이트로 변환해 MemoryStream으로 메모리에 쓰고 있습니다. 메모리에 쓸때는 Write메서드를 사용하며 0부터 각 데이터 배열길이까지 전체를 쓰도록 하고 있습니다.

 

byte[] cb = BitConverter.GetBytes('a');
byte[] ib = BitConverter.GetBytes(1000);

MemoryStream ms = new MemoryStream();

 

ms.Write(cb, 0, cb.Length);
ms.Write(ib, 0, ib.Length);

ms.Position = 0;

byte[] outcb = new byte[2];
byte[] outib = new byte[4];

ms.Read(outcb, 0, 2);
ms.Read(outib, 0, 4);

char c = BitConverter.ToChar(outcb, 0);
int i = BitConverter.ToInt32(outib, 0);

Console.WriteLine(c);
Console.WriteLine(i);

 

메모리에 데이터를 쓴뒤 읽기 위해서는 Position 속성을 0으로 하여 MemoryStream의 내부 배열위치를 초기화해야 합니다. 그래야 다시 처음부터 데이터를 읽을 수 있습니다. 읽기 메서드는 Read이며 이때 데이터를 읽어들이기 위한 byte배열이 별도로 필요합니다.

 

byte[] cb = BitConverter.GetBytes('a');
byte[] ib = BitConverter.GetBytes(1000);

MemoryStream ms = new MemoryStream();

 

ms.Write(cb, 0, cb.Length);
ms.Write(ib, 0, ib.Length);

ms.Position = 0;

byte[] outData = ms.ToArray();

char c = BitConverter.ToChar(outData, 0);
int i = BitConverter.ToInt32(outData, 2);

Console.WriteLine(c);
Console.WriteLine(i);

 

위 예제는 MemoryStream의 ToArray() 메서드를 통해 byte배열 전체를 가져오도록한 것입니다. 이렇게 전체를 가져오고 나면 해당 byte배열에 원하는 데이터를 가져올때 가져올 데이터의 시작위치를 지정하는것에 주의(int i = BitConverter.ToInt32(outData, 2))하도록 합니다.

 

같은 방법으로 문자열을 MemoryStream에 담을 수도 있습니다.

 

MemoryStream ms = new MemoryStream();

byte[] b = Encoding.UTF8.GetBytes("Hello World!");
ms.Write(b, 0, b.Length);

 

문자열은 인코딩을 통해서 byte배열에 담는데 이걸 좀더 간단히 하기위해 StreamWriter를 사용할 수 있습니다.

 

MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);

sw.WriteLine("hello world!");
sw.Flush();

 

StreamWriter의 WriteLine메서드는 문자열쓰기 전용이며 정수형등 다른 단일 데이터의 경우 Write메서드를 사용합니다.

 

StreamWriter가 데이터를 쓸때는 곧장 MemoryStream에 기록하는 것이 아니라 내부적으로 갖고 있는 일정한 크기의 byte배열버퍼에 데이터를 기록합니다. 그러다 만약 일정한 크기에 도달하면 그때 MemoryStream에 기록하게 되는데 마지막에 있는 Flush() 메서드는 쓸데이터가 버퍼에 다 차지않더라도 MemoryStream에 접근해 데이터를 기록하도록 하는 메서드입니다.

 

MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms, Encoding.UTF8);

sw.WriteLine("hello world!");
sw.Flush();

ms.Position = 0;

StreamReader sr = new StreamReader(ms, Encoding.UTF8);
string s = sr.ReadToEnd();

Console.WriteLine(s);

 

StreamWriter로 쓴 데이터는 다시 StreamReader로 읽을 수 있습니다. 인코딩방식은 StreamWriter를 사용할때와 똑같이 지정해야 하며 데이터를 반드시 Position을 0으로 맞춰야 합니다.

 

MemoryStream ms = new MemoryStream();

BinaryWriter bw = new BinaryWriter(ms);
bw.Write("hello ");
bw.Write("world!" + Environment.NewLine);
bw.Flush();

 

BinaryWriter는 메모리에 2진데이터를 쓰기위한 전용입니다. BinaryWriter를 통해 데이터를 쓰는 경우 byte배열 중간중간에 다음 몇바이트가 데이터바이트인지를 나타내는 구분자가 추가됩니다. 때문에 뭔가 규격화된 데이터를 다루고자할때는 BinaryWriter를 사용합니다. 쓰기 작업에 대한 효휼성도 좋아서 성능이 우선시 되는 상황이라면 BinaryWriter도 좋은 선택이 될 수 있습니다.

 

MemoryStream ms = new MemoryStream();

BinaryWriter bw = new BinaryWriter(ms);
bw.Write("hello ");
bw.Write("world!" + Environment.NewLine);
bw.Flush();

ms.Position = 0;

BinaryReader br = new BinaryReader(ms);
string s = br.ReadString();
s += br.ReadString();

Console.WriteLine(s);

 

BinaryWriter로 작성된 데이터는 BinaryReader로 읽을 수 있습니다. 예제에서는 ReadString() 메서드를 2번 호출해 문자열데이터를 읽고 있는데 ReadString()메서드를 한번 호출할때마다 BinaryWriter를 사용할때 Write메서드로 작성한 데이터를 한묶음씩 읽어오기 때문입니다.

0 0
Programming/.NET

Visual Studio -> APS.NET MVC 프로젝트에서 cshtml파일을 생성할때나 혹은 기존 파일을 열지 못할때

 

%LocalAppData%\Microsoft\VisualStudio\14.0\ComponentModelCache

 

우선 Visual Studio를 닫고 이 폴더의 파일을 모두 삭제한 뒤 다시 실행해보자...

 

관련 오류 : Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))

0 0
Programming/.NET

TCP는 UDP에서 보장받지 못하는 신뢰성을 확보할 수 있습니다. 즉, 데이터를 송신하면 수신받은 측에서는 데이터를 받았다는 응답을 반드시 해야 하고 만약 응답이 없으면 데이터를 자동으로 다시 보내는등의 동작을 수행합니다. 뿐만 아니라 데이터의 송신순서와 수신순서가 일치하는등 높은 신뢰성을 유지합니다.

 

TCP도 UDP때와 마찬가지로 클라이언트에서 특정 내용을 보내면 서버에서 'server : '문자열을 붙여 회신하는 방식으로 구현해 보고자 합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

    t.Start();

Console.Read();

}

 

static void myMethod()
{
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {
        IPEndPoint iEp = new IPEndPoint(IPAddress.Any, 2020);
        socket.Bind(iEp);
        socket.Listen(10);

        while (true) {
            Socket client = socket.Accept();

            myData md = new myData();
            md.client = client;
            md.rec = new byte[1024];

            client.BeginReceive(md.rec, 0, md.rec.Length, SocketFlags.None, myRecCallBack, md);
        }
    }
}

 

서버에서는 우선 TCP통신을 위한 소켓을 생성합니다. UDP와는 다르게 SocketType을 Stream으로 하며 ProtocolType을 TCP로 지정합니다.

 

그런다음 모든 IP에 대해 2020포트로 데이터 수신을 대기하도록 합니다. 여기까지는 UDP와 비슷합니다. 그런데 TCP에서는 Bind를 하고 난뒤 Listen메서드를 호출해서 클라이언트의 접속을 대기해야 합니다. Listen메서드 호출시 전달된 정수값은 클라이언트를 최대보관할 수 있는 큐의 값입니다. 10이면 10개의 클라이언트접속을 보관하겠다는 의미입니다.

 

이제 while문을 통해 무한정 클라이언트의 접속을 처리하게 되는데 UDP에서는 클라이언트와 1:1로 연결되는 형태가 아니므로 데이터의 송수신을 위해 일일이 클라이언트의 접점을 필요로 했으나 TCP는 클라이언트와 직접적으로 1:1연결되는 형태이므로 클라이언트의 접점을 필요로 하지 않습니다. 대신 Socket의 Accept메서드를 통해 접속중인 클라이언트의 개체를 꺼냄으로서 각 클라이언트를 구별할 수 있고 통신도 이 Accept에서 나온 개체와 통신하게 됩니다. 소켓은 그저 클라이언트의 접속을 생성해줄 뿐입니다.

 

이제 BeginReceive메서드로 가져온 접속중인 클라언트에게서 데이터를 수신받기 위해 BeginReceive메서드를 호출합니다. 이는 비동기 호출입니다. 물론 동기호출로 Receive메서드를 사용할 수 있지만 해당 클라이언트와의 통신이 모두 마무리될때까지 다른 클라이언트는 무작정 대기상태에 들어가게 되므로 성능상 불리하게 됩니다.

 

static void myRecCallBack(IAsyncResult ar)
{
    myData md = (myData)ar.AsyncState;

    int i = md.client.EndReceive(ar);

    string s = Encoding.UTF8.GetString(md.rec, 0, i);

    byte[] sndB = Encoding.UTF8.GetBytes("server : " + s);
    md.client.BeginSend(sndB, 0, sndB.Length, SocketFlags.None, mySadCallBack, md.client);
}

 

비동기 호출에서 데이터가 수신완료될때 호출될 콜백메서드입니다. 이 콜백메서드에서는 접속중인 클라이언트와 데이터를 수신한 바이트배열 모두를 다루어야 하기에 비동기메서드호출시

 

public class myData {

public Socket client;
    public byte[] rec;
}

 

위와 같이 별도의 클래스를 만들어 client와 배열 모두를 담아 마지막 매개변수로 전달해야 합니다.

 

그러면 콜백에서는 해당 데이터타입으로 변환하여 수신을 마무리 짓고 받은 데이터를 문자열로 변환해 'server : '를 붙여 다시 BeginSend메서드로 데이터를 보냅니다.

 

static void mySadCallBack(IAsyncResult ar)
{
    Socket client = (Socket)ar.AsyncState;
    client.EndSend(ar);
    client.Close();
}

 

데이터를 보낼 메서드도 비동기로 호출하였기 때문에 이를 처리할 콜백메서드가 필요합니다. 이 콜백메서드는 데이터 송신을 모두 완료한뒤 호출되므로 송신을 마무리짓고 클라이언트와의 접속을 해제합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

 

        t.Start();

Console.Read();

}


static void myMethod(object o)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    IPAddress ip = IPAddress.Parse("192.168.20.126");
    EndPoint sEP = new IPEndPoint(ip, 2020);

    socket.Connect(sEP);

    byte[] send = Encoding.UTF8.GetBytes("hi server!!");
    socket.Send(send);

    byte[] rec = new byte[1024];
    int i = socket.Receive(rec);
    string s = Encoding.UTF8.GetString(rec, 0, i);

    Console.WriteLine(s);

    socket.Close();
}

 

클라이언트는 비교적 간소합니다. Stream와 Tcp로 소켓을 생성해 접속할 서버의 접점을 생성하고 Connect메서드를 통해 서버에 접속합니다.

 

그리고 Send메서드로 서버에 데이터를 보내고 다시 Receive메서드로 서버에서 보낸 데이터를 수신해 사용자에게 표시합니다. 이와 같은 방법으로 클라이언트와 통신하는데 대한 대부분의 서버를 구성할 수 있습니다.

 

static void myRecCallBack(IAsyncResult ar)
{
    myData md = (myData)ar.AsyncState;

    int i = md.client.EndReceive(ar);

    string s = Encoding.UTF8.GetString(md.rec, 0, i); //일단 웹서버 테스트이므로 수신내용은 무시

    string sendMsg = "HTTP/1.0 200 OK/nContent-Type: text/html; charset=UTF-8\r\n\r\n" +
                    "<html><body>Web Server Response</body></html>";

    byte[] sndB = Encoding.UTF8.GetBytes(sendMsg);
    md.client.BeginSend(sndB, 0, sndB.Length, SocketFlags.None, mySadCallBack, md.client);
}

 

위 예제는 myRecCallBack메서드를 수정해 웹서버를 구성한 것입니다. 테스트용이므로 클라이언트에게 어떤 요청을 받든지 그것은 무시하기로 하고 응답에 대한 적절한 메세지를 작성해 클라언트에 보내면 됩니다. 클라이언트는 웹브라우저를 통해 설정된 포트로 접근하면 수신된 내용을 화면에 표시할 것입니다.

tcp
0 0
Programming/.NET

ASP.NET MVC에서는 기본구조인 컨트롤러와 뷰, 그리고 라우팅시스템까지 전 구조를 통째로 분리하여 '영역'이라는 것을 만들고 이를 웹프로그램에 그대로 적용할 수 있습니다.

 

프로젝트에 '영역(Area)'을 추가하려면 프로젝트에서 마우스 오른쪽 버튼을 눌러 'Add -> Area'를 클릭합니다. 그런다음 적당한 영역이름을 입력하고 'OK'버튼을 클릭하면 다음과 같은 영역이 프로젝트에 생성됩니다.

 

 

예제는 이름을 'MyArea'로 하였습니다.

 

영역을 추가하고 나면 MyAreaAreaRegistration.cs 파일을 자동으로 열어 보여주는데 이 파일에서 RegisterArea 메서드에 보면 MyArea 영역에 대한 라우트정보가 새로 구성되어 있음을 확인할 수 있습니다.

 

public class MyAreaAreaRegistration : AreaRegistration 
{
    public override string AreaName 
    {
        get 
        {
            return "MyArea";
        }
    }

    public override void RegisterArea(AreaRegistrationContext context) 
    {
        context.MapRoute(
            "MyArea_default",
            "MyArea/{controller}/{action}/{id}",
            new { action = "Index"id = UrlParameter.Optional }
        );
    }
}

 

클래스는 AreaRegistration 클래스를 상속받아 내부에 RegisterArea 메서드를 통해 라우트정보를 등록하고 있는데 이 메서드는 프로젝트의 Global.asax 파일에서

 

AreaRegistration.RegisterAllAreas();

 

AreaRegistration 개체의 RegisterAllAreas 정적메서드를 호출하여 AreaRegistration 를 상속받은 모든 클래스를 찾도록 합니다. 그런 후 해당 클래스가 존재하면 내부에 RegisterArea 메서드를 호출함으로서 라우트가 적용될 수 있도록 합니다.

 

라우트정보에 따라 새롭게 생성된 영역은 이제부터 컨트롤러와 뷰를 적절히 배치하여 MyArea/Home/Index 와 같은 형식으로 접근이 가능하게 됩니다. 그러나 주의해야할 것이 있는데 만약 메인 프로젝트에서 Home컨트롤러가 존재하고 영역에도 Home컨트롤러가 존재한다면 /Home/Index 형식으로 접근할때 오류를 발생시킬 수 있습니다. 따라서 다음과 같이 우선적으로 처리될 기본 네임스페이스를 지정해야 합니다.

 

routes.MapRoute("Default""{controller}/{action}/{id}"new { controller = "Home"action = "Index"id = UrlParameter.Optional }new[] { "WebApplication1.Controllers" });

 

참고로 네임스페이스의 우선순위는 Global.asax 의 Application_Start 메서드 에서도 다음과 같이 설정할 수 있습니다.

 

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();
    RouteConfig.RegisterRoutes(RouteTable.Routes);

    ControllerBuilder.Current.DefaultNamespaces.Add("MyNamespace");
    ControllerBuilder.Current.DefaultNamespaces.Add("MyName*");
}

 

MyName에 *은 해당 네임스페이스와 자식 네임스페이스도 포함된다는 것을 의미합니다.

 

'영역'이라는 것은 새롭게 프로젝트에 추가해서 만들 수 있을 뿐만 아니라 필요하다면 기존에 있던 컨트롤러에 영역속성을 추가하여 새로운 영역을 생성하는 것도 가능합니다.

 

[RouteArea("abc")]
public class HomeController : Controller
{
    // GET: Home
    [Route("Home")]
    public ActionResult Index()
    {
        return View();
    }

    public ActionResult MyPage()
    {
        return View();
    }
}

 

기존에 HomeController에 RouteArea 속성을 추가하여 abc라는 영역을 생성하였습니다. 또한 Index 액션메서드에는 Route 속성도 추가되었으므로 /abc/Home/Index 과 같은 방법으로 접근할 수 있게 됩니다.

 

참고로 만약 영역안에서 다른 영역에 존재하는 액션메서드를 찾아가는 링크를 생성해야 한다면 area 속성을 만들어서 해당하는 영역을 지정해 줘야 합니다.

 

@Html.ActionLink("go""Index"new { area = "MyArea2" })

만약 별도의 영역이 아닌 최상위의 경우라면 area는 ""형식으로 공백을 지정하도록 합니다.

'Programming > .NET' 카테고리의 다른 글

Invalid pointer (Exception from HRESULT: 0x80004003 (E_POINTER))  (0) 2018.10.24
[C#] 네트워크 - TCP  (0) 2018.10.17
[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
0 0
Programming/Microsoft SQL Server

일반적으로 텍스트 검색시 사용하는 쿼리로 Like검색을 들 수 있습니다.

 

Select *
From [Sales].[OrderLines]
Where [Description] Like '%car%';

 

그런데 위와 같은 방식은 테이블스캔작업이 발생되기에 행(Row)이 많으면 원하는 내용을 찾는데 오랜 시간이 소모될 것입니다. 물론 인덱스를 타게 할 수도 있으나 car%와 같은 방법으로 해야만 해서 내용 중간에 'car'가 있는 데이터는 찾을 수 없고 더군다나 무작위의 문자열을 다루는 열(Column)에는 특별한 경우가 아닌이상 인덱스를 걸어놓지 않습니다.

 

결국 신문기사 검색과 같은 텍스트검색을 위해서는 Like이외에 다른 방법이 필요하게 됐고 보통의 인덱스처럼 열(Column)에 인덱스를 적용하는 것이 아니라 특정 단어나 문장을 대상으로 인덱스를 적용하는 '전체 텍스트 검색'을 사용자에게 제공하게 되었습니다.

 

전체 텍스트 검색기능을 이용하려면 일단 서버에 SQL Full-text Filter Daemon 서비스가 실행중이어야 하는데

 

 

이 서비스는 SQL Server를 설치할때 'Full-Text and Semantic Extractions for Search'항목을 옵션으로 지정해 설치를 해야 합니다.

 

 

이것으로 보아 전체 텍스트 검색을 위한 엔진이 별도로 존재한다는 것을 알 수 있을 것입니다. 만약 사용자가 전체 텍스트 검색을 위한 쿼리를 실행하면 전체 텍스트 엔진은 전체 텍스트 인덱스를 검색해 해당 열을 찾아 그 결과를 SQL Server 서비스에 알려주고 서비스는 그 열의 내용을 가져와 사용자에게 반환하는 방식으로 전체 텍스트검색을 수행하게 됩니다.

 

그럼 실제 검색할 문자열이 존재하는 테이블을 대상으로 전체 텍스트 검색을 사용해 보겠습니다. 아래 예제의 대상은 WideWorldLmporters라는 Sample 데이터베이스를 사용한 것입니다.

 

전체 텍스트 검색사용을 위해서는 우선 전체 텍스트 카탈로그를 생성해야 합니다. 이는 전체 텍스트 인덱스를 저장하고 있는 저장소 역활을 수행합니다.

 

Create Fulltext Catalog SalesOrderLinesCatalog As Default;

 

카타로그는 Create Fulltext Catalog 구문을 사용하며 SalesOrderLinesCatalog라는 이름으로 카타로그를 생성하였습니다. 참고로 sys.fulltext_catalogs 테이블을 살펴보면 생성한 카타로그의 정보를 확인해 볼 수 있습니다.

 

Select *
From sys.fulltext_catalogs;

 

만약 생성한 카탈로그의 is_default값이 1이면 해당 카탈로그가 기본 카탈로그임을 의미하는 것입니다.

 

카탈로그 삭제는 Drop FullText를 사용합니다.

 

Drop Fulltext Catalog SalesOrderLinesCatalog

 

카탈로그를 생성하고 나면 이제 해당 카탈로그에 전체 텍스트검색 인덱스를 생성해야 합니다.

 

Create FullText Index On Sales.OrderLines([Description])
Key Index PK_Sales_OrderLines
On SalesOrderLinesCatalog
With Change_Tracking Auto;

 

위 예제에서 인덱스는 Sales.OrderLines 테이블의 Description 열에 생성하였으며 전체 텍스트 검색엔진이 검색후 결과를 PK_Sales_OrderLines 인덱스키로 SQL Server 서비스에 반환하도록 하였습니다. 즉, 전체 텍스트 검색엔진은 검색 후에 그 내용자체가 아닌 해당 내용이 있는 열을 가리키고 있는 키값을 반환하기 때문에 전체 텍스트 인덱스를 생성하려고 하는 테이블은 반드시 값이 중복될 수 없는 Primary Key나 Unique Key를 가지고 있어야 합니다. 참고로 Description 열은 nVarChar형인데 char, nChar, text, nText, xml등 문자열이 들어가는 거의 모든 열에 지정이 가능합니다. 또한 일반 인덱스와 다르게 전체 텍스트 인덱스는 테이블당 하나만 생성이 가능합니다.

 

혹시 인덱스 생성시 Key Index로 어떤걸 지정해야 할지 모르는 경우라면

 

Select [name]
From sysindexes
Where OBJECT_ID('Sales.OrderLines') = id;

 

처럼 특정 테이블의 개체를 확인하고 PK로 시작하는 이름을 확인하시기 바랍니다.

 

그리고 해당 인덱스는 On구문을 통하여 이전에 만들어둔 전체 텍스트 카탈로그인 SalesOrderLinesCatalog에 생성되도록 합니다. 다만 SalesOrderLinesCatalog는 현재 기본 카탈로그이므로 생략이 가능합니다.

 

그리고 마지막으로 만약 실제 테이블의 값(Sales.OrderLines의 Description열)이 변경되는 경우 이를 인덱스에도 자동으로 반영되도록 Change_Tracing 옵션을 Auto로 지정하였습니다. 성능이 문제가 되는 경우라면 Auto대신 Munual을 지정해 변경데이터를 수동으로 적용시켜 주는 방법도 있으며 Off를 통해 아예 변경내용을 업데이트하지 않도록 처리할 수도 있습니다.

 

인덱스를 생성 후 sys.fulltext_indexes 테이블을 통해 인덱스의 상황을 확인할 수 있습니다.

 

Select *
From sys.fulltext_indexes;

 

만약 결과에서 is_enabled가 1이라면 현재 해당 인덱스는 활성화중이라는 것을 의미합니다.

 

인덱스를 생성하고 나면 어떤 단어들이 인덱스로 생성되었는가를 확인할 수 있습니다.

 

Select *
From sys.dm_fts_index_keywords(DB_ID(), OBJECT_ID('Sales.OrderLines'));

 

위 결과에서 display_term은 생성된 단어들 그리고 column_id는 해당 단어가 들어간 열의 순서값(2라면 2번째열), 마지막으로 document_count는 해당 단어가 몇번이나 들어가 있는지를 나타냅니다.

 

단어를 나열해 보면 가령 필요없는 단어들까지 인덱스로 만들어놓은걸 확인할 수 있습니다. 예들 들어 'ta' 이라는 단어까지 인덱스로 만들필요가 없다면 이들 단어는 중지목록에 포함시켜 인덱스에서 제외할 수 있습니다.

 

특정 단어를 중지목록에 넣으려면 우선 중지목록자체를 생성해야 합니다. SSMS(SQL Server Management Studio)에서 Object Explorer창을 통해 원하는 DB의 Storage->Full Text Stoplists 항목을 찾아가 마우스 오른쪽 버튼을 눌러 'New Full-Text Stoplist'메뉴를 선택합니다.

 

 

Full-text stoplist name에 적절한 목록이름을 입력하고 아래 옵션중 원하는 방식으로 목록옵션을 선택합니다. 옵션중에서 Create an empty stoplist는 말 그대로 빈목록을 만들고 거기에서 시작하겠다는 의미이며 Create from the system stoplist는 이미 시스템상에 만들어져 있는 목록에서 추가하는 형태로 새로운 목록을 만들겠다는 것을 의미합니다. 마지막으로 Create from an existing full-text stoplist는 다른 곳에서 만들어진 목록을 가져와 추가하는 형태로 목록을 만드는 경우 선택하는 옵션인데 이들은 인덱스를 생성하고자 하는 목적에 따라 적절이 선택하시면 됩니다. 예제에서는 이름을 mystoplist하고 Create an empty stoplist를 선택하도록 하겠습니다.

 

 

중지목록을 만들고 나면 위에서 처럼 목록이 생성되는 것을 확인할 수 있습니다. 아이콘 모양을 언뜻보면 뭔가 중지된 느낌이 들지만 중지목록이라는 의미로 모양자체가 원래 그런것이니 신경쓰지 않아도 됩니다.

 

이 작업은 아래 쿼리를 통해서도 동일하게 처리할 수 있습니다.

 

Create FullText Stoplist mystoplist;

 

혹은 시스템 목록에서 만들고자 하는 경우

 

Create FullText Stoplist mystoplist From System StopList;

 

이제 목록에 인덱스에서 예외로 하고자 하는 단어를 추가해 봅시다. 목록에서 마우스 오른쪽 버튼을 눌러 Properties(속성)을 선택합니다.

 

 

위 화면에서 예외로 하고자 하는 단어를 입력하고 OK(확인)버튼을 누르면 해당 단어가 추가됩니다. 이 작업은 아래 쿼리를 통해서도 동일하게 처리할 수 있습니다.

 

Alter FullText StopList mystoplist Add 'ta' Language 'English';

 

이제 기존의 만들어둔 인덱스를 삭제하고

 

Drop FullText Index On Sales.OrderLines;

 

새롭게 인덱스를 구성해 보겠습니다. 쿼리는 이전과 완전히 동일하지만 중지목록에 있는 단어들은 제외하도록 지정해야 합니다.

 

Create FullText Index On Sales.OrderLines([Description])
Key Index PK_Sales_OrderLines
On SalesOrderLinesCatalog
With Change_Tracking Auto, StopList = mystoplist;

 

위에서 처럼 인덱스를 생성하고 나면 중지목록에 있는 단어는 더이상 포함되어 있지 않음을 확인할 수 있습니다.

 

인덱스를 생성하였으면 이제 테이블에서 전체텍스트 검색을 통해 데이터를 찾아보로고 하겠습니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to');

 

Description 컬럼에 to라는 단어가 들어간 모든 데이터를 찾도록 합니다.

 

실행계획에서 FullTextMatch를 확인해 볼 수 있습니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'to*');

 

to로 시작하는 단어를 찾습니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to And become');

 

to 와 become 두개가 동시에 들어간 데이터를 찾습니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'Near((to, become), 2)');

 

to와 become라는 단어가 서로 근접한것을 찾습니다. 이때 이 두단어 사이에 최대 2개까지의 단어만 포함하도록 합니다.

 

Select *
From Sales.OrderLines
Where Contains([Description], 'IsAbout (to weight (1.0), become weight (0.5))');

 

가중치를 부여한 검색을 수행합니다. 0~1까지 지정이 가능하며 숫자가 높을수록 가중치를 많이 부여합니다.

 

Select *
From Sales.OrderLines
Where CONTAINS([Description], 'to Or box');

 

to 또는 box단어가 존재하는 데이터를 찾습니다.

 

Select *
From Sales.OrderLines
Where FreeText([Description], 'press F1 to');

 

FreeText는 빗스한 내용을 찾아주는 역활을 수행합니다. 예제에서 press F1 to로 지정했는데 이와 완전히 동일하지 않더라도 비슷한 내용이 있으면 그 결과를 반환할 것입니다.

 

Select *
From ContainsTable(Sales.OrderLines, [Description], 'to');

 

ContainsTable은 지정한 열을 포함하는 키값별로 지정한 단어를 가장 많이 그리고 정확하게 포함하고 있는 가중치값을 표현합니다. 비슷한 기능으로 FreeTextTable이 있으며 개념은 FreeText와 같습니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

파일그룹에 인덱스생성 후 삭제관련 문제  (0) 2019.05.17
인덱스(Index) - 1  (0) 2019.03.26
[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
0 0
Programming/.NET

UDP는 간단하게 서버와 클라이언트가 데이터를 송수신할 수 있지만 일단 데이터를 보내면 상대측이 잘 받았는지 확인할 도리가 없고 심지어 여러건의 데이터를 나누어 보내면 순서대로 받을 수 있을지 조차 장담할 수 없습니다. 따라서 신뢰성이 너무 약해 잘 쓰지 않는 통신방식입니다.

 

지금은 간단하게 C#으로 UDP통신이 가능한 서버와 클라이언트 프로그램을 구현해 보고자 합니다. 통신 방식은 클라이언트가 서버에 특정 데이터를 보내면 서버는 이 데이터를 받아 앞에 'server : ' 라는 문자열을 추가한뒤 다시 클라이언트에 보내는 방식입니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

t.Start();

Console.Read();

}

 

static void myMethod(object o)
{
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)) {
        IPEndPoint iEp = new IPEndPoint(IPAddress.Any, 2020);
        socket.Bind(iEp);

        byte[] bRec = new byte[1024];
        EndPoint ep = new IPEndPoint(IPAddress.None, 0);

        while (true) {
            int i = socket.ReceiveFrom(bRec, ref ep);
            string s = Encoding.UTF8.GetString(bRec, 0, i);

            byte[] bSnd = Encoding.UTF8.GetBytes("server : " + s);
            socket.SendTo(bSnd, ep);
        }
    }
}

 

우선 서버측부터 구현해 보겠습니다. 제일 먼저 필요한건 소켓입니다. Socket클래스를 통해 socket을 생성하는데 ProtocolType을 Udp로 지정해 UDP로 통신할 것임을 알려줍니다.

 

서버는 경우에 따라 다수의 IP를 가질 수 있습니다. 이 가운데 지금 통신을 위해 어떤 IP와 포트로 수신대기를 해야하는지를 정해야 하며 그 접점을 IPEndPoint클래스로 설정합니다. IPAddress.Any는 현재 시스템에 마련된 모든 IP를 대상으로 하겠다는 것을 의미하며 2020은 포트번호에 해당합니다. 만약 특정 IP를 명시적으로 지정하고자 한다면 다음과 같이 코드를 수정할 수 있습니다.

 

IPAddress ip = IPAddress.Parse("192.168.20.126");
IPEndPoint iEp = new IPEndPoint(ip, 2020);

 

접점이 마련되었으면 socket의 Bind 메서드를 통해 해당 접점정보를 전달하여 설정된 네트워크정보로 데이터를 수신대기하도록 합니다.

 

bRec는 클라이언트로 부터 데이터가 수신되면 받을 버퍼이며 ep는 클라이언트측 접점정보를 가진 개체입니다. 그런데 어떤 클라이언트로 부터 데이터가 수신될지 모르니 IPEndPoint로 인스턴스를 생성할때는 IP를 지정하지 않고 또한 포트도 0으로 설정할 수 밖에 없습니다. 특정 내용으로 설정한다 하더라도 이개체는 socket의 ReceiveFrom메서드에서 데이터가 수신될때 데이터를 보낸 접점정보를 담아 반환하게될 것이므로 초기설정정보는 그렇게 의미가 없습니다.

 

ReceiveFrom메서드에서 데이터가 수신되면 bRec버퍼에 수신된 내용을 담은 후 UTF8인코딩을 통해 문자열로 변환한 후 앞단에 'sever : '라는 내용을 붙여 SendTo메서드를 통해 받은 데이터를 다시 보냅니다. 이때 어느 클라이언트로 보낼지는 ep개체를 통해 알 수 있습니다. ep는 초기화 시에는 의미없는 내용이었지만 ReceiveForm에서 정보가 담겨지게 되므로 클라이언트의 접점정보를 알 수 있게 됩니다.

 

또한 SendTo로 데이터를 보낼때는 보낼 수 있는 데이터의 크기도 생각을 해봐야 합니다. 이론상으로는 최대 65535byte인데 이는 네트워크통신에서 패킷에 붙는 별도의 정보로 인해 크기가 줄어들 수 있을 뿐만 아니라 각종 네트워크장비에서 통과할 수 있는 데이터의 양을 제한하는 경우도 있으므로 주의해야 합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;


    t.Start();

    Console.Read();
}

static void myMethod(object o)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

    IPAddress ip = IPAddress.Parse("192.168.20.126");
    EndPoint sEP = new IPEndPoint(ip, 2020);

    EndPoint cEP = new IPEndPoint(IPAddress.None, 0);

    byte[] send = Encoding.UTF8.GetBytes("hi server!!");
    socket.SendTo(send, sEP);

    byte[] rec = new byte[1024];
    int i = socket.ReceiveFrom(rec, ref cEP);
    string s = Encoding.UTF8.GetString(rec, 0, i);

    Console.WriteLine(s);

    socket.Close();
}

 

위 예제는 클라이언트측 예제로서 소켓생성방법은 서버와 같습니다.

 

클라이언트입장에서 데이터를 보낼때 어느 서버로 보낼지를 알아야 하므로 그에 대한 설정을 진행합니다. 서버의 IP는 192.168.20.126(여러분의 환경에 따라 IP는 다를 것입니다!)이며 포트 2020으로 접속할 것입니다. 또한 서버에 데이터를 보낸 후 다시 서버에서 메세지를 수신받아야 하는데 이때 필요한 접점정보도 필요합니다. 다만 이미 서버정보에 대해서는 알고 있으나 어차피 ReceiveFrom에서 데이터를 보낸 대상의 접점정보가 파악될 것이므로 굳이 IP와 포트정보를 따로 지정하지 않습니다.

 

데이터를 보낼때는 보낼 내용을 UTF8로 인코딩하여 바이트배열에 담아 SendTo메서드에 전달해 데이터를 송신합니다. 이때 서버측 접점정보도 매개변수를 통해 같이 전달합니다.

 

서버에 데이터를 송신하면 그 즉시 서버에서 특정 메세지가 회신될 것이므로 그 회신데이터를 담을 버퍼를 마련해 두고 ReceiveFrom 메서드에 전달합니다. 그리고 버퍼에 담긴 내용을 다시 UTF8로 디코딩하여 사용자에게 보내줍니다.

 

UDP통신은 앞서 말한대로 데이터를 송수신할때의 신뢰성이 떨어지지만 데이터를 보내면 데이터를 받는 쪽에서는 보낸만큼 한꺼번에 데이터를 수신받을 수 있는등 통신방식은 대단히 간결하다는 장점이 있습니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 네트워크 - TCP  (0) 2018.10.17
[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
udp
0 0
Programming/Microsoft SQL Server

MS SQL Server에서 데이터베이스(DB)를 생성하면 실제 데이터베이스파일이 만들어 지는데 일부로 변경한 경우가 아니라면 아래와 같은 위치에 파일이 생성될것입니다.

 

C:\Program Files\Microsoft SQL Server\MSSQL12.MSSQLSERVER\MSSQL\DATA

 

실제 위와 같은 위치로 들어가면 확장자가 mdf인 파일과 ldf인 파일을 확인해 볼 수 있는데, 예를 들어 abc라는 데이터베이스를 생성했다면 abc.mdf와 abc_log.ldf파일이 존재함을 알 수 있습니다. 여기서 mdf는 실제 데이터가 들어가 있는 파일이며 ldf가 트랜잭션로그파일입니다. 이들 파일은 일반적으로는 한개만 존재할 경우가 많지만 필요에 따라 여러개로 나뉘어질 수 있는데 만약 데이터파일이 나뉘어진거라면 *.ndf형식으로 데이터파일이 생성되어 있을 것입니다.

 

이번에 주목해야할 것은 ldf즉 트랜잭션로그파일입니다. 이 파일은 Update나 Insert, Delete등의 데이터 변경작업을 기록하는 파일입니다. 만약 사용자가 Select와 같은 데이터조회 쿼리를 서버에 전달하면 서버는 mdf에서 원하는 데이터를 찾아 사용자에게 돌려주게 됩니다. 당연히 이 과정에서는 따로 트랜잭션로그가 남지 않습니다.(참고로 로그를 남기는 행위는 어떻게든 성능면에서는 불이익입니다. 트랜잭션로그도 마찬가지인데 로그를 남기지 않도록 하면 데이터의 무결성을 보증하기는 힘들어지지만 성능은 훨씬 좋아질것입니다.)

 

아래 쿼리는 데이터 변경 쿼리로 트랜잭션로그를 남기게 될 것인데 여기서 로그는 변경구문 그 자체를 기록하는것을 의미합니다.

 

Update [Person].[Person]
Set Title = NULL,
    FirstName = 'Ovidiu',
    MiddleName = 'V',
    LastName = 'Cracium'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set Title = 'Ms.',
    FirstName = 'Janice',
    MiddleName = 'M',
    LastName = 'Galvin'
Where BusinessEntityID = 13;

 

그런데 위 구문은 Update가 2개이므로 2번의 트랜잭션을 발생시키게 됩니다. MS SQL은 기본적으로 자동 커밋이기에 사용자가 따로 트랜잭션시작과 종료시점을 명시하지 않는다면 변경 구문 하나하나마다 별도의 트랜잭션을 자동적으로 추가하게 됩니다.

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu',
     MiddleName = 'V',
     LastName = 'Cracium'
Where BusinessEntityID = 11;
Commit Tran;

Begin Tran
Update [Person].[Person]
Set Title = 'Ms.',
     FirstName = 'Janice',
     MiddleName = 'M',
     LastName = 'Galvin'
Where BusinessEntityID = 13;
Commit Tran;

 

그래서 만약 여러개의 변경구문을 묶어 단 한번만 트랜잭션을 유발시키려면 Begin Tran와 Commit Tran사이에 필요한 내용을 모두 명시해야 합니다.

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu',
     MiddleName = 'V',
     LastName = 'Cracium'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set Title = 'Ms.',
     FirstName = 'Janice',
     MiddleName = 'M',
     LastName = 'Galvin'
Where BusinessEntityID = 13;
Commit Tran;

 

여기서 Begin Tran과 Commit Tran에서의 Tran은 Transaction의 줄임표현이며 원한다면 Begin Transaction처럼 풀어써주는것도 가능합니다.

 

트랜잭션은 위에서 처럼 Begin Tran과 Commit Tran을 명시적으로 해줘야 하는 경우도 있고 오라클처럼 Begin Tran은 자동으로 붙여주되 Commit Tran이나 Rollback Tran은 명시적으로 붙여줘야 하는 방식을 취할 수도 있습니다. 이러한 방식을 암시적 트랜잭션이라고 하는데 MS-SQL에서 이 방식을 사용하려면 다음과 같이 설정을 부여해야 합니다.

 

Set Implicit_Transactions On;

 

다만 일반적으로 트랜잭션은 중복으로 실행될 수 있지만 암시적 트랜잭션은 Commit이나 Rollback을 만나기 전까지는 중복실행이 불가능합니다.

 

위에서 처럼 MS-SQL서버에 트랜잭션이 발생하게 되면 그러니까 Begin Tran이 시작되면 ldf에 Begin Tran구문을 기록하고 변경 쿼리의 내용대로 데이터를 임시로 변경하게 됩니다. 임시로 변경한다는 것은 실제 데이터를 변경하는 것이 아니라 변경이 필요한 행 데이터(페이지)를 메모리캐시에 올려놓고 올려놓은 데이터를 대상으로 변경하는 것을 의미합니다. 그리고 나서 변경 쿼리에 일련번호를 부여하여 ldf파일에 로그로 기록하게 됩니다. 이 과정을 Commit이나 Rollback이 나올때까지 변경쿼리마다 하나씩 반복합니다.

 

만약 이 과정중 Commit Tran을 만나게 되면 ldf에 로그를 기록하고 캐시에 기록된 내용대로 실제 mdf데이터에 해당 내용을 적용(Commit)하게 됩니다. 그리고 나서 ldf의 Commit Tran부분에 체크포인트를 설정합니다. 체크포인트는 지금까지의 내용이 모두 실제 mdf에 적용되었음을 표시하는 것인데 만약 CheckPoint 문을 임의로 실행하면 캐시의 내용을 실제 데이터에 적용하고 그 시점에 체크포인트를 설정할 수 있도록 합니다.

 

Begin Tran부터 변경쿼리를 통해 캐시내용을 바꾸는 과정중 어떠한 이유로 인해서 서버나 혹은 서비스가 종료되는 상황이 발생하면 메모리의 내용이 모두 사라지게 될 것입니다. 변경내용이 모두 메모리에 존재하고 있었는데 이 캐시데이터를 모두 잃어버리게 되었고 로그상 아직 Begin Tran중이었으니 처음 Begin Tran이나 혹은 이전 체크포인트까지의 처리를 모두 취소하여 데이터의 무결성을 유지하게 됩니다.

 

그런데 처리과정이 ldf에 Commit Tran을 기록하는 것까지 도달하였으나 캐시데이터가 아직 실제 데이터에 적용되기 전에 사고가 발생한 경우라면 Begin Tran시점(또는 이전 체크포인트 이후)부터 모든 쿼리에 대한 트랜잭션을 다시 재 수행하여 변경절차를 마무리하게 됩니다.

 

Commit Tran은 캐시데이터를 실제 데이터에 적용하지만 Rollback Tran은 변경이전으로 되돌리는 작업을 수행합니다. 그런데 트랜잭션이 여러개 중첩되어 사용되는 경우 Rollback Tran을 사용할때 오해할 수 있는 부분이 하나 있는데 Rollback Tran은 이전의 트랜잭션을 되돌리는 것이 아니라 지금까지의 모든 트랜잭션을 되돌린다는 것입니다.

 

Begin Tran
     Update [Person].[Person]
     Set Title = NULL,
      FirstName = 'Ovidiu1',
      MiddleName = 'V1',
      LastName = 'Cracium1'
     Where BusinessEntityID = 11;

Begin Tran
      Update [Person].[Person]

Set EmailPromotion = 1
Where BusinessEntityID = 11;

RollBack Tran;

 

Begin Tran 이후 다시 Begin Tran으로 트랜잭션을 두번 호출하고 마지막에 RollBack Tran으로 변경 데이터를 되돌립니다. 얼핏봐서는 이전의 Begin Tran만 되돌릴것 같지만 사실은 걸려있는 모든 트랜잭션을 취소합니다. 따라서 만약 특정 트랜잭션만을 명시해 취소하고자 한다면 특정 지점에서 트랜잭션을 Save하고 Save한 트랜잭션까지 취소하는 방법을 써야 합니다.

 

Begin Tran
     Update [Person].[Person]
     Set Title = NULL,
     FirstName = 'Ovidiu1',
     MiddleName = 'V1',
     LastName = 'Cracium1'
     Where BusinessEntityID = 11;
Save Tran my_tran;
Begin Tran
     Update [Person].[Person]
     Set EmailPromotion = 1
     Where BusinessEntityID = 11;
RollBack Tran my_tran;

 

중간에 Save Tran으로 트랜잭션 지점을 생성하고 RollBack Tran에서 복원지점을 명시하면 명시한 지점까지만 트랜잭션을 복원하게 됩니다. 따라서 위 쿼리의 경우 EmailPromotion을 Update하는 부분만 적용되고 그 위로는 트랜잭션이 취소되지 않습니다.

 

참고로 특정 테이블에 트랜잭션이 걸려져 있는 상태라면 해당 테이블에는 잠금(Lock)이 발생할 수 있습니다. 이 현상을 확인하려면 Begin Tran을 발생시키고 다른 세션(별도의 쿼리창)에서 해당 테이블의 Select를 시도해 보면 쉽게 확인 할 수 있습니다. 따라서 임의로 트랜잭션을 구현할때는 주의해야 하며 필요한 경우 아래 쿼리를 실행(Select가 필요한 세션안에서)하여 테이블잠김상태라도 데이터를 가져올 수 있도록 설정할 수 있습니다.

 

Alter Database [대상DB] Set Allow_Snapshot_Isolation On;
Set Transaction Isolation Level Snapshot;

 

특정 세션에서 트랜잭션을 발생시킨뒤 데이터 변경 쿼리를 실행하고 테이블을 조회해 보면 변경된 상태의 데이터가 출력됨을 확인할 수 있는데 이것은 실제 테이블의 데이터를 가져오는 것이 아니라 캐시상태의 데이터를 가져와 보여주고 있기 때문입니다.

 

Snapshot을 취소하려면 다음과 같이 수행합니다.

 

Alter Database AdventureWorks Set Allow_Snapshot_Isolation Off;

 

위에서 알아본대로 트랜잭션은 데이터의 무결성을 유지하기 위한 아주 좋은 장치입니다. 그런데 트랜잭션은 장애발생에 대한 조치용이지 모든걸(모든 장애를) 해결해 주는 것으로 오해해서는 안됩니다. 예를 들어 아래 쿼리를 보면

 

Begin Tran

Update [Person].[Person]
Set Title = NULL,
     FirstName = 'Ovidiu1',
     MiddleName = 'V1',
     LastName = 'Cracium1'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set EmailPromotion = 'ABC'
Where BusinessEntityID = 11;
Commit Tran;

 

두번째 Update문에서 EmailPromotion은 Int형인데 여기에 'ABC'라는 문자열을 처리하도록 하고 있습니다. 트랜잭션에 대해 오해를 하고 있으면 여기에서 실패가 발생해 자동적으로 모든 Update문이 RollBack되리라 생각할 수 있겠지만 사실은 그렇지 않습니다. 이것은 엄밀히 말해 장애가 아닌 오류상황이기 때문입니다. 따라서 이런 경우에는 운영자가 명시적으로 Rollback이 되도록 처리를 해줘야 하며 이 때 가장 많이 쓰이는 방식은 Try Catch 구문을 활용하는 것입니다.

 

Begin Try

Begin Tran
Update [Person].[Person]
Set Title = NULL,
FirstName = 'Ovidiu1',
MiddleName = 'V1',
LastName = 'Cracium1'
Where BusinessEntityID = 11;

Update [Person].[Person]
Set EmailPromotion = 'ABC'
Where BusinessEntityID = 11;
Commit Tran;

End Try
Begin Catch
     Rollback Tran;
End Catch;

 

Begin Tran안에서 오류가 발생하면 Begin Catch에서 잡아 RollBack Tran을 실행할 것입니다.

'Programming > Microsoft SQL Server' 카테고리의 다른 글

인덱스(Index) - 1  (0) 2019.03.26
[SQL] 전체 텍스트 검색  (0) 2018.09.27
트랜잭션 (Transaction)  (0) 2018.09.12
분산 트랜잭션 설정  (0) 2018.07.11
[SQL] 기본언어확인및 변경  (0) 2018.01.30
[SQL] 스키마(Schema)  (0) 2018.01.23
0 0
Programming/.NET

특정 메서드의 비동기 호출을 시도할때는 대부분 작업을 진행하는 메서드부분과 작업을 완료하고 호출되는 메서드를 스레드로 분리하면서 이루어집니다. 하지만 async / await 를 사용하면 비동기호출시 완료처리를 위해 추가했던 별도의 메서드를 분리할 필요가 없어집니다.

 

using (FileStream fs = new FileStream(@"C:\\test.txt"FileMode.OpenFileAccess.ReadFileShare.Read)) {
    byte[] b = new byte[fs.Length];
    fs.Read(b, 0, b.Length);

    string s = Encoding.UTF8.GetString(b);
}

 

이 예제는 FileStream을 통해 파일 내용을 읽어들이는 동기식 방법입니다. 이 예제를 async / await 가 없을때 비동기식으로 바꾸면 다음과 같이 할 수 있습니다.

 

static byte[] b;
static void Main(string[] args)
{
    FileStream fs = new FileStream("test.txt"FileMode.OpenFileAccess.ReadFileShare.Read);
    b = new byte[fs.Length];
    fs.BeginRead(b, 0, b.LengthReadComplete, fs);


    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static void ReadComplete(IAsyncResult ar)
{
    FileStream fileStream = (FileStream)ar.AsyncState;
    fileStream.EndRead(ar);

    string s = Encoding.UTF8.GetString(b);

    Console.WriteLine(s);
}

 

예제에서 BeginRead 메서드를 보시면 작업이 완료되었을때 ReadComplete메서드를 호출하는 것을 확인할 수 있습니다. 이것은 다시 async / await 를 사용해 아래와 같이 바꿔볼 수 있습니다.

 

static byte[] b;
static void Main(string[] args)
{
    fileRead();

    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void fileRead()
{
    FileStream fs = new FileStream("test.txt"FileMode.OpenFileAccess.ReadFileShare.Read);
    b = new byte[fs.Length];

    await fs.ReadAsync(b, 0, b.Length);

    string s = Encoding.UTF8.GetString(b);
    Console.WriteLine(s);
}

 

보시는 것처럼 완료후의 메서드로 따로 분리되지 않았습니다. 대신 ReadAsync메서드로 바뀌었는데 이 메서드는 특별히 await구문을 사용한 비동기 호출을 지원하기 위한 것이며 앞에 await 가 따로 지정되었습니다. 이렇게 하면 await가 사용된 구문 아래에 모든 처리는 해당 메서드처리가 모두 완료되고 나서야 처리가 진행됩니다.

 

fileRead메서드에는 async가 구문이 포함되어 있는데 이는 해당 메서드안에서 await가 사용될 것임을 알려주고 있습니다. async가 필요한 이유는 본래 await는 C# 5.0이후에 추가된 것으로 그 버전이전에 변수이름같이 await가 사용된 경우를 대비하기 위해서 입니다.

 

또한 C#에는 async / await 를 사용을 좀더 간단히 하기위해서 기존의 복잡한 비동기호출이 필요한 메서드 이외에 async / await 를 사용할 수 있는 메서드를 추가했습니다.

 

static void Main(string[] args)
{
    getWebData();

    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void getWebData()
{
    HttpClient hc = new HttpClient();
    string s = await hc.GetStringAsync("http://www.naver.com/");

    Console.WriteLine(s);
}

 

위 예제는 HttpClient를 사용하여 특정 웹페이지의 데이터를 가져오는 구문인데 async / await 로 비동기호출을 시도했고 이에 대응하기 위해 await가 지원되는 GetStringAsync메서드를 호출하였습니다.

 

static void Main(string[] args)
{
    doWork();
    Console.WriteLine("현재 스레드 완료");
    Console.Read();
}

static async void doWork()
{
    Task<int> t1 = work5();
    Task<int> t2 = work10();

    await Task.WhenAll(t1, t2);

    Console.WriteLine("비동기 작업 완료");

    Console.WriteLine("결과 : {0}, {1}", t1.Result, t2.Result);
}

static Task<intwork5()
{
    return Task.Factory.StartNew(() => {
        Thread.Sleep(5000);
        Console.WriteLine("5초 작업 완료");
        return 3;
    });
}

static Task<intwork10()
{
    return Task.Factory.StartNew(() => {
        Thread.Sleep(10000);
        Console.WriteLine("10초 작업 완료");
        return 10;
    });
}

 

async / await 를 활용하면 위와 같이 특정 작업을 비동기로 병렬수행하는 것이 가능합니다. 이때 WhenAll은 Task타입을 반환하도록 되어 있어서 await 를 붙일 수 있는데 비슷한 처리를 하는 WaitAll 메서드는 작업이 완료될때까지 현재 스레드에서 대기해야 합니다.

'Programming > .NET' 카테고리의 다른 글

[ASP.NET MVC] 영역(Area)  (0) 2018.10.10
[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
0 0
Programming/.NET

System.IO.Path는 파일경로와 관련해 몇몇 유용한 정적메서드를 제공합니다.

 

string s = Path.ChangeExtension("sample.txt", ".jpg");

 

ChangeExtension메서드는 주어진 파일의 확장자를 지정한 확장자로 변경한 결과를 반환합니다. 실제 파일명을 바꾸지는 않습니다.

 

string s = Path.Combine("C:\\aaa", "sample.txt");

 

Combine은 주어진 경로를 합쳐 하나로 만듭니다. 이때 인자는 param형태로 전달하므로 다수의 인자를 전달할 수 있습니다.

 

string s = Path.GetExtension("sample.txt");

 

해당 경로에 대한 파일명에서 확장자만을 .과 함께 추출합니다.

 

string s = Path.GetFileNameWithoutExtension("sample.txt");

 

반면 확장자를 제외한 파일명만 가져오려 한다면 GetFileNameWithoutExtension() 메서드를 사용합니다.

 

string s = Path.GetFileName("sample.txt");

 

주어진 경로에서 전체파일명만을 가져옵니다.

 

string s = Path.GetFullPath("sample.txt");

 

주어진 경로에서 파일명을 제외한 경로만을 가져옵니다.

 

string s = Path.GetRandomFileName();

 

GetRandomFileName()은 임의의 파일명을 반환합니다.

 

string s = Path.GetTempFileName();

 

임시폴더에 임의의 파일을 만들고 전체경로를 반환합니다.

 

string s = Path.GetTempPath();

 

임시폴더 경로를 반환합니다.

'Programming > .NET' 카테고리의 다른 글

[C#] 네트워크 - UDP  (0) 2018.09.20
[C#] async / await  (0) 2018.09.04
[C#] Path  (0) 2018.08.23
[ASP.NET MVC] 뷰 (View)  (0) 2018.08.14
[C#] using static  (0) 2018.07.27
[ASP.NET MVC] 헬퍼메서드(Helper Method)  (0) 2018.07.18
0 0
1 2 3 4 ··· 20
블로그 이미지

클리엘