.net, C#

애플리케이션 서버(게임 서버, 로그인 서버 등)와 관리 서버(말 그대로 서버를 중앙 관리 하는 서버)

사이를 연결 해주는 프로그램을 저는 Bridge라고 부릅니다(다른 분은 Bridge를 다른 용도로 사용 하는 것으로 부르더군요).


Bridge는 애플리케이션 서버와 IPC 통신을 하는데 WM_COPYDATA를 사용합니다.


닷넷에서는 아직 WM_COPYDATA를 지원하지 않고 있습니다. 그래서 PInovke를 사용해야 됩니다.

WM_COPYDATA로 데이터를 보낼 때는 비 관리 메모리를 사용해야 됩니다.


닷넷의 string을 비 관리 메모리에 복사할 때 아래와 같은 방식으로 합니다.

string MsgText = "abcde";

Marshal.WriteIntPtr( CdsMsg.lpData, 0, Marshal.StringToCoTaskMemASCII(MsgText) );


영어는 아주 잘 됩니다.

그러나 문제는 한글입니다. 한글은 위와 같은 방식으로 하면 한글이 다 깨어지더군요.

Marshal.StringToCoTaskMemASCII 의 문제인가 생각 되어

Marshal.StringToCoTaskMemUni,

Marshal.StringToCoTaskMemAuto

도 사용해봤지만 여전히 안되더군요.


그래서 어쩔 수 없이 한 바이트씩 복사를 하여 해결했습니다.

string MsgText = "서버 프로그래머";

byte[] MsgTextASCII = System.Text.Encoding.GetEncoding(0).GetBytes(MsgText);


for (int i = 0; i < MsgTextASCII.Length; ++i)
{
      Marshal.WriteByte(CdsMsg.lpData, 0, MsgTextASCII[i]);
}


혹시 위의 방법보다 더 스마트한 방법이 있으면 한수 가르침 부탁합니다. ^^


신고
by 흥배 2009.03.21 12:43
.net, C#

닷넷의 SmtpClient 클래스를 사용하여 메일을 보내는 방법입니다.

MSDN의 예제는 빠진 부분이 많아서 그걸 사용하면 제대로 되지 않더군요
삽질하다가 구글링으로 알아냈습니다.


아래 예제는 구글의 Gmail을 사용하는 것으로 했습니다..

Gmail의 주소는 smtp.gmail.com 입니다.
            포트번호는 587을 사용합니다.

            구글에서는 465도 사용 할수 있다고 하지만 사용하면 연결이 되지 않습니다.




SmtpClient client = new SmtpClient("smtp.gmail.com", 587);
client.UseDefaultCredentials = false; // 시스템에 설정된 인증 정보를 사용하지 않는다.
client.EnableSsl = true;  // SSL을 사용한다.
client.DeliveryMethod = SmtpDeliveryMethod.Network; // 이걸 하지 않으면 Gmail에 인증을 받지 못한다.
client.Credentials = new System.Net.NetworkCredential("구글 아이디", "패스워드");
           
MailAddress from = new MailAddress("jacking12343@gmail.com","최흥배",         System.Text.Encoding.UTF8);
MailAddress to = new MailAddress("jacking@dyon.co.kr");
                       
 MailMessage message = new MailMessage(from, to);

 message.Body = "This is a test e-mail message sent by an application. ";
 string someArrows = new string(new char[] { '\u2190', '\u2191', '\u2192', '\u2193' });
 message.Body += Environment.NewLine + someArrows;
 message.BodyEncoding = System.Text.Encoding.UTF8;
 message.Subject = "test message 2" + someArrows;
 message.SubjectEncoding = System.Text.Encoding.UTF8;

 try
 {
      // 동기로 메일을 보낸다.
      client.Send(message);
               
       // Clean up.
       message.Dispose();
 }
 catch (Exception ex)
 {
       MessageBox.Show(ex.ToString());
 }

신고
by 흥배 2009.03.21 12:34

C#으로 클라이언트나 서버를 만들 때 C++에 비해서 가장 아쉬운 것이 클래스(혹은 구조체) byte[](C++로 보면 char* 입니다)로 바로 변환하지 못 한다는 것입니다.

 

클래스를 바로 byte[]로 변환만 할 수 있으면(혹은 반대로) 네트웍으로 받은 데이터를 처리

하는 것이 간단해지는데 C#으로는 그것이 쉽지 않아서 좀 피곤하기도 합니다.

