'2018/10'에 해당되는 글 4건

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
1
블로그 이미지

클리엘