欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

利用C語言實(shí)現(xiàn)http服務(wù)器(Linux)

 更新時間:2022年07月26日 14:26:03   作者:baixingyubxy  
本文將利用C語言實(shí)現(xiàn)一個輕量級的http服務(wù)器,使用Reactor模式,即主線程只負(fù)責(zé)監(jiān)聽文件描述符上是否有事件發(fā)生,有的話立即將該事件通知工作線程,感興趣的可以了解一下

這篇文章是我的生產(chǎn)實(shí)習(xí)報告,在Linux操作系統(tǒng)上實(shí)現(xiàn)的一個簡單的HTTP服務(wù)器,也算是一個小項目。請大家多多指教。

一、實(shí)習(xí)目的

本次實(shí)習(xí)緊緊圍繞Linux操作系統(tǒng)基礎(chǔ)知識展開,主要學(xué)習(xí)了Linux系統(tǒng)的常用命令、gcc編譯鏈接過程、多線程通信和同步技術(shù)、socket網(wǎng)絡(luò)通信、HTTP服務(wù)器等內(nèi)容。與此同時,在老師的帶領(lǐng)下進(jìn)行實(shí)操訓(xùn)練,例如:編寫Makefile文件管理工程、實(shí)現(xiàn)靜態(tài)庫和動態(tài)庫、模仿系統(tǒng)bash實(shí)現(xiàn)自己的命令解釋器、編寫多線程程序并實(shí)現(xiàn)同步、實(shí)現(xiàn)TCP/UDP服務(wù)器端和客戶端進(jìn)行通信等。

最后通過獨(dú)立完成一個基于Linux平臺C語言編寫的http服務(wù)器,鞏固課程學(xué)到的Linux平臺上的編程規(guī)范、技術(shù)和技巧,增強(qiáng)對于Linux操作系統(tǒng)的熟練度,培養(yǎng)我們編寫較大型程序的能力,培養(yǎng)底層軟件開發(fā)的能力,并為將來從事Linux平臺開發(fā)、嵌入式開發(fā)等相對高端的軟件開發(fā)工作打下基礎(chǔ)。

本次實(shí)習(xí)具體目的如下:

(1)掌握并熟練使用Linux操作系統(tǒng)常用命令;

(2)熟練使用vim、gcc編譯器、gdb等工具在Linux平臺上進(jìn)行程序的編寫、編譯以及調(diào)試;

(3)使用C語言編寫輕量級http服務(wù)器實(shí)現(xiàn)發(fā)布靜態(tài)頁面功能;

(4)采用線程池和I/O復(fù)用方法實(shí)現(xiàn)同時處理多個客戶端請求。

二、實(shí)習(xí)項目及內(nèi)容

2.1開發(fā)平臺

本項目是基于Linux系統(tǒng)C語言實(shí)現(xiàn)的http服務(wù)器,開發(fā)環(huán)境如下:

開發(fā)平臺:騰訊云服務(wù)器

操作系統(tǒng):Ubuntu Server 20.04 LTS 64bit

CPU:2核

內(nèi)存:4GB

系統(tǒng)盤:60GB SSD云硬盤

2.2項目功能

本項目設(shè)計的http服務(wù)器是一個輕量級的服務(wù)器,使用Reactor模式,即主線程只負(fù)責(zé)監(jiān)聽文件描述符上是否有事件發(fā)生,有的話立即將該事件通知工作線程。除此之外,主線程不做其他實(shí)質(zhì)性的工作。讀寫數(shù)據(jù),接受新的連接,以及處理客戶請求均在工作線程中完成。

本項目的基本功能如下:

(1)能接收客戶端的GET請求;

(2)能夠解析客戶端的請求報文,根據(jù)客戶端要求找到相應(yīng)的資源;

(3)能夠回復(fù)http應(yīng)答報文;

(4)能夠讀取服務(wù)器中存儲的文件,并返回給請求客戶端,實(shí)現(xiàn)對外發(fā)布靜態(tài)資源;

(5)使用I/O復(fù)用來提高處理請求的并發(fā)度;

(6)服務(wù)器端支持錯誤處理,如要訪問的資源不存在時回復(fù)404錯誤等。

2.3技能儲備

為了完成本項目,實(shí)現(xiàn)本項目的具體功能,需要具有一定的技能儲備作為技術(shù)支撐。

