[C#] 비동기 웹서버

개발 노트/C#  2013.10.16 00:37

 
안녕하세요 개발자 명월입니다.

 

요즘 가을이 훌쩍 다가왔는지 갑자기 급 추워졌네요...감기 조심하시구요...

아주 오랫만에 포스팅 나갑니다... 원래는 주말에 하려고 했는데 주말에 급 일이 생기는 바람이 늦게 올리네요.. 어떤 분이 저에게 웹서버 구성하는 소스 없냐고 물어 보시길레 아주 예전에 테스트 프로그램으로 작성한 적이 있어서 찾아봤는데.. 완성된 소스는 없네요.. 못찾은 건지 안보이네요...

 

그래서 미완성이지만 일단 올려보고 기회있을때 수정하겠습니다... (사실 이래 놓고 수정안한게 많아요... ㅎㅎ ^^)

 

보통은 웹서버를 직접 만들지는 않아요.. 왜냐면 무료 웹서버도 좋은게 무지 많기 때문에 굳이 만들어서 쓰는 사람은 드물죠... 그러나 아예 안만드는 건 아닙니다...

 

프로토콜 스크래핑 기법이라던가 아님 Soap 통신을 좀 더 빠르게 프로토콜을 개조(?) 하고 싶은 때는 쓰죠... 저도 한때 프로토콜 스크래핑 검증한다고 테스트 프로그램으로 만든것 입니다.

 

먼저 결과 화면을 보고 처리 해 보도록 하겠습니다.

 

 

먼저 프로토콜 통신화면입니다.. 무언가 지금 통신하는 듯한 전문이네요....

 

 

자 위의 내용은 소스를 웹브라우져(크롬)을 이용해서 기동한 모습입니다...

 

이제 로직 설명에 들어가 보겠습니다.

 

웹서버는 일단 소켓통신(TCP/IP)의 일회성통신(?)입니다..

즉 서버의 요청한 값을 프로토콜에 실어서 보내면 서버는 그에 해당하는 파일을 전송 해서 브라우져는 그 xml(http 문서겠지요)를 분석해서 화면에 표시하는 처리를 합니다.

 

이게 소스는 참 간단한건데 풀어서 이론을 설명하자니 복잡해지네요.....

일단 플로워 순서는 웹브라우져에서 디렉토리 위치 즉 사이트의 위치를 전송하면 서버는 그의 맞는 파일을 전송해주고 끊어버리면 전송은 끝나게 되겠습니다.

 

소스를 확인 해 보죠..

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace WebServer
{
    class WebServer
    {
        static void Main(string[] args)
        {
            new WebServer();
        }

        private Thread m_MainProcess;
        private Socket m_ServerSocket;
        public WebServer()
        {
            m_MainProcess = new Thread(new ThreadStart(MainThread));
            m_MainProcess.Start();

            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 8080);
            m_ServerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            m_ServerSocket.Bind(ipep);
            m_ServerSocket.Listen(20);

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed += new EventHandler(Accpet_Completed);
            m_ServerSocket.AcceptAsync(args);


        }

        private void Accpet_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket client = e.AcceptSocket;
            Socket server = (Socket)sender;

            if (client != null)
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                byte[] szBuffer = new byte[4096];
                args.SetBuffer(szBuffer, 0, 4096);
                args.UserToken = client;
                args.Completed += new EventHandler(Recieve_Completed);
                client.ReceiveAsync(args);
            }
            
            e.AcceptSocket = null;
            server.AcceptAsync(e);
        }

        private void Recieve_Completed(object sender, SocketAsyncEventArgs e)
        {
            byte[] pBuffer = e.Buffer;
            String sHttpText = Encoding.UTF8.GetString(pBuffer);
            sHttpText = sHttpText.Trim();
            sHttpText = sHttpText.Replace("\0", "");
            Console.WriteLine(sHttpText);
            int nPos = sHttpText.IndexOf(" ")+1;
            int nEPos = sHttpText.IndexOf(" ", nPos);
            if (nPos < 0 || nEPos < 0) return;
            String fileName = sHttpText.Substring(nPos,nEPos - nPos);
            nPos = sHttpText.IndexOf("Accept",nPos);
            nEPos = sHttpText.IndexOf("\n", nPos);
            String AcceptType = sHttpText.Substring(nPos, nEPos - nPos);

            //String html = "Hello World!!";

            StringBuilder strBuffer = new StringBuilder();

            strBuffer.Append("HTTP/1.1 200 OK\r\n");
            strBuffer.Append("Cache-Control: private\r\n");
            strBuffer.Append("Content-Type: text/html; charset=utf-8\r\n");
            strBuffer.Append("Server: SYWebServer - 1.00\r\n");
            //strBuffer.AppendFormat("Content-Length: {0}\r\n", html.Length);
            //strBuffer.Append("\r\n");
            //strBuffer.Append(html);
            byte[] data = FileRead(fileName, ref strBuffer);
            if (data != null && data.Length > 0)
            {
                String Header = strBuffer.ToString();
                pBuffer = Encoding.Default.GetBytes(Header);
               
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                args.SetBuffer(data, 0, data.Length);
                args.Completed += new EventHandler(Send_Completed);
                Socket client = (Socket)sender;
                client.Send(pBuffer);
                client.SendAsync(args);
            }
        }
        
        private byte[] FileRead(string filename,ref StringBuilder sb)
        {
            filename.Replace("/","\\");
            if (filename.CompareTo("/") == 0)
            {
                filename += "Default.html";
            }

            try
            {
                FileStream FS = new FileStream("c:\\Web" + filename, FileMode.Open, FileAccess.Read);
                byte[] pBuffer = new byte[FS.Length];
                FS.Read(pBuffer, 0, pBuffer.Length);
                FS.Close();
                sb.AppendFormat("Content-Length: {0}\r\n", pBuffer.Length);
                sb.Append("\r\n");
                return pBuffer;
            }
            catch (FileNotFoundException e)
            {
                return null;
            }
        }
        private void Send_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket client = (Socket)sender;
            client.Disconnect(true);
            client.Close();
        }
        public void MainThread()
        {
            while (true)
            {
                //Console.Write("Command>");
                string cmd = Console.ReadLine();
                if (cmd.Trim().CompareTo("exit") == 0)
                {
                    m_MainProcess.Abort();
                    break;
                }
            }
        }
    }
}

