[C#] 비동기 웹서버


Development note/C#  2013. 10. 16. 00:37

안녕하세요. 명월입니다.


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

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


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


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


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


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

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

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


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


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

즉 서버의 요청한 값을 프로토콜에 실어서 보내면 서버는 그에 해당하는 파일을 전송 해서 브라우져는 그 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<socketasynceventargs>(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<socketasynceventargs>(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<socketasynceventargs>(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

궁금한 점이 있으면 메일이나 쪽지 주세요..