<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          C 語言實(shí)現(xiàn)一個簡單的 web 服務(wù)器

          共 5675字,需瀏覽 12分鐘

           ·

          2022-02-16 02:04

          說到 web 服務(wù)器想必大多數(shù)人首先想到的協(xié)議是 http,那么 http 之下則是 tcp,本篇文章將通過 tcp 來實(shí)現(xiàn)一個簡單的 web 服務(wù)器。

          本篇文章將著重講解如何實(shí)現(xiàn),對于 http 與 tcp 的概念本篇將不過多講解。

          一、了解 Socket 及 web 服務(wù)工作原理

          既然是基于 tcp 實(shí)現(xiàn) web 服務(wù)器,很多學(xué)習(xí) C 語言的小伙伴可能會很快的想到套接字 socket。socket 是一個較為抽象的通信進(jìn)程,或者說是主機(jī)與主機(jī)進(jìn)行信息交互的一種抽象。socket 可以將數(shù)據(jù)流送入網(wǎng)絡(luò)中,也可以接收數(shù)據(jù)流。

          socket 的信息交互與本地文件信息的讀取從表面特征上看類似,但其中所存在的編寫復(fù)雜度是本地 IO 不能比擬的,但卻有相似點(diǎn)。在 win 下 socket 的交互交互步驟為:WSAStartup 進(jìn)行初始化--> socket 創(chuàng)建套接字--> bind 綁定--> listen 監(jiān)聽--> connect 連接--> accept 接收請求--> send/recv 發(fā)送或接收數(shù)據(jù)--> closesocket 關(guān)閉 socket--> WSACleanup 最終關(guān)閉。

          了解完了一個 socket 的基本步驟后我們了解一下一個基本 web 請求的用戶常規(guī)操作,操作分為:打開瀏覽器-->輸入資源地址 ip 地址-->得到資源。當(dāng)目標(biāo)服務(wù)器接收到該操作產(chǎn)生掉請求后,我們可以把服務(wù)器的響應(yīng)流程步驟看為:獲得 request 請求-->得到請求關(guān)鍵數(shù)據(jù)-->獲取關(guān)鍵數(shù)據(jù)-->發(fā)送關(guān)鍵數(shù)據(jù)。服務(wù)器的這一步流程是在啟動socket 進(jìn)行監(jiān)聽后才能響應(yīng)。通過監(jiān)聽得知接收到請求,使用 recv 接收請求數(shù)據(jù),從而根據(jù)該參數(shù)得到進(jìn)行資源獲取,最后通過 send 將數(shù)據(jù)進(jìn)行返回。

          二、創(chuàng)建sokect完成監(jiān)聽

          2.1 WSAStartup初始化

          首先在c語言頭文件中引入依賴 WinSock2.h:

          #include?

          在第一點(diǎn)中對 socket 的創(chuàng)建步驟已有說明,首先需要完成 socket 的初始化操作,使用函數(shù) WSAStartup,該函數(shù)的原型為:

          int?WSAStartup(
          ??WORD??????wVersionRequired,
          ??LPWSADATA?lpWSAData
          )
          ;

          該函數(shù)的參數(shù) wVersionRequired 表示 WinSock2 的版本號;lpWSAData 參數(shù)為指向 WSADATA 的指針,WSADATA 結(jié)構(gòu)用于 WSAStartup 初始化后返回的信息。

          wVersionRequired 可以使用 MAKEWORD 生成,在這里可以使用版本 1.1 或版本2.2,1.1 只支持 TCP/IP,版本 2.1 則會有更多的支持,在此我們選擇版本 1.1。

          首先聲明一個 WSADATA 結(jié)構(gòu)體 ?:

          WSADATA?wsaData;

          隨后傳參至初始化函數(shù) WSAStartup 完成初始化:

          WSAStartup(MAKEWORD(1,?1),?&wsaData)

          WSAStartup 若初始化失敗則會返回非0值:

          if?(WSAStartup(MAKEWORD(1,?1),?&wsaData)?!=?0)?
          {
          ?exit(1);
          }

          2.2 創(chuàng)建socket 套接字

          初始化完畢后開始創(chuàng)建套接字,套接字創(chuàng)建使用函數(shù),函數(shù)原型為:

          SOCKET?WSAAPI?socket(
          ??int?af,
          ??int?type,
          ??int?protocol
          )
          ;

          在函數(shù)原型中,af 表示 IP 地址類型,使用 PF_INET 表示 IPV4,type 表示使用哪種通信類型,例如 SOCK_STREAM 表示 TCP,protocol 表示傳輸協(xié)議,使用 0 會根據(jù)前 2 個參數(shù)使用默認(rèn)值。

          int?skt?=?socket(PF_INET,?SOCK_STREAM,?0);

          創(chuàng)建完 socket 后,若為 -1 表示創(chuàng)建失敗,進(jìn)行判斷如下:

          if?(skt?==?-1)?
          {?????????
          ?return?-1;
          }

          2.3 綁定服務(wù)器

          創(chuàng)建完 socket 后需要對服務(wù)器進(jìn)行綁定,配置端口信息、IP 地址等。首先查看 bind 函數(shù)需要哪一些參數(shù),函數(shù)原型如下:

          int?bind(
          ??SOCKET?????????socket,
          ??const?sockaddr?*addr,
          ??int????????????addrlen
          )
          ;

          參數(shù) socket 表示綁定的 socket,傳入 socket 即可;addr 為 sockaddr_in 的結(jié)構(gòu)體變量的指針,在 sockaddr_in 結(jié)構(gòu)體變量中配置一些服務(wù)器信息;addrlen 為 addr 的大小值。

          通過 bind 函數(shù)原型得知了我們所需要的數(shù)據(jù),接下來創(chuàng)建一個 sockaddr_in 結(jié)構(gòu)體變量用于配置服務(wù)器信息:

          struct?sockaddr_in?server_addr;

          隨后配置地址家族為AF_INET對應(yīng)TCP/IP:

          server_addr.sin_family?=?AF_INET;

          接著配置端口信息:

          server_addr.sin_port?=?htons(8080);

          再指定 ip 地址:

          server_addr.sin_addr.s_addr?=?inet_addr("127.0.0.1");

          ip 地址若不確定可以手動輸入,最后使用神器 memset 初始化內(nèi)存,完整代碼如下:

          //配置服務(wù)器?
          struct?sockaddr_in?server_addr;
          server_addr.sin_family?=?AF_INET;
          server_addr.sin_port?=?htons(8080);
          server_addr.sin_addr.s_addr?=?inet_addr("127.0.0.1");
          memset(&(server_addr.sin_zero),?'\0',?8);

          隨后使用 bind 函數(shù)進(jìn)行綁定且進(jìn)行判斷是否綁定成功:

          //綁定
          if?(bind(skt,?(struct?sockaddr?*)&server_addr,sizeof(server_addr))?==?-1)?{???????
          ?return?-1;?
          }?

          2.4 listen進(jìn)行監(jiān)聽

          綁定成功后開始對端口進(jìn)行監(jiān)聽。查看 listen 函數(shù)原型:

          int?listen(
          ?int?sockfd,?
          ?int?backlog
          )

          函數(shù)原型中,參數(shù) sockfd 表示監(jiān)聽的套接字,backlog 為設(shè)置內(nèi)核中的某一些處理(此處不進(jìn)行深入講解),直接設(shè)置成 10 即可,最大上限為 128。使用監(jiān)聽并且判斷是否成功代碼為:

          if?(listen(skt,?10)?==?-1?)?{????
          ?return?-1;
          }

          此階段完整代碼如下:

          #include?
          #include?
          int?main(){
          ?//初始化?
          ?WSADATA?wsaData;
          ?if?(WSAStartup(MAKEWORD(1,?1),?&wsaData)?!=?0)?{
          ??exit(1);
          ?}
          ?//socket創(chuàng)建?
          ?int?skt?=?socket(PF_INET,?SOCK_STREAM,?0);
          ?if?(skt?==?-1)?{?????????
          ??return?-1;
          ?}
          ?//配置服務(wù)器?
          ?struct?sockaddr_in?server_addr;
          ?server_addr.sin_family?=?AF_INET;
          ?server_addr.sin_port?=?htons(8080);
          ?server_addr.sin_addr.s_addr?=?inet_addr("127.0.0.1");
          ?memset(&(server_addr.sin_zero),?'\0',?8);
          ?//綁定
          ?if?(bind(skt,?(struct?sockaddr?*)&server_addr,sizeof(server_addr))?==?-1){???????
          ??return?-1;?
          ?}?
          ?//監(jiān)聽?
          ?if?(listen(skt,?10)?==?-1?)?{????
          ??return?-1;
          ?}
          ?
          ?printf("Listening?...?...\n");
          }

          運(yùn)行代碼可得知代碼無錯誤,并且輸出 listening:

          在這里插入圖片描述

          2.5 獲取請求

          監(jiān)聽完成后開始獲取請求。受限需要使用 accept 對套接字進(jìn)行連接,accept 函數(shù)原型如下:

          int?accept(
          ?int?sockfd,
          ?struct?sockaddr?*addr,
          ?socklen_t?*addrlen
          ?)
          ;

          參數(shù) sockfd 為指定的套接字;addr 為指向 struct sockaddr 的指針,一般為客戶端地址;addrlen 一般設(shè)置為設(shè)置為 sizeof(struct ? sockaddr_in) 即可。代碼為:

          struct?sockaddr_in?c_skt;?
          int?s_size=sizeof(struct???sockaddr_in);
          int?access_skt?=?accept(skt,?(struct?sockaddr?*)&c_skt,?&s_size);

          接下來開始接受客戶端的請求,使用recv函數(shù),函數(shù)原型為:

          ssize_t?recv(
          ?int?sockfd,?
          ?void?*buf,?
          ?size_t?len,?
          ?int?flags
          )

          參數(shù) sockfd 為 accept 建立的通信;buf 為緩存,數(shù)據(jù)存放的位置;len 為緩存大小;flags 一般設(shè)置為0即可:

          //獲取數(shù)據(jù)?
          char?buf[1024];
          if?(recv(access_skt,?buf,?1024,?0)?==?-1)?{
          ?exit(1);
          }

          此時我們再到 accpt 和 recv 外層添加一個循環(huán),使之流程可重復(fù):

          while(1){
          ??//建立連接?
          ??printf("Listening?...?...\n");
          ??struct?sockaddr_in?c_skt;?
          ??int?s_size=sizeof(struct???sockaddr_in);
          ??int?access_skt?=?accept(skt,?(struct?sockaddr?*)&c_skt,?&s_size);
          ??
          ??//獲取數(shù)據(jù)?
          ??char?buf[1024];
          ??if?(recv(access_skt,?buf,?1024,?0)?==?-1)?{
          ???exit(1);
          ??}
          ?}?

          并且可以在瀏覽器輸入 127.0.0.1:8080 將會看到客戶端打印了 listening 新建了鏈接:

          我們添加printf語句可查看客戶端請求:

          while(1){
          ??//建立連接?
          ??printf("Listening?...?...\n");
          ??struct?sockaddr_in?c_skt;?
          ??int?s_size=sizeof(struct???sockaddr_in);
          ??int?access_skt?=?accept(skt,?(struct?sockaddr?*)&c_skt,?&s_size);
          ??
          ??//獲取數(shù)據(jù)?
          ??char?buf[1024];
          ??if?(recv(access_skt,?buf,?1024,?0)?==?-1)?{
          ???exit(1);
          ??}
          ??
          ??printf("%s",buf);
          ?}?

          接下來我們對請求頭進(jìn)行對應(yīng)的操作。

          2.6 請求處理層編寫

          得到請求后開始編寫處理層。繼續(xù)接著代碼往下寫沒有層級,編寫一個函數(shù)名為 req,該函數(shù)接收請求信息與一個建立好的連接為參數(shù):

          void?req(char*?buf,?int?access_socket)?
          {
          }

          然后先在 while 循環(huán)中傳遞需要的值:

          req(buf,?access_skt);

          接著開始編寫 req 函數(shù),首先在 req 函數(shù)中標(biāo)記當(dāng)前目錄下:

          char?arguments[BUFSIZ];??
          strcpy(arguments,?"./");

          隨后分離出請求與參數(shù):

          char?command[BUFSIZ];?????
          sscanf(request,?"%s%s",?command,?arguments+2);

          接著我們標(biāo)記一些頭元素:

          char*?extension?=?"text/html";???
          char*?content_type?=?"text/plain";?????
          char*?body_length?=?"Content-Length:?";

          接著獲取請求參數(shù),若獲取 index.html,就獲取當(dāng)前路徑下的該文件:

          FILE*?rfile=?fopen(arguments,?"rb");

          獲取文件后表示請求 ok,我們先返回一個 200 狀態(tài):

          char*?head?=?"HTTP/1.1?200?OK\r\n";????
          int?len;?
          char?ctype[30]?=?"Content-type:text/html\r\n";???
          len?=?strlen(head);

          接著編寫一個發(fā)送函數(shù) send_:

          int?send_(int?s,?char?*buf,?int?*len)?
          {
          ?int?total;??????????
          ?int?bytesleft;????????????????????????????????
          ?int?n;
          ?total=0;
          ?bytesleft=*len;
          ?while(total??{
          ??n?=?send(s,?buf+total,?bytesleft,?0);
          ??if?(n?==?-1)?
          ??{
          ???break;
          ??}
          ??total?+=?n;
          ??bytesleft?-=?n;
          ?}
          ?*len?=?total;??????????
          ?return?n==-1?-1:0;?????????
          }

          send 函數(shù)功能并不難在此不再贅述,就是一個遍歷發(fā)送的邏輯。隨后發(fā)送 http 響應(yīng)與文件類型:

          send_(send_to,?head,?&len);
          len?=?strlen(ctype);
          send_(send_to,?ctype,?&len);

          隨后獲得請求文件的描述,需要添加頭文件#include 使用fstat,且向已連接的通信發(fā)生必要的信息 :

          //獲取文件描述
          struct?stat?statbuf;
          char?read_buf[1024];???????
          char?length_buf[20];
          fstat(fileno(rfile),?&statbuf);
          itoa(?statbuf.st_size,?length_buf,?10?);
          send(client_sock,?body_length,?strlen(body_length),?0);
          send(client_sock,?length_buf,?strlen(length_buf),?0);

          send(client_sock,?"\n",?1,?0);
          send(client_sock,?"\r\n",?2,?0);

          最后發(fā)送數(shù)據(jù):

          //·數(shù)據(jù)發(fā)送
          char?read_buf[1024];?
          len?=?fread(read_buf?,1?,?statbuf.st_size,?rfile);
          if?(send_(client_sock,?read_buf,?&len)?==?-1)?{?
          ?printf("error!");???
          }

          最后訪問地址 http://127.0.0.1:8080/index.html,得到當(dāng)前目錄下 index.html 文件數(shù)據(jù),并且在瀏覽器渲染:

          所有代碼如下:

          #include?
          #include?
          #include??

          int?send_(int?s,?char?*buf,?int?*len)?{
          ?int?total;??????????
          ?int?bytesleft;????????????????????????????????
          ?int?n;
          ?total=0;
          ?bytesleft=*len;
          ?while(total??{
          ??n?=?send(s,?buf+total,?bytesleft,?0);
          ??if?(n?==?-1)?
          ??{
          ???break;
          ??}
          ??total?+=?n;
          ??bytesleft?-=?n;
          ?}
          ?*len?=?total;??????????
          ?return?n==-1?-1:0;?????????
          }

          void?req(char*?request,?int?client_sock)?{???
          ?char?arguments[BUFSIZ];??
          ?strcpy(arguments,?"./");
          ?
          ?char?command[BUFSIZ];?????
          ?sscanf(request,?"%s%s",?command,?arguments+2);
          ?
          ?char*?extension?=?"text/html";???
          ?char*?content_type?=?"text/plain";?????
          ?char*?body_length?=?"Content-Length:?";
          ?
          ?FILE*?rfile=?fopen(arguments,?"rb");
          ?

          ?char*?head?=?"HTTP/1.1?200?OK\r\n";????
          ?int?len;?
          ?char?ctype[30]?=?"Content-type:text/html\r\n";???
          ?len?=?strlen(head);
          ??
          ?send_(client_sock,?head,?&len);
          ?len?=?strlen(ctype);
          ?send_(client_sock,?ctype,?&len);
          ?

          ?struct?stat?statbuf;
          ???????
          ?char?length_buf[20];
          ?fstat(fileno(rfile),?&statbuf);
          ?itoa(?statbuf.st_size,?length_buf,?10?);
          ?send(client_sock,?body_length,?strlen(body_length),?0);
          ?send(client_sock,?length_buf,?strlen(length_buf),?0);

          ?send(client_sock,?"\n",?1,?0);
          ?send(client_sock,?"\r\n",?2,?0);
          ?

          ?char?read_buf[1024];?
          ?len?=?fread(read_buf?,1?,?statbuf.st_size,?rfile);
          ?if?(send_(client_sock,?read_buf,?&len)?==?-1)?{?
          ??printf("error!");???
          ?}
          ?
          ?return;
          }


          int?main(){
          ?WSADATA?wsaData;
          ?if?(WSAStartup(MAKEWORD(1,?1),?&wsaData)?!=?0)?{
          ??exit(1);
          ?}

          ?int?skt?=?socket(PF_INET,?SOCK_STREAM,?0);
          ?if?(skt?==?-1)?{?????????
          ??return?-1;
          ?}

          ?struct?sockaddr_in?server_addr;
          ?server_addr.sin_family?=?AF_INET;
          ?server_addr.sin_port?=?htons(8080);
          ?server_addr.sin_addr.s_addr?=?inet_addr("127.0.0.1");
          ?memset(&(server_addr.sin_zero),?'\0',?8);

          ?if?(bind(skt,?(struct?sockaddr?*)&server_addr,sizeof(server_addr))?==?-1)?{???????
          ??return?-1;?
          ?}?

          ?if?(listen(skt,?10)?==?-1?)?{????
          ??return?-1;
          ?}
          ?
          ?while(1){

          ??printf("Listening?...?...\n");
          ??struct?sockaddr_in?c_skt;?
          ??int?s_size=sizeof(struct???sockaddr_in);
          ??int?access_skt?=?accept(skt,?(struct?sockaddr?*)&c_skt,?&s_size);

          ??char?buf[1024];
          ??if?(recv(access_skt,?buf,?1024,?0)?==?-1)?{
          ???exit(1);
          ??}
          ??
          ??req(buf,?access_skt);
          ?}?
          ?
          }

          小伙伴們可以編寫更加靈活的指定資源類型、錯誤處理等完善這個 demo。

          瀏覽 29
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久精品国产 | 操逼免费网址 | 十八禁91 | 黄色片网站视频 | 激情五月天激情 |