當(dāng)前位置:首頁 > 公眾號(hào)精選 > C語言與CPP編程
[導(dǎo)讀]說到web服務(wù)器,想必大多數(shù)人首先想到的協(xié)議是http,那么http之下則是 tcp。本文將通過tcp來實(shí)現(xiàn)一個(gè)簡單的web服務(wù)器。本篇文章將著重講解如何實(shí)現(xiàn),對(duì)于http與tcp的概念本篇將不過多講解。

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

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

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

既然是基于 tcp 實(shí)現(xiàn) web 服務(wù)器,很多學(xué)習(xí) C 語言的小伙伴可能會(huì)很快的想到套接字 socket。socket 是一個(gè)較為抽象的通信進(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)閉。

了解完了一個(gè) socket 的基本步驟后我們了解一下一個(gè)基本 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ù)器的這一步流程是在啟動(dòng)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)中對(duì) socket 的創(chuàng)建步驟已有說明,首先需要完成 socket 的初始化操作,使用函數(shù) WSAStartup,該函數(shù)的原型為:

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

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

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

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

WSADATA?wsaData;

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

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

WSAStartup 若初始化失敗則會(huì)返回非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 會(huì)根據(jù)前 2 個(gè)參數(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 后需要對(duì)服務(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)建一個(gè) sockaddr_in 結(jié)構(gòu)體變量用于配置服務(wù)器信息:

struct?sockaddr_in?server_addr;

隨后配置地址家族為AF_INET對(duì)應(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 地址若不確定可以手動(dòng)輸入,最后使用神器 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)聽

綁定成功后開始對(duì)端口進(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)行代碼可得知代碼無錯(cuò)誤,并且輸出 listening:

在這里插入圖片描述

2.5 獲取請求

監(jiān)聽完成后開始獲取請求。受限需要使用 accept 對(duì)套接字進(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);
}

此時(shí)我們再到 accpt 和 recv 外層添加一個(gè)循環(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 將會(huì)看到客戶端打印了 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);
?}?

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

2.6 請求處理層編寫

得到請求后開始編寫處理層。繼續(xù)接著代碼往下寫沒有層級(jí),編寫一個(gè)函數(shù)名為 req,該函數(shù)接收請求信息與一個(gè)建立好的連接為參數(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,我們先返回一個(gè) 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);

接著編寫一個(gè)發(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ù)功能并不難在此不再贅述,就是一個(gè)遍歷發(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);
?}?
?
}

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

免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機(jī)構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點(diǎn),本站亦不保證或承諾內(nèi)容真實(shí)性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時(shí)聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫?dú)角獸公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關(guān)鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關(guān)鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認(rèn)證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時(shí)1.5...

關(guān)鍵字: 汽車 人工智能 智能驅(qū)動(dòng) BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務(wù)能7×24不間斷運(yùn)行,同時(shí)企業(yè)卻面臨越來越多業(yè)務(wù)中斷的風(fēng)險(xiǎn),如企業(yè)系統(tǒng)復(fù)雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務(wù)連續(xù)性,提升韌性,成...

關(guān)鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報(bào)道,騰訊和網(wǎng)易近期正在縮減他們對(duì)日本游戲市場的投資。

關(guān)鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關(guān)鍵字: 華為 12nm EDA 半導(dǎo)體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會(huì)上,華為常務(wù)董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關(guān)鍵字: 華為 12nm 手機(jī) 衛(wèi)星通信

要點(diǎn): 有效應(yīng)對(duì)環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實(shí)提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務(wù)引領(lǐng)增長 以科技創(chuàng)新為引領(lǐng),提升企業(yè)核心競爭力 堅(jiān)持高質(zhì)量發(fā)展策略,塑強(qiáng)核心競爭優(yōu)勢...

關(guān)鍵字: 通信 BSP 電信運(yùn)營商 數(shù)字經(jīng)濟(jì)

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺(tái)與中國電影電視技術(shù)學(xué)會(huì)聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會(huì)上宣布正式成立。 活動(dòng)現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關(guān)鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會(huì)上,軟通動(dòng)力信息技術(shù)(集團(tuán))股份有限公司(以下簡稱"軟通動(dòng)力")與長三角投資(上海)有限...

關(guān)鍵字: BSP 信息技術(shù)
關(guān)閉
關(guān)閉