처음에는 어떻게든 변환 해보려고 했는데 막상 해보니 이것도 나름 피곤하고 성능 상으로 좋지도 않아서 지금은 이 방법은 사용하지 않습니다.

 

아래는 클래스를 byte[]로 변환하는 코드입니다.

이 방법은 데브피아에서 찾았습니다.

 

    // 패킷 헤더 클래스
    [StructLayout(LayoutKind.Sequential)]//[StructLayout(LayoutKind.Sequential, Pack=1)]
    public class HEADER
    {
        public ushort a1;
       public ushort a2;
        public ushort a3;
        public ushort a4;
    }
   
    //
로그인 요청 클래스
    // GetBuffer
을 부모 클래스에서 정의하고 여기서는 상속 받지 않은 이유는 그렇게 하면 클래스의
    //
데이타를 복사 할 때 부모클래스의 크기(4바이트) 만큼을 앞에 계산 해버린다
    [StructLayout(LayoutKind.Sequential)]
    public class LoginAuthorRet
    {
        public LoginAuthorRet()
       {
            Header = new HEADER();
            acID = new byte[21];
            acPasswd = new byte[31];
        }

// 클래스의 있는 데이타를 메모리에 담아서 리턴 한다.
        public void GetBuffer( byte[] outBuffer )
        {
            if( 0 == outBuffer.Length )
                outBuffer = new byte[ MAX_PACKET_DATA ];

            unsafe
           {
                fixed(byte* fixed_buffer = outBuffer)
               {
                    Marshal.StructureToPtr(this, (IntPtr)fixed_buffer, false);
                }
            }
        }

        public HEADER Header;    //
헤더

        [MarshalAs(UnmanagedType.ByValArray, SizeConst=21)] public byte[] acID;          //
아이디
       [MarshalAs(UnmanagedType.ByValArray, SizeConst=31)] public byte[] acPasswd;    //
패스워드
    }

 

사용 예

LoginAuthorRet LoginPacket = new LoginAuthorRet();링크
LoginPacket.Header.a2 = PK_LOGIN_AUTHOR_REQ;
LoginPacket.Header.a3 = (ushort)(Marshal.SizeOf(LoginPacket)-Marshal.SizeOf(LoginPacket.Header));
               
int iIDLen = strID.Length;
int iPWLen = strPass.Length;
Buffer.BlockCopy( Encoding.ASCII.GetBytes(strID), 0, LoginPacket.acID, 0, iIDLen );
Buffer.BlockCopy( Encoding.ASCII.GetBytes(strPass ), 0, LoginPacket.acPasswd, 0, iPWLen );

byte[] packet1 = new byte[ MAX_PACKET_DATA ];
LoginPacket.GetBuffer( packet1 );
SendPacket( packet1, Marshal.SizeOf(LoginPacket) );

 

2. byte[] to struct

출처: http://qiita.com/maenotti_99/items/519047bee9fd19e6cb10

구조체

[StructLayout( LayoutKind.Sequential, Pack = 1 )]
public struct Hoge
{
    /// <summary>     6:コード</summary>
    [MarshalAs( UnmanagedType.ByValArray, SizeConst = 6 )]
    public byte[]   Code;
}

변환 함수
static public object ToStr( byte[] byteData, Type type )
{
    GCHandle gch = GCHandle.Alloc( byteData, GCHandleType.Pinned );
    object result = Marshal.PtrToStructure( gch.AddrOfPinnedObject(), type );
    gch.Free();
    return result;
}

사용 방법
Hoge dataL = (Hoge)ToStr( byData, typeof( Hoge ) );

 

3. struct to byte[]

출처: http://qiita.com/yu_ka1984/items/969728290b05e15f07a9

public static byte[] ToByteArray<T>(this T structure) where T : struct
{
    byte[] bb = new byte[Marshal.SizeOf(typeof(T))];
    GCHandle gch = GCHandle.Alloc(bb, GCHandleType.Pinned);
    Marshal.StructureToPtr(structure, gch.AddrOfPinnedObject(), false);
    gch.Free();
    return bb;
}


신고
by 흥배 2009.03.21 12:17

WinForm과 WPF의 차이에 의해서 멀티 스레드에서 컨트롤을 조작 하는 방법이 서로 다릅니다.

방법은 MSDN 매거진에서 찾았습니다.

제가 보기에는 아래 방법이 가장 간단하더군요.


참조 : http://msdn.microsoft.com/ko-kr/library/ms741870.aspx


아래의 예는 리스트박스에 새로운 데이터를 추가하는 것이다.


