1. 程式人生 > >一個多執行緒web伺服器例項(C,Linux,詳細的web伺服器原理)

一個多執行緒web伺服器例項(C,Linux,詳細的web伺服器原理)

系統:fedora core 5
編譯器:g++
實現功能:通過http協議,用瀏覽器檢視伺服器上的html,htm,jpg,jpeg,gif,png,css檔案 ,或者說檢視帶有jpg,jpeg,gif等檔案的網頁,即是web~
把程式碼複製下來到linux裡,照著後面的方法編譯、執行,就可以看到一個簡單的多執行緒伺服器的效果了。

原理:
在瀏覽器中輸入一個網址,回車之後,瀏覽器會向相應主機的相應埠傳送一段報文,如果是http協議的(如平常看到的網頁的傳輸協議),就會發送HTTP請求報文。下面是一個報文的例子:

GET /index.html HTTP/1.1
Host: 127.0.0.1:8848
User-Agent: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.8.0.1) Gecko/20060313 Fedora/1.5.0.1-9 Firefox/1.5.0.1 pango-text
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive

我們在伺服器端把收到的資料打印出來,可以看到瀏覽器發過來的就是這個東西。當然,也可以用ethereal等抓包工具來抓獲這些報文。關於報文裡寫的是什麼意思,網上有很多資料的,GOOGLE一下就有了。我們只看第一行。

GET表示是要從伺服器獲取檔案,/index.html是檔案的路徑,這個路徑是相對於伺服器端程式所在資料夾的路徑。如我的伺服器端程式放在/home/mio/program/webserver1707/裡面,那這個index.html在伺服器上的絕對路徑就是/home/mio/program/webserver1707/index.html。如果報文裡是GET /admin/login.html HTTP/1.1的話,那麼login.html檔案在伺服器端的路徑是/home/mio/program/webserver1707/admin/login.html.HTTP/1.1表示的是HTTP協議的版本是1.1.

伺服器端程式執行後,一直監聽8848端品(0-1023的埠由IANA統一分配和控制的,不要用,最好選大一些的埠號。我原來用了個1234,用不了,還是選大一點好,可以用5460之類的啊~:) ),當監聽到客戶端發來的請求後,就與客戶端建立連結,接收客戶端發過來的請求報文。我們如果把這些報文打出來,就可以看到就是與上面請求報文類似的東西了。

下面我們要根據所接受的到的請求報文(GET /index.html HTTP/1.1)來決定放給客戶端(即瀏覽器)什麼東西。這裡我們看到瀏覽器要的是index.html這樣一個html文字,我們就在相應路徑(/home/mio/program/webserver1707/index.html)找到這個檔案,不過不要急著發給客戶端,我們要先告訴客戶端,發過去的是一個html檔案,讓瀏覽器做好相應的準備。怎麼讓瀏覽器知道呢?我們還是用報文,這個報文叫響應報文。報文由狀態行、首部行、實體主體三部分組成。狀態行只有一行,它和首部行、首部行的每行之間是沒有空行的,但是首部行與實體主體之間有一個空行,表明從這個空行開始,就是你瀏覽器要的資料了。下面是一個用ethereal抓到的響應報文: 

HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
Server: GWS/2.1
Content-Length: 1851
Date: Sat, 14 Oct 2006 11:33:39 GMT