首先應(yīng)該掌握Linux操作系統(tǒng)的常用命令,C語言基礎(chǔ),熟練使用vim、gcc編譯器、gdb等工具,Linux平臺上進(jìn)行程序的編寫、編譯以及調(diào)試能力,socket網(wǎng)絡(luò)通信的編程能力,I/O復(fù)用理論知識以及編程能力,多線程編程能力,以及一定的HTML語言能力。

三、項目設(shè)計

3.1設(shè)計概述

本項目是基于Linux操作系統(tǒng),使用C語言實(shí)現(xiàn)的輕量級http服務(wù)器。使用socket網(wǎng)絡(luò)編程技術(shù)實(shí)現(xiàn)服務(wù)器端和客戶端之間的通信。同時,為了提高本服務(wù)器的并發(fā)處理性能,本次http服務(wù)器設(shè)計使用Reactor模式。通過I/O復(fù)用和線程池相結(jié)合,實(shí)現(xiàn)同時響應(yīng)多個客戶端的請求,保證http服務(wù)器的并發(fā)性。

3.2 Reactor模式

Reactor模式是指主線程只負(fù)責(zé)監(jiān)聽文件描述符上是否有事件發(fā)生,有的話立即將該事件通知工作線程。除此之外,主線程不做其他實(shí)質(zhì)性的工作。讀寫數(shù)據(jù),接受新的連接,以及處理客戶請求均在工作線程中完成。

工作流程如下:

(1)主線程往epoll內(nèi)核事件表中注冊socket上的讀就緒事件。

(2)主線程調(diào)用epoll_wait等待socket上有數(shù)據(jù)可讀。

(3)當(dāng)socket上有數(shù)據(jù)可讀時,epoll_wait 通知主線程。主線程則將socket可讀事件放入消息隊列。

(4)一旦放入消息隊列便創(chuàng)建相應(yīng)的線程即工作線程,在線程函數(shù)中處理客戶端信息,然后往epoll內(nèi)核事件表中注冊該socket上的寫就緒事件。

(5)主線程調(diào)用epoll_ wait 等待socket可寫。

(6)當(dāng)socket可寫時,epoll _wait 通知主線程。主線程將socket可寫事件放入消息隊列。

(7)創(chuàng)建工作線程,往socket上寫入服務(wù)器處理客戶請求的結(jié)果。

3.3 socket網(wǎng)絡(luò)編程

本項目通過socket網(wǎng)絡(luò)編程技術(shù)實(shí)現(xiàn)http服務(wù)器端和客戶端實(shí)現(xiàn)通信。并且采用的是TCP協(xié)議。

TCP 提供的是面向連接的、可靠的、字節(jié)流服務(wù)。TCP 的服務(wù)器端和客戶端編程流程如下圖:

3.4 http服務(wù)器應(yīng)答報文設(shè)計

如果客戶端請求響應(yīng)成功,則想客戶端發(fā)送成功應(yīng)答報文。如下表所示:

表3-1 請求成功的應(yīng)答報文

如果客戶端請求響應(yīng)失敗,例如服務(wù)器端沒有客戶端所請求的資源,則回復(fù)失敗報文。如下表所示:

表3-2 請求失敗應(yīng)答報文

四、代碼實(shí)現(xiàn)及運(yùn)行結(jié)果

4.1主要功能實(shí)現(xiàn)

4.1.1 主函數(shù)

int main()
{
    signal(SIGPIPE,sig_fun);
    sockfd = socket_init();//調(diào)用創(chuàng)建套接字函數(shù)
    if ( sockfd == -1 )
    {
        exit(0);
    }
    msgid = msgget((key_t)1234,IPC_CREAT|0600);//創(chuàng)建消息隊列
    if ( msgid == -1 )
    {
        exit(0);
    }
    pthread_t id[4];
    for( int i = 0; i < 4; i++ )    //循環(huán)創(chuàng)建線程池
    {
        pthread_create(&id[i],NULL,loop_thread,NULL);
    }    
    epfd = epoll_create(MAXFD);//創(chuàng)建內(nèi)核事件表
    if ( epfd == -1 )
    {
        printf("create epoll err\n");
        exit(0);
}
epoll_add(epfd,sockfd);//調(diào)用封裝的函數(shù)添加描述符和事件
  struct epoll_event evs[MAXFD];
    while( 1 )
    {
        int n = epoll_wait(epfd,evs,MAXFD,-1);//獲取就緒描述符
        if( n == -1 )
        {
            continue;
        }
else    
        {
            struct mess m;
            m.type = 1;
            for(int i = 0; i < n; i++ )
            {
                m.c = evs[i].data.fd;
                if ( evs[i].events & EPOLLIN )
                {
                    msgsnd(msgid,&m,sizeof(int),0); //向消息隊列發(fā)送消息
                }
            }
        }
    }
}