private delegate void ListBoxDelegate(string arg);

 

void SetStateText(string state)
{
         this.listBox1.Dispatcher.BeginInvoke(

                   System.Windows.Threading.DispatcherPriority.Normal,
                             new ListBoxDelegate(UpdateListBox), state);
}

private void UpdateListBox(string state)
{
        this.listBox1.Items.Add(state);
}



메인 스레드가 아닌 곳에서는 SetStateText(string state)을 호출하여 리스트박스를 변경한다. 


신고
by 흥배 2009.03.21 12:13
닷넷에서 Timer를 사용하는 경우 새로운 스레드를 만들어서 처리 되는 것이므로 메인 스레드와의 동기화에 조심해야 됩니다. 그래서 Timer에서는 메인 폼의 컨트롤을 직접 조작이 불가능합니다.

그러나 DispatcherTimer를 사용하면 작업의 처리가 메인 스레드의 Dispatch에서 처리 되므로 메인 스레드와 동기화가 됩니다.

시간이 아주 정확하게 실행되어야 되는 경우가 아니라면 이것을 사용하는 것이 좋을 것 같습니다.

이것을 사용하려면 직접 참조에 WindwosBase를 추가해야 됩니다.




출처 : MSDN

Dispatcher 루프의 맨 위에서 DispatcherTimer가 다시 평가됩니다.

시간 간격이 발생할 때 정확하게 타이머가 실행될지 보장할 수 없지만 시간 간격이 발생하기 전에 타이머가 실행되지 않는 것은 보장할 수 있습니다. 이는 DispatcherTimer 작업이 다른 작업처럼 Dispatcher 큐에 있기 때문입니다. DispatcherTimer 작업 실행 시기는 큐의 다른 작업 및 해당 우선 순위에 따라 다릅니다.

System.Timers..::.Timer가 WPF 응용 프로그램에서 사용되는 경우 System.Timers..::.Timer는 UI(사용자 인터페이스) 스레드가 아닌 다른 스레드에서 실행됩니다. UI(사용자 인터페이스) 스레드의 개체에 액세스하려면 InvokeBeginInvoke를 사용하여 UI(사용자 인터페이스) 스레드의 Dispatcher로 작업을 게시해야 합니다. System.Timers..::.Timer를 사용하는 예제를 보려면 시스템 타이머를 통한 명령 소스 비활성화 샘플을 참조하십시오. System.Timers..::.Timer 대신 DispatcherTimer를 사용하는 이유는 DispatcherTimerDispatcher와 같은 스레드에서 실행되며 DispatcherTimer에서 DispatcherPriority를 설정할 수 있기 때문입니다.

개체의 메서드가 타이머에 바인딩될 때마다 DispatcherTimer는 개체를 유지합니다.


신고
by 흥배 2009.03.21 12:06
닷넷 프레임워크 2.0부터 기본 라이브러리로 압축 관련 라이브러리가 있습니다만
이것은 기본적으로 하나의 파일만 압축이 가능합니다.

닷넷에서 사용하기 좋은 압축 라이브러리로 SharpZipLib 라는 것이 있습니다.
복수 개의 파일을 압축/해제가 가능하며 압축 레벨이나 패스워드 설정도 가능합니다.

저는 이것을 이번에 사내 패치 시스템을 만들 때 사용했습니다.

SharpZipLib은 아래의 사이트에서 얻을 수 있습니다.
SharpZipLib : http://icsharpcode.net/OpenSource/SharpZipLib/Default.aspx

제가 이 라이브러리를 사용하는 것에 대한 방법은 아래의 사이트에서 참조했습니다.
아래 제가 올린 소스는 이 사이트에서 소개하고 있는 것과 거의 같습니다.
http://dobon.net/vb/dotnet/links/createzipfile.html


// 압축
void Compression()
{
    try
    {
        string zipPath = "test.zip";
        System.IO.FileStream writer = new System.IO.FileStream( zipPath,
                                        System.IO.FileMode.Create,
                                         System.IO.FileAccess.Write, System.IO.FileShare.Write);
       
        ICSharpCode.SharpZipLib.Zip.ZipOutputStream zos =
                            new ICSharpCode.SharpZipLib.Zip.ZipOutputStream(writer);
       
        foreach (string file in DiffFiles)
        {
            int Substringindex = textBox2.Text.Length;
            string f = file.Substring(Substringindex + 1);
           
            ICSharpCode.SharpZipLib.Zip.ZipEntry ze =
                                     new ICSharpCode.SharpZipLib.Zip.ZipEntry(f);

            System.IO.FileStream fs = new System.IO.FileStream( file,
                             System.IO.FileMode.Open, System.IO.FileAccess.Read,
                                  System.IO.FileShare.Read);
           
            byte[] buffer = new byte[fs.Length];
            fs.Read(buffer, 0, buffer.Length);
            fs.Close();
           
            ze.Size = buffer.Length;
           
            ze.DateTime = DateTime.Now;

            // 새로운 엔트리(파일)을 넣는다.
            zos.PutNextEntry(ze);
           
            // 쓰기
            zos.Write(buffer, 0, buffer.Length);
        }

        zos.Close();
        writer.Close();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString());
    }
}