<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Google</title><style><!--
body,td,a,p,.h{font-family:arial,sans-serif}
.h{font-size:20px}
.q{color:#00c}
--></style>
<script>
<!--
function sf(){document.f.q.focus();}
function clk(url,oi,cad,ct,cd,sg){if(document.images){var e = window.encodeURIComponent ? encodeURIComponent : escape;var u="";var oi_param="";var cad_param="";if (url) u="&url="+e(url.replace(/#.*/,"")).replace(//+/g,"%2B");if (oi) oi_param="&oi="+e(oi);if (cad) cad_param="&cad="+e(cad);new Image().src="/url?sa=T"+oi_param+cad_param+"&ct="+e(ct)+"&cd="+e(cd)+u+"&ei=E8swRYIOkpKwAvzZ8JkB"+sg;}return true;}
// -->
</script>
</head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onLoad=sf() topmargin=3 marginheight=3><center><div align=right nowrap style="padding-bottom:4px" width=100%><font size=-1><b>[email protected]il.com</b>&nbsp;|&nbsp;<a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Dzh-CN&sig=__1eXNMn0jGllmJ57x74DzjVvy6Vk=" onmousedown="return clk('/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Dzh-CN&sig=__1eXNMn0jGllmJ57x74DzjVvy6Vk=','promos','hppphou:zh-cn_all','pro','1','&sig2=zclmOmtQiZPPuTCMWUJMZA')">個性化主頁</a>&nbsp;|&nbsp;<a href="https://www.google.com/accounts/ManageAccount">我的帳戶</a>&nbsp;|&nbsp;<a href="http://www.google.com/accounts/Logout?continue=http://www.google.com/intl/zh-CN/">退出</a></font></div><img src="/intl/zh-CN_ALL/images/logo.gif" width=286 height=110 alt="Google"><br><br>
<form action=/search name=f><script><!--
function qs(el) {if (window.RegExp && window.encodeURIComponent) {var ue=el.href;var qe=encodeURIComponent(document.f.q.value);if(ue.indexOf("q=")!=-1){el.href=ue.replace(new RegExp("q=[^&$]*"),"q="+qe);}else{el.href=ue+"&q="+qe;}}return 1;}
// -->
..........

第一個空行上面的就是“說明”了,下面是html程式碼。有了說明,瀏覽器就知道這是什麼了,拿到這段資料後,就把這些html標籤解釋成各種各樣的元素,在瀏覽器上有序地顯示出來。瀏覽器還蠻聰明的,當看到<img src=..>標籤,那就會又自己發一個請求報文給伺服器,要求得到一個影象檔案,請求報文就像:

GET /image/pp.jpg HTTP/1.1
....

這樣,伺服器端就找到這個.jpg影象,加上"說明"之後發給瀏覽器,瀏覽器收到後就顯示在對應的位置上。遇到包含css、js...的標籤也一樣。

如此重複,一個完整的web就會呈現在我們眼前了。

伺服器端程式碼:

/*****************************************************************

  mymultiwebserver.c 

  system:redhat linux Fedora Core 5

  enviroment:g++

  compile command:g++ -g -o mymultiwebserver -lpthread

  date:10/15/2006

  By Manio

 ****************************************************************
*/

#include 
<stdlib.h>

#include 
<sys/types.h>

#include 
<sys/socket.h>

#include 
<sys/stat.h>

#include 
<netinet/in.h>

#include 
<unistd.h>

#include 
<pthread.h>

#include 
<stdio.h>

#include 
<string.h>

#include 
<arpa/inet.h>#define PORT 8848#define BACKLOG 5#define MAXDATASIZE 1000#define DEBUG 1void process_cli(int connectfd, sockaddr_in client);

int sendobj(int connectfd,char* serverfilepath);

int IsDIR(char* fpath);

int fileordirExist(char* fpath);

char* getextname(char*);

int writehead(FILE* cfp, char* extname);

void* start_routine(void* arg);

void msg404(int connectfd);



struct ARG {

       
int connfd;

       sockaddr_in client;

       };

       

main()

{

      
int listenfd, connectfd;

      pthread_t thread;         
//id of thread
      ARG 
*arg;            //pass this var to the threadstruct sockaddr_in server; //server's address infostruct sockaddr_in client; //client'sint sin_size;

      

      
//create tcp socket
#ifdef DEBUG

      printf(
"socket.... ");

#endifif ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) ==-1) {

                    perror(
"creating socket failed.");

                    exit(
1);

      }

      

      
int opt = SO_REUSEADDR;

      setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, 
&opt, sizeof(opt));

      

      bzero(
&server,sizeof(server));

      server.sin_family 
= AF_INET;

      server.sin_port 
= htons(PORT);

      server.sin_addr.s_addr 
= htonl(INADDR_ANY);

      printf(
"bind.... ");

      
if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) ==-1) {

          perror(
"bind error.");

          exit(
1);

      }

      

      printf(
"listen.... ");

      
if(listen(listenfd,BACKLOG) ==-1) {

          perror(
"listen() error ");

          exit(
1);

      }



      sin_size 
=sizeof(struct sockaddr_in);

      
while(1)

      {

          
//accept() using main thread
          printf(
"accepting.... ");

          
if((connectfd = accept(listenfd,

                     (
struct sockaddr *)&client,

                     (socklen_t
*)&sin_size)) ==-1) {

              printf(
"accept() error ");

          }



          arg 
=new ARG;

          arg
->connfd = connectfd;

          memcpy((
void*)&arg->client, &client, sizeof(client));

        

          
//invoke start_routine to handle this thread
#ifdef DEBUG

          printf(
"thread_creating....");

#endifif(pthread_create(&thread, NULL, start_routine, (void*)arg)){

              perror(
"pthread_create() error");

              exit(
1);

          }          

      }

      close(listenfd);      

}