主函數(shù)中主要調(diào)用各個封裝好的方法函數(shù),首先調(diào)用創(chuàng)建套接字函數(shù),創(chuàng)建套接字,然后創(chuàng)建消息隊列。接著創(chuàng)建線程池,子線程同時執(zhí)行l(wèi)oop_thread線程函數(shù),將在 msgrcv處阻塞,等待獲取消息隊列中的消息。主線程調(diào)用epoll_create方法創(chuàng)建內(nèi)核事件表,調(diào)用epoll_add函數(shù)添加描述符和事件。接著使用epoll_wait方法獲取就緒描述符,一旦獲取到就緒描述符便向消息隊列中發(fā)送消息,便可以解除子線程中消息隊列的阻塞,執(zhí)行子線程中的程序,連接客戶端,實(shí)現(xiàn)通信。

4.1.2創(chuàng)建套接字函數(shù)

int socket_init()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if ( sockfd == -1 )
    {
        return -1;
    }
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(80);
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
 if ( res == -1 )
    {
        printf("bind err\n");
        return -1;
    }
    res = listen(sockfd,5);
    if ( res == -1 )
    {
        return -1;
    }
    return sockfd;
}

將初始化創(chuàng)建套接字函數(shù)封裝。

4.1.3線程函數(shù)

 void* loop_thread(void*  arg)
{
    while( 1 )
    {
        struct mess m;
        msgrcv(msgid,&m,sizeof(int),1,0);//從消息隊列中讀取消息
        int c = m.c;
        if ( c == sockfd )
        {
            struct sockaddr_in caddr;
            int len = sizeof(caddr);
            int cli = accept(sockfd,(struct sockaddr*)&caddr,&len);
            if ( cli < 0 )
            {
                continue;
            }
            epoll_add(epfd,cli);
        }
        else
        {
            char buff[1024] = {0};
            int n = recv(c,buff,1023,0);
             if ( n <= 0 )
            {
                epoll_del(epfd,c);//調(diào)用移除描述符函數(shù)
                close(c);
                printf("close\n");
                continue;
            }
            char* filename = get_filename(buff);//調(diào)用資源名獲取函數(shù)
            if ( filename == NULL )
            {
                send_404status(c);//調(diào)用發(fā)送錯誤應(yīng)答報文函數(shù)
                epoll_del(epfd,c);//調(diào)用移除描述符函數(shù)
                close(c);
                continue;
            }
            printf("filename:%s\n",filename);

            if ( send_httpfile(c,filename) == -1 )//調(diào)用發(fā)送正確應(yīng)答報文函數(shù)
            {
                printf("主動關(guān)閉連接\n");
                epoll_del(epfd,c);
                close(c);
                continue;
            }
        }
        epoll_mod(epfd,c);//調(diào)用重置函數(shù)
    }
}

線程將在 msgrcv處阻塞,等待獲取消息隊列中的消息,判斷描述符類型,進(jìn)行accept操作或recv操作。收到客戶端請求數(shù)據(jù)后再調(diào)用get_filename函數(shù)獲取客戶端請求的資源名稱,再判斷發(fā)送錯誤或者正確應(yīng)答報文。

4.1.4封裝epoll函數(shù)

//添加描述符函數(shù)
void epoll_add(int epfd,int fd)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLONESHOT;

    if ( epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) == -1)
    {
        printf("epoll add err\n");
    }
}
//移除描述符函數(shù)
void epoll_del(int epfd, int fd )
{
    if ( epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1 )
    {
        printf("epoll del err\n");
    }
}
//重置描述符函數(shù)
void epoll_mod(int epfd, int fd)
{
    struct epoll_event ev;
    ev.data.fd = fd;
    ev.events = EPOLLIN | EPOLLONESHOT;
    if ( epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev) == -1 )
    {
        printf("epoll mod err\n");
    }
}

4.1.5獲取資源名函數(shù)