소스 부분 설명입니다.

 

 

 

생성자와 Accept 이벤트는 일단 통신프로그램과 큰 차이는 없습니다. 생성자 에서는 먼저 소켓을 열고 포트는 8080으로 잡았네요. AcceptAsync 모드로 등록을 시켰네요...

그 뒤 접속이 완료 되면 다시 Recieve를 를 등록시켰습니다. 사이즈는 4096으로 했네요..

 

다음은 Recieve 부분을 살펴 보겠습니다.

 

 

 

 

윗 부분은 프로토콜의 수신부 이네요...

수신부 에서는 다른 처리는 없고 그냥 파일 경로만 뚝 잘라내서 FileRead를 한후 그 데이터를 Send로 날려버렸습니다. 물론 Send가 완료 된 후에 이벤트도 발생 시키네요..

 

 

 

위 부분은 파일을 읽어 드리는 부분과 전문을 보낸 후의 결과 이벤트 처리가 있습니다.

전문을 보내고 나면 그냥 Connection을 종료 시키네요...

 

브라우져와 서버는 브라우져에서 경로 요청을 하면 서버는 해당 경로의 파일을 바이너리로 변환하고 Send를 한후 Disconnect 하는 것으로 처리가 되겠네요....

 

예제 소스를 첨부하니 확인 해 보네요...

 

모르시는 게 있으면 메일이나 쪽지 주세요..

 

 

WebServer.zip

 

 

 

 


댓글 4개가 달렸습니다.
댓글쓰기
  1. yhs
    2013.10.21 14:44 신고 |  수정/삭제  댓글쓰기

    안녕하세요 예제를 따라하면서 배워가는 사람입니다
    이번 예제에서 Content-Type: text/html 로 지정 해놨는데 다른 브라우저를 사용하면
    다른결과가 나와서 궁금해서 질문드립니다.

    예로 html문서로 크롬에서 열었을때 실행이 되며 jpg,png를 사용했을때는 타입을 text/html로 명시해놨기에 읽어도 텍스트로 읽고 깨지는 반면에
    같은 조건에서 Explorer로 실행하면 jpg,png파일까지 자체적으로 읽어지더군요
    어떤 원리로 Explorer는 명시되어 있는 조건 외에 파일들을 읽는지 궁금합니다

    • 明月 v명월v
      2013.10.26 09:30 신고 |  수정/삭제

      안녕하세요... 먼저 블로그 방문 감사합니다...
      위 소스는 완성된 게 아닙니다... ㅠㅠ
      프로토콜 부분은 손을 봐야 하는게 맞습니다...
      위 예제는 프로토콜의 설명 보다는 웹서버의 동작은 이렇다라는 걸 설명해 놓았기 때문에 그렇네요.. ㅠㅠ 프로토콜 부분은 역시 손을 봐야 하는 부분입니다.. 죄송합니다. ㅠㅠ

  2. 푸실
    2013.10.28 16:41 신고 |  수정/삭제  댓글쓰기

    하하 ... 정말 감사합니다.
    자바에서 C#으로 오니 개발이 쉽지만은 않네요 ...

    • 明月 v명월v
      2013.10.30 01:20 신고 |  수정/삭제

      블로그 방문 감사합니다.
      ㅎㅎ 요즘 저랑은 반대네요...
      자바안건이 있어서 자바 책을 뒤적이고 있네요 ㅎㅎ
      근데 저는 오히려 문법이 비슷해서 첨 배울때보단 진행이 빠르네요..