//handle the request of the clientvoid process_cli(int connectfd, sockaddr_in client)

{

    
int num;

    
//char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];char requestline[MAXDATASIZE], filepath[MAXDATASIZE], cmd[MAXDATASIZE],extname[MAXDATASIZE];

    
int c;

    FILE 
*fp;

    FILE 
*cfp;

    fp 
= fdopen(connectfd,"r");    

    

#ifdef DEBUG

    printf(
"the host is:%s  ",inet_ntoa(client.sin_addr) );

#endif

    fgets(requestline,MAXDATASIZE,fp);

#ifdef DEBUG

    printf(
" THE REQUEST IS :%s ",requestline);

#endif

    strcpy(filepath,
"./");

    sscanf(requestline,
"%s%s",cmd,filepath+2);

    strcpy(extname, getextname(filepath));

#ifdef DEBUG

    printf(
"cmd:%s filepath:%s extname:%s ",cmd,filepath,extname);

    

    printf(
"string comparing :::::::::::::start::::::::::::::: ");    

#endifif(strcmp(cmd,"GET"==0){

    
//the command is get
#ifdef DEBUG

        printf(
"cmd(%s)==GET ",cmd);

#endif//is this a file or dir or notexist?if(fileordirExist(filepath)){

        
//is a file or dir or none

            
//is this a dir if(IsDIR(filepath)){

                
//is a dir
#ifdef DEBUG

                printf(
"%s is a DIR ",filepath);

#endifif( fileordirExist( strcat(filepath,"index.htm") )){

                    sendobj(connectfd,
"index.htm");

                }
elseif(fileordirExist(strcat(filepath,"index.html"))){

                    sendobj(connectfd,
"index.htm");

                }
else{

                    msg404(connectfd);

                }

            }
else{

                    
//is a file
#ifdef DEBUG

                    printf(
"%s is a file",filepath);

#endif

                    sendobj(connectfd,filepath);

            }

        }
else{

#ifdef DEBUG

            printf(
"404 ");

#endif

            msg404(connectfd);

        }

    }
else{

#ifdef DEBUG

        printf(
"cmd(%s)!=GET ",cmd);

#endif

    }

#ifdef DEBUG

    printf(
":::::::::::::end::::::::::::::: ");    

#endif

    close(connectfd);

}

//send the 404 error message to the clientvoid msg404(int connectfd)

{

    
char* msg;

    msg  
="HTTP/1.0 404 Not Found Content-Type: text/plain 404 not found by Manio";

    send(connectfd,msg,strlen(msg),
0);

}