char* get_filename(char buff[])
{   char* ptr = NULL;
    char * s = strtok_r(buff," ",&ptr);
    if ( s == NULL )
    {
        printf("請求報文錯誤\n");
        return NULL;
    }
    printf("請求方法:%s\n",s);
    s = strtok_r(NULL," ",&ptr);
    if ( s == NULL )
    {
        printf("請求報文 無資源名字\n");
        return NULL;
    }
    if ( strcmp(s,"/") == 0 )
    {
        return "/index.html";
    }
    return s;
}

通過這個函數(shù)來解析客戶端請求報文,獲取資源名稱。

4.1.6發(fā)送正確應(yīng)答報文函數(shù)

int  send_httpfile(int c, char* filename)
{
    if ( filename == NULL || c < 0 )
    {
        send(c,"err",3,0);
        return -1 ;
    }

    char path[128] = {PATH};
    strcat(path,filename);//  /home/ubuntu/ligong/day12/index.hmtl
    int fd = open(path,O_RDONLY);
    if ( fd == -1 )
    {
        //send(c,"404",3,0);
        send_404status(c);
        return -1;
    }

    int size = lseek(fd,0,SEEK_END);
    lseek(fd,0,SEEK_SET);
    char head_buff[512] = {"HTTP/1.1 200 OK\r\n"};
    strcat(head_buff,"Server: myhttp\r\n");
    sprintf(head_buff+strlen(head_buff),"Content-Length: %d\r\n",size);
    strcat(head_buff,"\r\n");//分隔報頭和數(shù)據(jù) 空行
    send(c,head_buff,strlen(head_buff),0);
    printf("send file:\n%s\n",head_buff);

    int num = 0;
    char data[1024] = {0};
    while( ( num = read(fd,data,1024)) > 0 )
    {
        send(c,data,num,0);
    }
    close(fd);

    return 0;
}

如果客戶請求資源可以正常訪問,則調(diào)用該函數(shù)發(fā)送應(yīng)答報文。

4.1.7發(fā)送錯誤應(yīng)答報文函數(shù)

int  send_404status(int c)
{
    int fd = open("err404.html",O_RDONLY);
    if ( fd == -1 )
    {
        send(c,"404",3,0);
        return 0;
    }

    int size = lseek(fd,0,SEEK_END);
    lseek(fd,0,SEEK_SET);
    char head_buff[512] = {"HTTP/1.1 404 Not Found\r\n"};
    strcat(head_buff,"Server: myhttp\r\n");
    sprintf(head_buff+strlen(head_buff),"Content-Length: %d\r\n",size);
    strcat(head_buff,"\r\n");//分隔報頭和數(shù)據(jù) 空行
    send(c,head_buff,strlen(head_buff),0);

    char data[1024] = {0};
    int num = 0;
    while( ( num = read(fd,data,1024)) > 0 )
    {
        send(c,data,num,0);
    }
    close(fd);
    return 0;
}

如果客戶端訪問的在服務(wù)器端資源不存在,則調(diào)用該函數(shù)發(fā)送應(yīng)答報文。

4.1.8 index.htlm

<html>
     <head>
         <meta charset=utf8>
          <title>baixingyu</title>
          </head>
      <body background="R-C.jpg">
             <center>
                 <h2>bxy</h2>
                 </center>
                <a href="test.html">下一頁</a>
                </body>
      </html>

4.1.9 test.html

 <html>
      <head>
         <meta charset=utf8>
         <title>測試</title>
          </head>
         <body>
             <center>
                 <h2>小狗小狗
                 </center>
                   <a href="index.html">返回</a>
              </body>
 </html>

4.1.10 404err.html

 <html>
     <head>
          <meta charset=utf8>
          <title>訪問失敗</title>
          </head>
          <body background="1.jpg">
              <center>
                  <h2>頁面走丟了
                  </center>
              </body>
</html>

4.2測試及運(yùn)行結(jié)果

為了測試http服務(wù)器是否能夠正常運(yùn)行,并且實(shí)現(xiàn)上文提到的功能,分別采用了PC端和移動手機(jī)端進(jìn)行網(wǎng)頁測試。

本次測試用例及預(yù)期結(jié)果如下表所示:

表4-1 測試用例及結(jié)果

首先PC端在瀏覽器地址欄輸入服務(wù)器所在的IP地址進(jìn)行訪問,可以成功獲取到服務(wù)器端的index頁面,如圖所示。

點(diǎn)擊下一頁,跳轉(zhuǎn)到test頁面。如圖所示:

在訪問地址后隨意追加錯誤訪問信息,即訪問客戶端不存在的資源,得到404err頁面。如圖所示:

訪問客戶端存在的資源,讀取到的相應(yīng)的資源。例如輸入1.116.157.150\3.jpg或2.jpg。得到圖片內(nèi)容,如圖所示:

移動手機(jī)端測試同理,同樣可以得到相應(yīng)的結(jié)果如下圖所示:

最后,為測試http服務(wù)器端的并發(fā)性,同時使用多個客戶端進(jìn)行連接,同時訪問服務(wù)器端的資源,均可正常運(yùn)行。服務(wù)器端打印的部分請求信息如下圖:

通過測試結(jié)果顯示,本http服務(wù)器實(shí)現(xiàn)了對外發(fā)布的靜態(tài)資源的功能,并且對于錯誤的訪問信息可以進(jìn)行處理回復(fù)。并且,通過多用戶同時訪問測試結(jié)果顯示,該服務(wù)器具有較好的并發(fā)性,能夠滿足一定客戶端同時請求資源的需求。

到此這篇關(guān)于利用C語言實(shí)現(xiàn)http服務(wù)器(Linux)的文章就介紹到這了,更多相關(guān)C語言 http服務(wù)器內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C++重載輸入和輸出運(yùn)算符詳解

    C++重載輸入和輸出運(yùn)算符詳解

    在C++中,標(biāo)準(zhǔn)庫本身已經(jīng)對左移運(yùn)算符<<和右移運(yùn)算符>>分別進(jìn)行了重載,使其能夠用于不同數(shù)據(jù)的輸入輸出,本節(jié)以前面的?complex?類為例來演示輸入輸出運(yùn)算符的重載,需要的朋友可以參考下
    2023-09-09
  • Qt中connect()函數(shù)及用法詳解

    Qt中connect()函數(shù)及用法詳解

    connect() 函數(shù)就是Qt 框架中用于將信號(SIGNAL)和槽(SLOT)關(guān)聯(lián)起來的核心函數(shù),本文給大家介紹Qt中connect()函數(shù),感興趣的朋友跟隨小編一起看看吧
    2024-07-07
  • 用C語言實(shí)現(xiàn)簡單掃雷小游戲

    用C語言實(shí)現(xiàn)簡單掃雷小游戲

    這篇文章主要為大家詳細(xì)介紹了用C語言實(shí)現(xiàn)簡單掃雷小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • C++中Boost的智能指針weak_ptr

    C++中Boost的智能指針weak_ptr

    這篇文章介紹了C++中Boost的智能指針weak_ptr,文中通過示例代碼介紹的非常詳細(xì)。對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-07-07
  • C語言繪制曲線圖的示例代碼

    C語言繪制曲線圖的示例代碼

    這篇文章主要介為大家詳細(xì)紹了如何使用C語言繪制統(tǒng)計圖中的曲線圖,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-02-02
  • C++深入分析講解鏈表

    C++深入分析講解鏈表

    當(dāng)我們在寫一段代碼時,如果要頻繁的在一塊區(qū)域進(jìn)行插入或者刪除操作時,會發(fā)現(xiàn)用數(shù)組實(shí)現(xiàn)會比較復(fù)雜,這時候我們就要用另一種數(shù)據(jù)結(jié)構(gòu),鏈表來實(shí)現(xiàn)
    2022-06-06
  • 基于C++實(shí)現(xiàn)酒店管理系統(tǒng)

    基于C++實(shí)現(xiàn)酒店管理系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了基于C++實(shí)現(xiàn)酒店管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • C++98/11/17表達(dá)式類別(小結(jié))

    C++98/11/17表達(dá)式類別(小結(jié))

    這篇文章主要介紹了C++98/11/17表達(dá)式類別,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-05-05
  • C語言實(shí)現(xiàn)的bitmap位圖代碼分享

    C語言實(shí)現(xiàn)的bitmap位圖代碼分享

    這篇文章主要介紹了C語言實(shí)現(xiàn)的bitmap位圖代碼分享,位圖(bitmap)是一種非常常用的結(jié)構(gòu),在索引、數(shù)據(jù)壓縮等方面有廣泛應(yīng)用,需要的朋友可以參考下
    2014-08-08
  • C語言實(shí)現(xiàn)校園導(dǎo)游系統(tǒng)

    C語言實(shí)現(xiàn)校園導(dǎo)游系統(tǒng)

    這篇文章主要為大家詳細(xì)介紹了C語言實(shí)現(xiàn)校園導(dǎo)游系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03

最新評論