// 해제
void DeCompression(string filename)
{
    string zipPath = filename;
    string extractDir = Environment.CurrentDirectory;

    System.IO.FileStream fs = new System.IO.FileStream( zipPath,
                                         System.IO.FileMode.Open,
                                 System.IO.FileAccess.Read, System.IO.FileShare.Read);
   
    ICSharpCode.SharpZipLib.Zip.ZipInputStream zis =
                            new ICSharpCode.SharpZipLib.Zip.ZipInputStream(fs);

    ICSharpCode.SharpZipLib.Zip.ZipEntry ze;
   
    while ((ze = zis.GetNextEntry()) != null)
    {
        if (!ze.IsDirectory)
        {
            string fileName = System.IO.Path.GetFileName(ze.Name);

            string destDir = System.IO.Path.Combine(extractDir,
                             System.IO.Path.GetDirectoryName(ze.Name));
           
            if (false == Directory.Exists(destDir))
            {
                System.IO.Directory.CreateDirectory(destDir);
            }

            string destPath = System.IO.Path.Combine(destDir, fileName);

            System.IO.FileStream writer = new System.IO.FileStream(
                            destPath, System.IO.FileMode.Create,
                                    System.IO.FileAccess.Write,
                                        System.IO.FileShare.Write);

            byte[] buffer = new byte[2048];
            int len;
            while ((len = zis.Read(buffer, 0, buffer.Length)) > 0)
            {
                writer.Write(buffer, 0, len);
            }

            writer.Close();
        }
    }

    zis.Close();
    fs.Close();
}


신고
by 흥배 2009.03.21 12:04
.net, C#
MSDN에 자세하게 나오는 것이지만 혹시 못 찾는 분을 위해서 올립니다.

출처 : MSDN


Windows Forms 컨트롤에 대한 접근은 기본적으로 스레드에 안전하지 않다. 그래서 두 개의 스레드에서 컨트롤에 접근을 하면 스레드 관련 버그가 발생 할 수 있다. 프로그램의 디버그 모드에서는 예외를 발생시킨다.



헤결 방법


// 델리게이트를 선언한다.

delegate void SetTextCallback(string text);


// 컨트롤의 접근은 따로 함수를 만들어서 접근하도록 한다.

private void SetText(string text)

{

    // InvokeRequired required compares the thread ID of the

    // calling thread to the thread ID of the creating thread.

    // If these threads are different, it returns true.

    if (this.textBox1.InvokeRequired)

    {

        SetTextCallback d = new SetTextCallback(SetText);
        this.Invoke(d, new object[] { text });
     }
    else 
    {
        this.textBox1.Text = text;
     }
}


스레드에서 아래 함수를 호출하면 된다.

SetTextCallback(SetText);


신고
by 흥배 2009.03.21 12:01
.NET 2.0 부터 GZip 압축 라이브러리가 생겼습니다.
하지만 이것은 기본적으로 하나의 파일만 압축이 가능합니다.

여러 개의 파일을 압축하는 방법은 3가지가 있습니다.

1. GZip 라이브러리 사용. 까다롭다.
http://www.vwd-cms.com/Forum/Forums.aspx?topic=18


2. 압축 관련 공개 라이브러리 사용
http://icsharpcode.net/OpenSource/SharpZipLib/Default.aspx


3. J#의 압축 라이브러리 사용
.NET Framework SDK를 설치 하지 않은 PC에는 VJSharp 재배포 패키지를 설치해야 된다.

출처 : http://dobon.net/vb/dotnet/links/createzipfile.html

