国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發 > 綜合 > 正文

使用C#開發自己的web服務器(圖)

2024-07-21 02:25:52
字體:
來源:轉載
供稿:網友
  • 本文來源于網頁設計愛好者web開發社區http://www.html.org.cn收集整理,歡迎訪問。
  • 摘要

    這 篇文章討論了如何使用c#開發一個簡單的web服務器應用程序。盡管我們可以使用任何一種支持.net的編程語言開發,但我選擇了c#。本篇文章中的代碼 是使用微軟的β2版的visual c# compiler version 7.00.9254 [clr version v1.0.2914]編譯通過的,對代碼作一些小的改動后,使用β1版也可能編譯通過。該web服務器應用程序能夠與iis或其他任何web服務器軟件同 時在一臺服務器上運行,只要為它指定一個空閑的端口即可。在本篇文章中,我還假定讀者對.net、c#或visual basic .net有一定的了解。

    該web服務器應用程序能夠向瀏覽器返回html格式的文件,而且支持圖像,它不加載嵌入式圖像或支持任何一種腳本語言。為了簡單起見,我將它開發成一個命令行應用程序。

    準備工作

    首先,我們需要為這個web服務器應用程序定義一個根文件夾,例如,c:/mypersonalwebserver,然后在該要根目錄下創建一個數據目錄,例如,c:/mypersonalwebserver/data;最后在數據目錄下創建三個文件,例如:

    mimes.dat
      vdirs.dat
      default.dat

    mime.dat中將包含該web服務器支持的mime類型,其格式為<擴展名>; ,例如:

    .html;text/html
      .htm;text/html
      .bmp;image/bmp

    vdirs.dat中包含有虛擬目錄的信息,格式為; <物理目錄>,例如:

    /; c:/mywebserverroot/
      test/; c:/mywebserverroot/imtiaz/

    default.dat中包含有虛擬目錄中文件的信息,例如:

    default.html
      default.htm
      index.html
      index.htm

    為簡單起見,我們將使用文本文件存儲所有的信息,但我們也可以使用xml等其他的格式。在開始研究代碼之前,我們先來看一下在登錄網站時瀏覽器需要傳遞的頭部信息。

    我們以請求test.html為例進行說明。在瀏覽器的地址欄輸入http://localhost:5050/test.html(記住,需要在url中包括端口號),服務器將得到下面的信息:

    〈/drive:/physicaldir〉
      get /test.html http/1.1
    accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/vnd.ms-powerpoint, application/vnd.ms-excel, application/msword, */*
    accept-language: en-usaccept-encoding: gzip, deflate
    user-agent: mozilla/4.0 (compatible; msie 5.5; windows nt 4.0; .net clr 1.0.2914)
    host: localhost:5050connection: keep-alive
      開始編程
      namespace imtiaz
      {
      using system;
      using system.io;
      using system.net;
      using system.net.sockets;
      using system.text;
      using system.threading ;
      class mywebserver
      {
      private tcplistener mylistener ;
      private int port = 5050 ; // 可以任意選擇空閑的端口
      //生成tcplistener的構建器開始監聽給定的端口,它還啟動調用startlisten()方法的一個線程
      public mywebserver()
      {
      try
      {
      //開始監聽給定的端口
      mylistener = new tcplistener(port) ;
      mylistener.start();
      console.writeline("web server running... press ^c to stop...");
      //啟動調用startlisten方法的線程
      thread th = new thread(new threadstart(startlisten));
      th.start() ;
      }
      catch(exception e)
      {
      console.writeline("an exception occurred while listening :" +e.tostring());
      }
      }

    我們定義了名字空間,包括應用程序必需的引用,初始化了構建器中的端口,啟動了端口監聽進程,創建了一個新的線程調用startlisten函數。

    我們假設用戶沒有在url中提供文件名,在這種情況下我們必須自己確定缺省的文件名,并將它返回給瀏覽器,就象在iis中的文檔標簽中定義缺省的文檔那樣。

    我們已經在default.dat中存儲了缺省的文件名,并將文件存儲在了數據目錄中。getthedefaultfilename函數將目錄路徑作為輸入參數,打開default.dat文件,在目錄中查找文件,根據是否找到了文件返回文件名或一個空格。

    public string getthedefaultfilename(string slocaldirectory)
      {
      streamreader sr;
      string sline = "";
      try
      {
      //打開default.dat,獲得缺省清單
      sr = new streamreader("data//default.dat");
      while ((sline = sr.readline()) != null)
      {
      //在web服務器的根目錄下查找缺少文件
      if (file.exists( slocaldirectory + sline) == true)
      break;
      }
      }
      catch(exception e)
      {
      console.writeline("an exception occurred : " + e.tostring());
      }
      if (file.exists( slocaldirectory + sline) == true)
      return sline;
      else
      return "";
      }

    象在iis中那樣,我們必須將虛擬目錄解析為物理目錄。在vdir.dat中,我們已經存儲了實際的物理目錄和虛擬目錄之間的映像關系。需要記住的是,在任何情況下,文件的格式都是重要的。

    public string getlocalpath(string smywebserverroot, string sdirname)
      {
      treamreader sr;
      string sline = "";
      string svirtualdir = "";
      string srealdir = "";
      intistartpos = 0;
      //刪除多余的空格
      sdirname.trim();
      // 轉換成小寫
      smywebserverroot = smywebserverroot.tolower();
      // 轉換成小寫
      sdirname = sdirname.tolower();
      try
      {
      //打開vdirs.dat文件,獲得虛擬目錄
      sr = new streamreader("data//vdirs.dat");
      while ((sline = sr.readline()) != null)
      {
      //刪除多余的空格
      sline.trim();
      if (sline.length > 0)
      {
      //找到分割符
      istartpos = sline.indexof(";");
      // 轉換成小寫
      sline = sline.tolower();
      svirtualdir = sline.substring(0,istartpos);
      srealdir = sline.substring(istartpos + 1);
      if (svirtualdir == sdirname)
      {
      break;
      }
      }
      }
      }
      catch(exception e)
      {
      console.writeline("an exception occurred : " + e.tostring());
      }
      if (svirtualdir == sdirname)
      return srealdir;
      else
      return "";
      }
      我們還必須使用用戶提供的文件擴展名確定mime類型。
      public string getmimetype(string srequestedfile)
      {
      streamreader sr;
      string sline = "";
      string smimetype = "";
      string sfileext = "";
      string smimeext = "";
      // 轉換成小寫
      srequestedfile = srequestedfile.tolower();
      int istartpos = srequestedfile.indexof(".");
      sfileext = srequestedfile.substring(istartpos);
      try
      {
      //打開vdirs.dat文件,獲得虛擬目錄
      sr = new streamreader("data//mime.dat");
      while ((sline = sr.readline()) != null)
      {
      sline.trim();
      if (sline.length > 0)
      {
      //找到分割符
      istartpos = sline.indexof(";");
      // 轉換成小寫
      sline = sline.tolower();
      smimeext = sline.substring(0,istartpos);
      smimetype = sline.substring(istartpos + 1);
      if (smimeext == sfileext)
      break;
      }
      }
      }
      catch (exception e)
      {
      console.writeline("an exception occurred : " + e.tostring());
      }
      if (smimeext == sfileext)
      return smimetype;
      else
      return "";
      }

    下面我們來編寫建立和向瀏覽器(客戶端)發送頭部信息的函數。

    public void sendheader( string shttpversion,
      string smimeheader,
      int itotbytes,
      string sstatuscode,
      ref socket mysocket)
      {
      string sbuffer = "";
      //如果用戶沒有提供mime類型,則將其缺省地設置為text/html
      if (smimeheader.length == 0 )
      {
      smimeheader = "text/html"; // default mime type is text/html
      }
      sbuffer = sbuffer + shttpversion + sstatuscode + "/r/n";
      sbuffer = sbuffer + "server: cx1193719-b/r/n";
      sbuffer = sbuffer + "content-type: " + smimeheader + "/r/n";
      sbuffer = sbuffer + "accept-ranges: bytes/r/n";
      sbuffer = sbuffer + "content-length: " + itotbytes + "/r/n/r/n";
      byte[] bsenddata = encoding.ascii.getbytes(sbuffer);
      sendtobrowser( bsenddata, ref mysocket);
      console.writeline("total bytes : " + itotbytes.tostring());
      }
      sendtobrowser函數向瀏覽器發送信息,這是一個工作量比較大的函數。
      public void sendtobrowser(string sdata, ref socket mysocket)
      {
      sendtobrowser (encoding.ascii.getbytes(sdata), ref mysocket);
      }
      public void sendtobrowser(byte[] bsenddata, ref socket mysocket)
      {
      int numbytes = 0;
      try
      {
      if (mysocket.connected)
      {
      if (( numbytes = mysocket.send(bsenddata, bsenddata.length,0)) == -1)
      console.writeline("socket error cannot send packet");
      else
      {
      console.writeline("no. of bytes send {0}" , numbytes);
      }
      }
      else
      console.writeline("connection dropped....");
      }
      catch (exception e)
      {
      console.writeline("error occurred : {0} ", e );
      }
      }
      我們已經有了編寫一個互聯網服務器應用程序的一些部件,下面我們將討論互聯網服務器應用程序中的關健函數。
      public void startlisten()
      {
      int istartpos = 0;
      string srequest;
      string sdirname;
      string srequestedfile;
      string serrormessage;
      string slocaldir;
      string smywebserverroot = "c://mywebserverroot//";
      string sphysicalfilepath = "";
      string sformattedmessage = "";
      string sresponse = "";
      while(true)
      {
      //接受一個新的連接
      socket mysocket = mylistener.acceptsocket() ;
      console.writeline ("socket type " +mysocket.sockettype );
      if(mysocket.connected)
      {
      console.writeline("/nclient connected!!/n==================/n
      client ip {0}/n", mysocket.remoteendpoint) ;
      //生成一個字節數組,從客戶端接收數據
      byte[] breceive = new byte[1024] ;
      int i = mysocket.receive(breceive,breceive.length,0) ;
      //將字節型數據轉換為字符串
      string sbuffer = encoding.ascii.getstring(breceive);
      //上前我們將只處理get類型
      if (sbuffer.substring(0,3) != "get" )
      {
      console.writeline("only get method is supported..");
      mysocket.close();
      return;
      }
      // 查找http請求
      istartpos = sbuffer.indexof("http",1);
      // 獲取“http”文本和版本號,例如,它會返回“http/1.1”
      string shttpversion = sbuffer.substring(istartpos,8);
      //解析請求的類型和目錄/文件
      srequest = sbuffer.substring(0,istartpos - 1);
      //如果存在/符號,則使用/替換
      srequest.replace("http://","/");
      //如果提供的文件名中沒有/,表明這是一個目錄,我們解危需要查找缺省的文件名
      if ((srequest.indexof(".") <1) && (!srequest.endswith("/")))
      {
      srequest = srequest + "/";
      }
      //解析請求的文件名
      istartpos = srequest.lastindexof("/") + 1;
      srequestedfile = srequest.substring(istartpos);
      //解析目錄名
      sdirname = srequest.substring(srequest.indexof("/"), srequest.lastindexof("/")-3);
      上面的代碼無須多加解釋,它接收用戶的請求,將用戶的請求由字節型數據轉換為字符串型數據,然后查找請求的類型,解析http的版本號、文件和目錄信息。
      // 確定物理目錄
      if ( sdirname == "/")
      slocaldir = smywebserverroot;
      else
      {
      //獲得虛擬目錄
      slocaldir = getlocalpath(smywebserverroot, sdirname);
      }
      console.writeline("directory requested : " + slocaldir);
      //如果物理目錄不存在,則顯示出錯信息
      if (slocaldir.length == 0 )
      {
      serrormessage = "〈h2〉error!! requested directory does not exists〈/h2〉〈br〉";
      //serrormessage = serrormessage + "please check data//vdirs.dat";
      //對信息進行格式化
      sendheader(shttpversion, "", serrormessage.length, " 404 not found", ref mysocket);
      //向瀏覽器發送信息
      sendtobrowser(serrormessage, ref mysocket);
      mysocket.close();
      continue;
      }

    提 示:微軟的ie瀏覽器一般情況下總會顯示一個比較“友好”一點的http錯誤網頁,如果要顯示我們的web服務器應用程序的錯誤信息,需要禁用ie中“顯 示友好http錯誤信息”的功能,方法是依次點擊“工具”->“互聯網工具”,然后在其中的“高級”標簽中即可以看到該選項。

    如 果用戶沒有提供目錄名,web服務器應用程序會使用getlocalpath函數獲取物理目錄的信息,如果目錄不存在(或者沒有映射為vdir.dat中 的條目),就會向瀏覽器發送錯誤信息。接下來web服務器應用程序會確定文件名,如果用戶沒有提供文件名,web服務器應用程序可以調用 getthedefaultfilename函數獲取文件名,如果有錯誤發生,則會將錯誤信息發送到瀏覽器。

    //如果文件名不存在,則查找缺省文件列表
      if (srequestedfile.length == 0 )
      {
      // 獲取缺省的文件名
      srequestedfile = getthedefaultfilename(slocaldir);
      if (srequestedfile == "")
      {
      serrormessage = "〈h2〉error!! no default file name specified〈/h2〉";
      sendheader(shttpversion, "", serrormessage.length, " 404 not found",
      ref mysocket);
      sendtobrowser ( serrormessage, ref mysocket);
      mysocket.close();
      return;
      }
      }

    下面我們來識別mime類型:

    string smimetype = getmimetype(srequestedfile);
      //構建物理路徑
      sphysicalfilepath = slocaldir + srequestedfile;
      console.writeline("file requested : " + sphysicalfilepath);
      最后一個步驟是打開被請求的文件,并將它發送給瀏覽器。
      if (file.exists(sphysicalfilepath) == false)
      {
      serrormessage = "〈h2〉404 error! file does not exists...〈/h2〉";
      sendheader(shttpversion, "", serrormessage.length, " 404 not found", ref mysocket);
      sendtobrowser( serrormessage, ref mysocket);
      console.writeline(sformattedmessage);
      }
      else
      {
      int itotbytes=0;
      sresponse ="";
      filestream fs = new filestream(sphysicalfilepath, filemode.open,fileaccess.read,
      fileshare.read);
      // 創建一個能夠從filestream中讀取字節數據的reader
      binaryreader reader = new binaryreader(fs);
      byte[] bytes = new byte[fs.length];
      int read;
      while((read = reader.read(bytes, 0, bytes.length)) != 0)
      {
      // 從文件中讀取數據,并將數據發送到網絡上
      sresponse = sresponse + encoding.ascii.getstring(bytes,0,read);
      itotbytes = itotbytes + read;
      }
      reader.close();
      fs.close();
      sendheader(shttpversion, smimetype, itotbytes, " 200 ok", ref mysocket);
      sendtobrowser(bytes, ref mysocket);
      //mysocket.send(bytes, bytes.length,0);
      }
      mysocket.close();
      }
      }
      }
      }
      }

    編譯和執行

    可以使用下圖所示的命令編譯我們的web服務器應用程序:

    在我使用的.net開發工具中,無須指定任何庫的名字,在較老版本的.net開發工具中,可能會需要使用/r參數添加對dll庫文件的引用。

    要運行該web服務器應用程序,只要如下圖那樣輸入程序的名字,并按回車鍵即可。

    now, let say user send the request, our web server will identify the default file name and sends to the browser.

    現在,我們假設用戶發送了請求,我們的web服務器應用程序將會決定使用缺省的文件,并將它返回給瀏覽器。如下圖所示:

    當然了,用戶也可以請求圖像文件

    可能的改進

    webserver仍然有許多地方可以加以改進。它不支持嵌入式圖像和腳本,讀者可以自己編寫isapi過濾器,也可以使用iis isapi過濾器。

    結束語

    本篇文章展示了開發web服務器的基本原理,我們仍然可以對文章中的web服務器應用程序進行許多改進,希望它能夠起到拋磚引玉的作用,對讀者有所啟迪。

    發表評論 共有條評論
    用戶名: 密碼:
    驗證碼: 匿名發表
    主站蜘蛛池模板: 雷山县| 多伦县| 依兰县| 宁阳县| 阜康市| 西乡县| 来安县| 班戈县| 澄迈县| 彩票| 大田县| 绥棱县| 翼城县| 堆龙德庆县| 奉新县| 紫阳县| 琼海市| 舞阳县| 崇礼县| 香格里拉县| 赣榆县| 陵水| 来宾市| 霸州市| 甘南县| 桦川县| 凤翔县| 凤山市| 文水县| 明光市| 肃北| 姚安县| 措勤县| 武穴市| 桐庐县| 永川市| 石林| 化隆| 宜宾市| 永善县| 广水市|