對于php程序員拄轻,對于web服務(wù)器來說再熟悉不過了俺孙,apache志鞍,nginx。娜汁。但是內(nèi)心一直想開發(fā)出一個屬于自己的web服務(wù)器筏养,所以借此機會斧抱,用c開發(fā)出了一款web服務(wù)器。作為1.0版本渐溶,他實現(xiàn)了以下功能
- 完成基礎(chǔ)的tcp連接辉浦,支持基礎(chǔ)的client與其連接
- 使用fork()來支持并發(fā)訪問服務(wù)器
- 簡單的http訪問,支持靜態(tài)頁面訪問
- 支持php動態(tài)頁面訪問
- 需要一定的報錯機制茎辐,如404頁面的建立
好了宪郊,先奉上幾張最后完成的圖片來說說我們需要實現(xiàn)哪些功能
靜態(tài)頁面
動態(tài)界面(php)
404界面
看了上面的截圖展示,是不是要下定決心自己寫出屬于自己的web服務(wù)器拖陆。所以弛槐,開始吧!
首先依啰,先看看 TCP協(xié)議通訊流程(這張圖希望多看幾遍乎串,記下每個流程,每個方法)
TCP通訊流程文字描述是這樣的:
Server端:
1.完成socket(),bind(),listen()這些初始化工作后孔飒,調(diào)用accept()方法阻塞等待(其實就是進入一個死循環(huán)),等待CLient的connect()方法連接
Client端:
2.先調(diào)用socket(),然后調(diào)用connect()想要與Server端進行連接灌闺,這個時候就會進行<b>傳說中的TCP三次握手</b>,也就是在Client 發(fā)起connect()坏瞄,并且Server進入accept()阻塞等待時發(fā)生三次握手
三次握手可以如下圖表示:
image
這里3次握手的詳細過程桂对,大家請自行查閱有關(guān)資料,這里不多做介紹了鸠匀。
Client端:
3.當(dāng)建立與Server端的連接后蕉斜,Client端就可以進行write()方法了,將數(shù)據(jù)傳輸給Server,于此同時宅此,Server端可以通過read()方法讀取數(shù)據(jù)机错,獲得CLient端傳遞的數(shù)據(jù),當(dāng)然Server端也可以通過write()方法將數(shù)據(jù)回寫給Client端,這樣兩端就進行相互的數(shù)據(jù)交互父腕,當(dāng)CLient端覺得交互完成了弱匪,調(diào)用close()方法通知Server端與其斷開連接時,則會進行傳說中的<b>TCP 四次揮手</b>
四次揮手可以如下圖表示:
image
這里四次握手的詳細過程璧亮,大家請自行查閱有關(guān)資料萧诫,這里不多做介紹了。
好了到這里枝嘶,簡單的TCP通訊交互介紹完了帘饶,希望大家能夠真正去了解以上內(nèi)容,然后對接下來的編碼有很大幫助!
千里之行群扶,始于足下
- 開始第一步及刻,實現(xiàn)client以及server的交互(我不希望全是長篇的代碼,這樣看的頭疼竞阐,我會一點一點剖析代碼缴饭,一步一步介紹每個功能點)
Server端
- 根據(jù)socket相關(guān)編程,首先在main函數(shù)中調(diào)起socket(),bind(),listen()這幾個方法
int main(int argc, char * argv[]) {
struct sockaddr_in servaddr,cliaddr;
socklen_t cliaddr_len;
int listenfd,connfd;
char buf[MAXLINE],first_line[MAXLINE],left_line[MAXLINE],method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char str[INET_ADDRSTRLEN];
char filename[MAXLINE];
long n;
int i,pid;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//初始化myaddr參數(shù)
bzero(&servaddr, sizeof(servaddr)); //結(jié)構(gòu)體清零
//對servaddr 結(jié)構(gòu)體進行賦值
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, BACKLOGSIZE);
}
當(dāng)然以上的程序需要加上頭文件
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//read方法需要的頭文件
#include <unistd.h>
//socket方法需要的頭文件
#include <sys/socket.h>
#include <sys/types.h>
//htonl 方法需要的頭文件
#include <netinet/in.h>
//inet_ntop方法需要的頭文件
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
當(dāng)然中間有些結(jié)構(gòu)體不太了解骆莹,比如servaddr這個茴扁。不了解也不影響閱讀,先讓程序跑起來對吧汪疮。這些等以后深入了自然能夠清楚明白
接下來就要Server端就要進行accept()方法進行阻塞等待Client連接了
我們使用
while(1) {
accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
...
}
這樣的死循環(huán)進行阻塞等待
Client端
對比Server端,Client端會顯得很簡單毁习,同樣的進行socket(),然后進行connect()智嚷,如果成功的話就可以進行write()發(fā)送消息以及read()方法接收消息了,代碼應(yīng)該像這樣:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//read方法需要的頭文件
#include <unistd.h>
//socket方法需要的頭文件
#include <sys/socket.h>
#include <sys/types.h>
//htonl 方法需要的頭文件
#include <netinet/in.h>
//inet_ntop方法需要的頭文件
#include <arpa/inet.h>
#define MAXLINE 100
#define CLI_PORT 8000
//webserver 主程序
int main(int argc, const char * argv[]) {
struct sockaddr_in servaddr;
char buf[MAXLINE];
int clientfd;
long n;
//client socket連接
clientfd = socket(AF_INET, SOCK_STREAM, 0);
char *str = "hello world";
//sockaddr_in結(jié)構(gòu)體初始化
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(CLI_PORT);
//connect()方法
connect(clientfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
//write()方法是client 向 server 寫數(shù)據(jù)
write(clientfd, buf, strlen(buf));
printf("write to server : %s\n",buf);
//read()方法是從server接收數(shù)據(jù)
n = read(clientfd, buf, strlen(buf));
if(n == 0) {
printf("the other side has been close\n");
}else {
printf("Response from server: %s\n",buf);
write(STDOUT_FILENO, buf, n);
printf("\n");
}
close(clientfd);
}
很簡答,Client像個線式程序一樣寫下來纺且,這時候可以去完成Server端剩下的代碼了
//死循環(huán)中進行accept()
while (1) {
cliaddr_len = sizeof(cliaddr);
//accept()函數(shù)返回一個connfd描述符
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
n = Read(connfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
printf("received from %s at PORT %d,message is %s\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port),buf);
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(connfd, buf, n);
Close(connfd);
exit(0);
}
這里客戶端還可以進行將CLient傳來的數(shù)據(jù)大寫轉(zhuǎn)化盏道,會傳給Client,這時候第一步代碼寫完了,趕緊運行下試試吧
Server端啟動
Client端響應(yīng)
第一階段完成载碌,撒花猜嘱,接下來將在第二篇博客中繼續(xù)完善這個web服務(wù)器