最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C 語言實現(xiàn)一個簡單的 web 服務器的原理解析

 更新時間:2020年11月04日 10:44:11   作者:C語言與CPP編程  
這篇文章主要介紹了C 語言實現(xiàn)一個簡單的 web 服務器的原理解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下

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

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

一、了解 Socket 及 web 服務工作原理

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

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

                                                      

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

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

2.1 WSAStartup初始化

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

#include <WinSock2.h>

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

int WSAStartup(
 WORD  wVersionRequired,
 LPWSADATA lpWSAData
);

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

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

首先聲明一個 WSADATA 結構體  :

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ù)使用默認值。

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

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

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

2.3 綁定服務器

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

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

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

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

struct sockaddr_in server_addr;

隨后配置地址家族為AF_INET對應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 初始化內存,完整代碼如下:

//配置服務器 
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ù)進行綁定且進行判斷是否綁定成功:

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

2.4 listen進行監(jiān)聽

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

int listen(
 int sockfd, 
 int backlog
)

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

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

此階段完整代碼如下:

#include <WinSock2.h>
#include<stdio.h> 
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;
 }
 //配置服務器 
 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");
}

運行代碼可得知代碼無錯誤,并且輸出 listening:

2.5 獲取請求

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

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

參數(shù) sockfd 為指定的套接字;addr 為指向 struct sockaddr 的指針,一般為客戶端地址;addrlen 一般設置為設置為 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 為緩存大?。籪lags 一般設置為0即可:

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

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

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);
 } 

接下來我們對請求頭進行對應的操作。

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ù)中標記當前目錄下:

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

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

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

接著我們標記一些頭元素:

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

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

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 < *len) 
 {
 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 響應與文件類型:

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

隨后獲得請求文件的描述,需要添加頭文件#include <sys/stat.h>使用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,得到當前目錄下 index.html 文件數(shù)據(jù),并且在瀏覽器渲染:

所有代碼如下:

#include <WinSock2.h>
#include<stdio.h> 
#include <sys/stat.h> 
 
int send_(int s, char *buf, int *len) {
 int total;   
 int bytesleft;        
 int n;
 total=0;
 bytesleft=*len;
 while(total < *len) 
 {
 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。

到此這篇關于C 語言實現(xiàn)一個簡單的 web 服務器的原理解析的文章就介紹到這了,更多相關C 語言實現(xiàn)web 服務器內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • C語言goto的應用舉例以及詳解

    C語言goto的應用舉例以及詳解

    goto的用法就是改變程序執(zhí)行的順序,從某個地方跳轉到你標志的地方,下面這篇文章主要給大家介紹了關于C語言goto的應用舉例及詳解的相關資料,需要的朋友可以參考下
    2022-11-11
  • 淺談分詞器Tokenizer

    淺談分詞器Tokenizer

    分詞器的工作就是分解文本流成詞(tokens).在這個文本中,每一個token都是這些字符的一個子序列。一個分析器(analyzer)必須知道它所配置的字段,但是tokenizer不需要,分詞器(tokenizer)從一個字符流(reader)讀取數(shù)據(jù),生成一個Token對象(TokenStream)的序列
    2021-06-06
  • C語言實現(xiàn)經(jīng)典windows游戲掃雷的示例代碼

    C語言實現(xiàn)經(jīng)典windows游戲掃雷的示例代碼

    今天我們會用C語言實現(xiàn)一個經(jīng)典的windows小游戲:掃雷。掃雷是一款單機小游戲,每次通關最高難度的關卡都會開心好一陣?,F(xiàn)在學會了C語言,總算可以自己實現(xiàn)掃雷了。話不多說,咱們開始吧
    2022-10-10
  • C++通過類實現(xiàn)控制臺貪吃蛇

    C++通過類實現(xiàn)控制臺貪吃蛇

    這篇文章主要為大家詳細介紹了C++通過類實現(xiàn)控制臺貪吃蛇,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-04-04
  • OpenCV c++滑動條的創(chuàng)建和使用代碼

    OpenCV c++滑動條的創(chuàng)建和使用代碼

    滾動條(Trackbar)在OpenCV中是非常方便的交互工具,它依附于特定的窗口而存在,下面這篇文章主要給大家介紹了關于OpenCV?c++滑動條的創(chuàng)建和使用的相關資料,需要的朋友可以參考下
    2023-06-06
  • C語言繪制曲線圖的示例代碼

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

    這篇文章主要介為大家詳細紹了如何使用C語言繪制統(tǒng)計圖中的曲線圖,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2024-02-02
  • 淺析結束程序函數(shù)exit, _exit,atexit的區(qū)別

    淺析結束程序函數(shù)exit, _exit,atexit的區(qū)別

    在一個程序中最多可以用atexit()注冊32個處理函數(shù),這些處理函數(shù)的調用順序與其注冊的順序相反,也即最先注冊的最后調用,最后注冊的最先調用
    2013-09-09
  • C語言堆實現(xiàn)建堆算法和堆排序

    C語言堆實現(xiàn)建堆算法和堆排序

    本文主要介紹了C語言堆實現(xiàn)建堆算法和堆排序,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2024-09-09
  • C++根據(jù)傳入的函數(shù)指針來解析需要的參數(shù)(推薦)

    C++根據(jù)傳入的函數(shù)指針來解析需要的參數(shù)(推薦)

    C++可以根據(jù)傳入的函數(shù)指針,獲取自己需要的參數(shù)類型,然后根據(jù)參數(shù)源中獲取需要的參數(shù),具體實現(xiàn)方式大家參考下本文
    2018-05-05
  • 使用OpenCV檢測圖像中的矩形

    使用OpenCV檢測圖像中的矩形

    這篇文章主要為大家詳細介紹了使用OpenCV檢測圖像中的矩形,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-07-07

最新評論

航空| 沙坪坝区| 讷河市| 观塘区| 泰顺县| 岳池县| 介休市| 运城市| 台南县| 宁波市| 栖霞市| 资源县| 余江县| 柳河县| 宁远县| 如东县| 都安| 扬州市| 芦溪县| 沧源| 开封县| 福鼎市| 新密市| 陆河县| 恩施市| 新营市| 南靖县| 田林县| 茶陵县| 高雄县| 丰镇市| 长沙县| 长汀县| 丹东市| 许昌市| 施甸县| 布拖县| 阳山县| 高陵县| 仁怀市| 固始县|