//is the filepath a file  or directoryint fileordirExist(char* fpath)

{

    
struct stat filestat;

    
return (  stat(fpath,&filestat) !=-1);

}



// is the filepath a directoryint IsDIR(char* fpath)

{

#ifdef DEBUG

    printf(
"IN IsDIR ");

#endifstruct stat filestat;

    
return ( stat(fpath,&filestat) !=-1&& S_ISDIR(filestat.st_mode));

}



//send the data of the file which the client wantint sendobj(int connectfd,char* serverfilepath)

{

    FILE
* sfp,*cfp;

    
int c;

    sfp 
= fopen(serverfilepath,"r");

    cfp 
= fdopen(connectfd,"w");



    writehead(cfp,getextname(serverfilepath));

    
while( (c = getc(sfp)) != EOF)putc(c,cfp);    

    fflush(cfp);

    
return0;

}

//write the packet header to the clientint writehead(FILE* cfp, char* extname)

{

#ifdef DEBUG

    printf(
"INWRITEHEAD:::::::extname is %s::::::: ",extname);

#endifchar* content ="text/plain";

    
if( strcmp(extname,"html"==0|| strcmp(extname,"htm"==0)

        content 
="text/html";

    
elseif ( strcmp(extname,"css"==0 )

        content 
="text/css";

    
elseif ( strcmp(extname,"gif"==0 )

        content 
="image/gif";

    
elseif ( strcmp(extname,"jpeg"==0|| strcmp(extname,"jpg"==0)

        content 
="image/jpeg";

    
elseif ( strcmp(extname,"png"==0)

        content 
="image/png";

#ifdef DEBUG

    printf(
"HTTP/1.1 200 OK ");

    printf(
"Content-Type: %s ",content);

#endif

    fprintf(cfp,
"HTTP/1.1 200 OK ");

    fprintf(cfp,
"Content-Type: %s ",content);

    
return0;

}



//get the extent name of the filechar* getextname(char* filepath)

{

    
char* p;

    
if(( p  =  strrchr(filepath,'.')) != NULL)

               
return p+1;

    
return NULL;           

}



//invoked by pthread_createvoid* start_routine(void* arg)

{

    ARG 
*info;

    info 
= (ARG *)arg;

    
//handle client's requirement
    process_cli(info
->connfd, info->client);



    delete arg;

    pthread_exit(NULL);

}

執行方法:

在fc5中開啟控制檯,按下面的方法進行

[[email protected] webserver1707]# ls
admin           header   img        index.htm~
chinaunix.html  header~  index.htm  mymultiwebserver.c
[[email protected] webserver1707]# g++ -g -o mymultiwebserver mymultiwebserver.c -lpthread
mymultiwebserver.c: In function 鈥榲oid* start_routine(void*)鈥?
mymultiwebserver.c:253: 璀﹀憡錛氬垹闄?鈥榲oid*鈥?鏈畾涔?[[email protected] webserver1707]# ./mymultiwebserver socket....
bind....
listen....
accepting....
thread_creating....accepting....
the host is:127.0.0.1
THE REQUEST IS :GET / HTTP/1.1

cmd:GET
filepath:.//
extname://
string comparing
:::::::::::::start:::::::::::::::
cmd(GET)==GET
IN IsDIR
.// is a DIR
INWRITEHEAD:::::::extname is htm:::::::
HTTP/1.1 200 OK
Content-Type: text/html

thread_creating....accepting....
:::::::::::::end:::::::::::::::
thread_creating....accepting....
the host is:127.0.0.1
THE REQUEST IS :GET /img/sb.jpg HTTP/1.1

cmd:GET
filepath:.//img/sb.jpg
extname:jpg
string comparing
:::::::::::::start:::::::::::::::
cmd(GET)==GET
IN IsDIR
.//img/sb.jpg is a fileINWRITEHEAD:::::::extname is jpg:::::::
HTTP/1.1 200 OK
Content-Type: image/jpeg

:::::::::::::end:::::::::::::::
the host is:127.0.0.1
THE REQUEST IS :GET /img/gcc.png HTTP/1.1

cmd:GET
filepath:.//img/gcc.png
extname:png
string comparing
:::::::::::::start:::::::::::::::
cmd(GET)==GET
IN IsDIR
.//img/gcc.png is a fileINWRITEHEAD:::::::extname is png:::::::
HTTP/1.1 200 OK
Content-Type: image/png

:::::::::::::end:::::::::::::::

放一個index.htm檔案在此程式所在的資料夾,開啟瀏覽器,在位址列輸入http://127.0.0.1:8848/,就可以看到網頁了~

學藝不精,如文中有不當之處,請指出:)

相關推薦

一個執行web伺服器例項C,Linux,詳細web伺服器原理

系統:fedora core 5編譯器:g++實現功能:通過http協議,用瀏覽器檢視伺服器上的html,htm,jpg,jpeg,gif,png,css檔案 ,或者說檢視帶有jpg,jpeg,gif等檔案的網頁,即是web~把程式碼複製下來到linux裡,照著後面的方法編譯

32-執行--概述+Thread類+執行的建立方式繼承Thread類+實現Runnable介面+Runnable介面+執行的名稱+執行的狀態

一、概述 1、程序:對應的是一個應用程式在記憶體中的所屬空間。程序是不直接執行的,它只是在分配該應用程式的記憶體空間 注:如果一個程式在記憶體中開闢了空間,就代表它在執行。不執行要釋放空間 2、執行緒:程序中的一個負責程式執行的控制單元,也叫執行路徑。一個程序中可以有多個執行路徑,稱之為

Windows遍歷全盤所搜檔案,並通過執行顯示搜尋進度C++/C

程式設計軟體,系統 軟體:VS2017 工程屬性:MFC對話方塊(其他工程屬性稍作修改也適用) 系統:windows10家庭版 主要思想 1、利用CFileFind實現查詢檔案 2、利用遞迴實現全盤搜尋 3、多執行緒實現父對話方塊搜尋檔案時,子對話方塊顯示搜尋進度 軟體執行效

執行學習筆記--02物件及變數的併發訪問

  1.學習目標          Synchronized物件監視器為Object時的使用      Synchronized物件監視器為Class時的使用    

OpenCV使用pthread實現執行加速處理影象C++

OpenCV使用pthread實現多執行緒加速處理影象 目錄     POSIX執行緒(POSIX threads),簡稱Pthreads,是執行緒的POSIX標準。Pthread是由POSIX提出的一套通用的執行緒庫,在linux平臺下,它被廣泛的支援,

Delphi執行的OnTerminate屬性附加一個關於臨界區執行同步的例子

  首先看TThread原始碼中關於OnTerminate的程式碼: ? 1 2 3 4 5 public .... property OnTerminate: TNotifyEvent read FOnTerminate write FOnTerminate; ... end;

執行之join()方法---Thread提供的讓一個執行等待另一個執行完成的方法

package Demo1; /** * Created by Petty on 2017/4/9. */ public class Thread_1 extends Thread {

PYTHON——執行:訊號量Semaphore

  訊號量也是一把鎖,用來控制執行緒併發數的。   BoundedSemaphore或Semaphore管理一個內建的計數 器,每當呼叫acquire()時-1,呼叫release()時+1。       計數器不能小於0,當計數器為 0時,acquire()將阻塞執行緒至同

PYTHON——執行:條件變數Condition

  條件變數(Condition)也是一把鎖,除了同步鎖的作用外,還具有線上程間通訊的功能。   有一類執行緒需要滿足條件之後才能夠繼續執行,Python提供了threading.Condition 物件用於條件變數執行緒的支援,它除了能提供RLock()或Lock()的方法外,還提供了 wait()、no

Java建立一個執行的三種方式

步驟一:執行緒概念 首先要理解程序(Processor)和執行緒(Thread)的區別 程序:啟動一個LOL.exe就叫一個程序。 接著又啟動一個DOTA.exe,這叫兩個程序。 執行緒:執行緒是在程序內部同時做的事情,比如在LOL裡,有很多事情要同時做,比如"蓋倫” 擊殺“

執行縣互動例項,生產消費

生產者消費者問題是一個非常典型性的執行緒互動的問題。 1. 使用棧來存放資料 1.1 把棧改造為支援執行緒安全 1.2 把棧的邊界操作進行處理,當棧裡的資料是0的時候,訪問pull的執行緒就會等待。 當棧裡的資料時200的時候,訪問push的執行緒就會等待 2. 提供一個生產者(Producer)執

作業系統,核心定時器:使用“訊號”建立一種使用者空間機制來測量一個執行程式的執行時間。

      核心是一個作業系統的核心。它負責管理系統的程序、記憶體、裝置驅動程式、檔案和網路系統,決定著系統的效能和穩定性。 定時器是Linux提供的一種定時服務的機制,它在某個特定的時間喚醒某個程序來進行工作。核心在時鐘中斷髮生後檢測各定時器是否到期,在li

執行常用操作方法sleep、yield、join

執行緒的命名與取得 執行緒的命名: 通過構造方法在建立執行緒時設定執行緒名稱 直接繼承Thread類: public Thread (String name); Runable或者Callable介面實現多執行緒: public Thread (Run

Java編寫的一個執行下載器

這裡只是演示這個下載器如何使用以及介面 1.可以百度TIM:找到下載介面,複製下載地址: 2.貼上到下載器介面如下圖:(注意儲存地址一定是存在的) 3.點選開始下載:(如果想要暫停或者繼續可以先選中下載行,點選暫停或者繼續)

C++執行中的future期望

Providers std::promise 和std::future配合使用,給std::future傳遞期望值,下面是最簡單的一個用法: #include <iostream> #include <functional> #include <

Java執行面試題整理BATJ都愛問

今天給大家總結一下,面試中出鏡率很高的幾個多執行緒面試題,希望對大家學習和麵試都能有所幫助。備註:文中的程式碼自己實現一遍的話效果會更佳哦! 一、面試中關於 synchronized 關鍵字的 5 連擊 1.1 說一說自己對於 synchronized 關鍵字的瞭解 synchroniz

[Xcode10 實際操作]八、網路與執行-(17)使用網址會話物件URLSession向遠端伺服器上傳圖片

本文將演示如何通過網址會話物件URLSession向遠端伺服器上傳圖片。 網址會話物件URLSession具有在後臺上傳和下載、暫停和恢復網路操作、豐富的代理模式等優點。 在專案導航區,開啟檢視控制器的程式碼檔案【ViewController.swift】 1 import UIKit 2

一個執行面試問題

問題:有三個執行緒,其中兩個每秒執行j+1,第三個執行緒每秒執行j-2。實現程式碼: package arithmetic; public class ThreadTest { private static int j = 0; public sta

執行斷點續傳

一、 學習內容 1、 多檔案下載列表的顯示 2、 啟動多個執行緒分段下載 二、 多執行緒下載原理簡介 假設要分3個執行緒下載一個100位元組的檔案:從頭到尾,每個執行緒下載一段 三、 學習點 1、 Adapter的getCount() 2、 A

執行斷點續傳

一、 學習內容 1、 基本UI定義 2、 資料庫的操作 3、 Service的啟動 4、 Activity給service傳遞引數 5、 使用廣播回傳資料到Activity 6、 執行緒和Handler 7、 網路操作:檔案的寫入,網路往本地磁碟寫入 二、 網路下