압축

  1. java.io.FileOutputStream fos = new java.io.FileOutputStream(zipPath);
    java.util.zip.ZipOutputStream zos = new java.util.zip.ZipOutputStream(fos);

    // Zip 파일에 추가한다.
    foreach (string file in DiffFiles)
    {
            // ZIP에 추가 할 때의 파일 이름을 정한다.
            //string f = System.IO.Path.GetFileName(file);

            // 디렉토리를 보존할 때는 다음과 같이 한다.
            string f = file.Remove( 0, System.IO.Path.GetPathRoot(file).Length);
            f = f.Replace("\\","/");

            java.util.zip.ZipEntry ze = new java.util.zip.ZipEntry(f);
            ze.setMethod(java.util.zip.ZipEntry.DEFLATED);
            zos.putNextEntry(ze);

             // FileInputStream을 생성
             java.io.FileInputStream fis = new java.io.FileInputStream(file);
             
  2.           // 쓰기
             sbyte[] buffer = new sbyte[8192];
             int len;
             while ((len = fis.read(buffer, 0, buffer.Length)) > 0)
             {
                   zos.write(buffer, 0, len);
              }
             
  3.            // 닫는다
              fis.close();
              zos.closeEntry();
    }

    zos.close();
    fos.close();



해제

  1. // 풀 압축 파일의 이름
    string zipPath = filename;
    // 압축을 풀 곳의 디렉토리 설정
    string extractDir = Environment.CurrentDirectory;

    // 읽기
    java.io.FileInputStream fis = new java.io.FileInputStream(zipPath);
    java.util.zip.ZipInputStream zis = new java.util.zip.ZipInputStream(fis);

    // ZIP 내의 파일 정보를 취득
    java.util.zip.ZipEntry ze;
    while ((ze = zis.getNextEntry()) != null)
    {
           if (!ze.isDirectory())
           {
                   // 파일 이름
                   string fileName = System.IO.Path.GetFileName(ze.getName());
                   // 풀곳의 폴더
                   string destDir = System.IO.Path.Combine(extractDir, System.IO.Path.GetDirectoryName(ze.getName()));
                       
                   if( false == Directory.Exists(destDir) )
                   {
                          System.IO.Directory.CreateDirectory(destDir);
                    }

                     // 풀 곳의 패스
                    string destPath = System.IO.Path.Combine(destDir, fileName);
                    // FileOutputStream 생성
                    java.io.FileOutputStream fos = new java.io.FileOutputStream(destPath);

                    // 쓰기
                   sbyte[] buffer = new sbyte[8192];
                    int len;
                    while ((len = zis.read(buffer, 0, buffer.Length)) > 0)
                    {
                          fos.write(buffer, 0, len);
                     }

                     // 닫는다.
                     fos.close();
            }
    }
신고
by 흥배 2009.03.21 03:07
.net, C#

C#으로 프로그래밍을 할 때(또는 공부할 때) 참고 할만한 것은 다음과 같습니다.


1. 자신의 수준에 맞는 C# 언어 설명 책

사람마다 프로그래밍 수준이 다르기 때문에 딱 어떤 책이 좋다고 말하기는 어렵습니다.

서점에서 직접 책을 보면서 자신의 수준 및 취향에 맞는 책을 고르는 것이 최고라고 생각합니다.



2. MSDN

C++ 등의 언어를 이미 알고 있으면 C#에 대해서도 단편적으로 알고 있는 경우는 위의 1번 책을 사지

않고 바로 MSDN에 나온 C# 부분을 보면서 공부해도 충분합니다.

그리고 C# 및 .NET의 사양 및 레퍼런스가 다 나와 있으므로 프로그래밍을 할 때는 필수적인 것입니다.



3. Effective C# : 강력한 C# 코드를 구현하는 개발지침 50가지

C#에 대해서 좀 더 깊게 알려고 할 때나 C#의 특징을 잘 살리는 프로그래밍을 하고자 할 때

추천합니다. 저같이 C++알고 있는 상태에서 C# 언어에 대해 자세하게 숙지 못한 경우에

잘못 사용하는 우를 피할 수 있을 것 같습니다.

(이벤트도서) Effective C# : 강력한 C# 코드를 구현하는 개발지침 50가지
ㆍ번역서 | 2007-01-10
한빛미디어
Bill Wagner



4. 닷넷 프로그래밍 정복: C#, 윈폼, ADO

C# 활용에 대해서 제가 본 책 중에서는 좋았습니다. 왠만한 기본적이고 많이 사용하는 것에 대해서

잘 나와 있습니다.

(이벤트도서) 닷넷 프로그래밍 정복: C#, 윈폼, ADO



신고
by 흥배 2009.03.21 03:04
| 1 ··· 5 6 7 8 |

티스토리 툴바