系統:fedora core 5
編譯器:g++
實現功能:通過http協議,用瀏覽器查看服務器上的html,htm,jpg,jpeg,gif,png,css文件 ,或者說查看帶有jpg,jpeg,gif等文件的網頁,即是web~
把代碼復制下來到linux里,照著后面的方法編譯、運行,就可以看到一個簡單的多線程服務器的效果了。
原理:
在瀏覽器中輸入一個網址,回車之后,瀏覽器會向相應主機的相應端口發送一段報文,如果是http協議的(如平??吹降木W頁的傳輸協議),就會發送HTTP請求報文。下面是一個報文的例子:
GET /index.html HTTP/1.1
Host: 127.0.0.1:8848
User-Agent: Mozilla/5.0 (X11; U; Linux i686; zh-CN; rv:1.8.0.1) Gecko/20060313 Fedora/1.5.0.1-9 Firefox/1.5.0.1 pango-text
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: gb2312,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
我們在服務器端把收到的數據打印出來,可以看到瀏覽器發過來的就是這個東西。當然,也可以用ethereal等抓包工具來抓獲這些報文。關于報文里寫的是什么意思,網上有很多資料的,GOOGLE一下就有了。我們只看第一行。
GET表示是要從服務器獲取文件,/index.html是文件的路徑,這個路徑是相對于服務器端程序所在文件夾的路徑。如我的服務器端程序放在/home/mio/program/webserver1707/里面,那這個index.html在服務器上的絕對路徑就是/home/mio/program/webserver1707/index.html。如果報文里是GET /admin/login.html HTTP/1.1的話,那么login.html文件在服務器端的路徑是/home/mio/program/webserver1707/admin/login.html.HTTP/1.1表示的是HTTP協議的版本是1.1.
服務器端程序運行后,一直監聽8848端品(0-1023的端口由IANA統一分配和控制的,不要用,最好選大一些的端口號。我原來用了個1234,用不了,還是選大一點好,可以用5460之類的啊~:) ),當監聽到客戶端發來的請求后,就與客戶端建立鏈接,接收客戶端發過來的請求報文。我們如果把這些報文打出來,就可以看到就是與上面請求報文類似的東西了。
下面我們要根據所接受的到的請求報文(GET /index.html HTTP/1.1)來決定放給客戶端(即瀏覽器)什么東西。這里我們看到瀏覽器要的是index.html這樣一個html文本,我們就在相應路徑(/home/mio/program/webserver1707/index.html)找到這個文件,不過不要急著發給客戶端,我們要先告訴客戶端,發過去的是一個html文件,讓瀏覽器做好相應的準備。怎么讓瀏覽器知道呢?我們還是用報文,這個報文叫響應報文。報文由狀態行、首部行、實體主體三部分組成。狀態行只有一行,它和首部行、首部行的每行之間是沒有空行的,但是首部行與實體主體之間有一個空行,表明從這個空行開始,就是你瀏覽器要的數據了。下面是一個用ethereal抓到的響應報文:
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: text/html; charset=UTF-8
Content-Encoding: gzip
Server: GWS/2.1
Content-Length: 1851
Date: Sat, 14 Oct 2006 11:33:39 GMT
<html><head><meta http-equiv="content-type" content="text/html; charset=UTF-8"><title>Google</title><style><!--
body,td,a,p,.h{font-family:arial,sans-serif}
.h{font-size:20px}
.q{color:#00c}
--></style>
<script>
<!--
function sf(){document.f.q.focus();}
function clk(url,oi,cad,ct,cd,sg){if(document.images){var e = window.encodeURIComponent ? encodeURIComponent : escape;var u="";var oi_param="";var cad_param="";if (url) u="&url="+e(url.replace(/#.*/,"")).replace(/\+/g,"%2B");if (oi) oi_param="&oi="+e(oi);if (cad) cad_param="&cad="+e(cad);new Image().src="/url?sa=T"+oi_param+cad_param+"&ct="+e(ct)+"&cd="+e(cd)+u+"&ei=E8swRYIOkpKwAvzZ8JkB"+sg;}return true;}
// -->
</script>
</head><body bgcolor=#ffffff text=#000000 link=#0000cc vlink=#551a8b alink=#ff0000 onLoad=sf() topmargin=3 marginheight=3><center><div align=right nowrap style="padding-bottom:4px" width=100%><font size=-1><b>manioster@gmail.com</b> | <a href="/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Dzh-CN&sig=__1eXNMn0jGllmJ57x74DzjVvy6Vk=" onmousedown="return clk('/url?sa=p&pref=ig&pval=3&q=http://www.google.com/ig%3Fhl%3Dzh-CN&sig=__1eXNMn0jGllmJ57x74DzjVvy6Vk=','promos','hppphou:zh-cn_all','pro','1','&sig2=zclmOmtQiZPPuTCMWUJMZA')">個性化主頁</a> | <a href="https://www.google.com/accounts/ManageAccount">我的帳戶</a> | <a href="http://www.google.com/accounts/Logout?continue=http://www.google.com/intl/zh-CN/">退出</a></font></div><img src="/intl/zh-CN_ALL/images/logo.gif" width=286 height=110 alt="Google"><br><br>
<form action=/search name=f><script><!--
function qs(el) {if (window.RegExp && window.encodeURIComponent) {var ue=el.href;var qe=encodeURIComponent(document.f.q.value);if(ue.indexOf("q=")!=-1){el.href=ue.replace(new RegExp("q=[^&$]*"),"q="+qe);}else{el.href=ue+"&q="+qe;}}return 1;}
// -->
..........
第一個空行上面的就是“說明”了,下面是html代碼。有了說明,瀏覽器就知道這是什么了,拿到這段數據后,就把這些html標簽解釋成各種各樣的元素,在瀏覽器上有序地顯示出來。瀏覽器還蠻聰明的,當看到<img src=..>標簽,那就會又自己發一個請求報文給服務器,要求得到一個圖像文件,請求報文就像:
GET /image/pp.jpg HTTP/1.1
....
這樣,服務器端就找到這個.jpg圖像,加上"說明"之后發給瀏覽器,瀏覽器收到后就顯示在對應的位置上。遇到包含css、js...的標簽也一樣。
如此重復,一個完整的web就會呈現在我們眼前了。
服務器端代碼:
/*****************************************************************
mymultiwebserver.c
system:redhat linux Fedora Core 5
enviroment:g++
compile command:g++ -g -o mymultiwebserver -lpthread
date:10/15/2006
By Manio
*****************************************************************/
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <unistd.h>
#include <pthread.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
#define PORT 8848
#define BACKLOG 5
#define MAXDATASIZE 1000
#define DEBUG 1
void process_cli(int connectfd, sockaddr_in client);
int sendobj(int connectfd,char* serverfilepath);
int IsDIR(char* fpath);
int fileordirExist(char* fpath);
char* getextname(char*);
int writehead(FILE* cfp, char* extname);
void* start_routine(void* arg);
void msg404(int connectfd);
struct ARG {
int connfd;
sockaddr_in client;
};
main()
{
int listenfd, connectfd;
pthread_t thread; //id of thread
ARG *arg; //pass this var to the thread
struct sockaddr_in server; //server's address info
struct sockaddr_in client; //client's
int sin_size;
//create tcp socket
#ifdef DEBUG
printf("socket.... ");
#endif
if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
printf("bind.... ");
if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1) {
perror("bind error.");
exit(1);
}
printf("listen.... ");
if(listen(listenfd,BACKLOG) == -1) {
perror("listen() error ");
exit(1);
}
sin_size = sizeof(struct sockaddr_in);
while(1)
{
//accept() using main thread
printf("accepting.... ");
if((connectfd = accept(listenfd,
(struct sockaddr *)&client,
(socklen_t*)&sin_size)) == -1) {
printf("accept() error ");
}
arg = new ARG;
arg->connfd = connectfd;
memcpy((void *)&arg->client, &client, sizeof(client));
//invoke start_routine to handle this thread
#ifdef DEBUG
printf("thread_creating....");
#endif
if(pthread_create(&thread, NULL, start_routine, (void*)arg)){
perror("pthread_create() error");
exit(1);
}
}
close(listenfd);
}
//handle the request of the client
void process_cli(int connectfd, sockaddr_in client)
{
int num;
//char recvbuf[MAXDATASIZE], sendbuf[MAXDATASIZE], cli_name[MAXDATASIZE];
char requestline[MAXDATASIZE], filepath[MAXDATASIZE], cmd[MAXDATASIZE],extname[MAXDATASIZE];
int c;
FILE *fp;
FILE *cfp;
fp = fdopen(connectfd,"r");
#ifdef DEBUG
printf("the host is:%s ",inet_ntoa(client.sin_addr) );
#endif
fgets(requestline,MAXDATASIZE,fp);
#ifdef DEBUG
printf(" THE REQUEST IS :%s ",requestline);
#endif
strcpy(filepath,"./");
sscanf(requestline,"%s%s",cmd,filepath+2);
strcpy(extname, getextname(filepath));
#ifdef DEBUG
printf("cmd:%s filepath:%s extname:%s ",cmd,filepath,extname);
printf("string comparing :::::::::::::start::::::::::::::: ");
#endif
if(strcmp(cmd,"GET") == 0){
//the command is get
#ifdef DEBUG
printf("cmd(%s)==GET ",cmd);
#endif
//is this a file or dir or notexist?
if(fileordirExist(filepath)){
//is a file or dir or none
//is this a dir
if(IsDIR(filepath)){
//is a dir
#ifdef DEBUG
printf("%s is a DIR ",filepath);
#endif
if( fileordirExist( strcat(filepath,"index.htm") )){
sendobj(connectfd,"index.htm");
}else if(fileordirExist(strcat(filepath,"index.html"))){
sendobj(connectfd,"index.htm");
}else{
msg404(connectfd);
}
}else{
//is a file
#ifdef DEBUG
printf("%s is a file",filepath);
#endif
sendobj(connectfd,filepath);
}
}else{
#ifdef DEBUG
printf("404 ");
#endif
msg404(connectfd);
}
}else{
#ifdef DEBUG
printf("cmd(%s)!=GET ",cmd);
#endif
}
#ifdef DEBUG
printf(":::::::::::::end::::::::::::::: ");
#endif
close(connectfd);
}
//send the 404 error message to the client
void msg404(int connectfd)
{
char* msg;
msg = "HTTP/1.0 404 Not Found Content-Type: text/plain 404 not found by Manio";
send(connectfd,msg,strlen(msg),0);
}
//is the filepath a file or directory
int fileordirExist(char* fpath)
{
struct stat filestat;
return ( stat(fpath,&filestat) != -1);
}
// is the filepath a directory
int IsDIR(char* fpath)
{
#ifdef DEBUG
printf("IN IsDIR ");
#endif
struct stat filestat;
return ( stat(fpath,&filestat) != -1 && S_ISDIR(filestat.st_mode));
}
//send the data of the file which the client want
int sendobj(int connectfd,char* serverfilepath)
{
FILE* sfp,*cfp;
int c;
sfp = fopen(serverfilepath,"r");
cfp = fdopen(connectfd,"w");
writehead(cfp,getextname(serverfilepath));
while( (c = getc(sfp)) != EOF)putc(c,cfp);
fflush(cfp);
return 0;
}
//write the packet header to the client
int writehead(FILE* cfp, char* extname)
{
#ifdef DEBUG
printf("INWRITEHEAD:::::::extname is %s::::::: ",extname);
#endif
char* content = "text/plain";
if( strcmp(extname,"html") == 0 || strcmp(extname,"htm") == 0)
content = "text/html";
else if ( strcmp(extname,"css") == 0 )
content = "text/css";
else if ( strcmp(extname,"gif") == 0 )
content = "image/gif";
else if ( strcmp(extname,"jpeg") == 0 || strcmp(extname,"jpg") == 0)
content = "image/jpeg";
else if ( strcmp(extname,"png") == 0)
content = "image/png";
#ifdef DEBUG
printf("HTTP/1.1 200 OK ");
printf("Content-Type: %s ",content);
#endif
fprintf(cfp,"HTTP/1.1 200 OK ");
fprintf(cfp,"Content-Type: %s ",content);
return 0;
}
//get the extent name of the file
char* getextname(char* filepath)
{
char* p;
if(( p = strrchr(filepath,'.')) != NULL)
return p+1;
return NULL;
}
//invoked by pthread_create
void* start_routine(void* arg)
{
ARG *info;
info = (ARG *)arg;
//handle client's requirement
process_cli(info->connfd, info->client);
delete arg;
pthread_exit(NULL);
}
運行方法:
在fc5中打開控制臺,按下面的方法進行
[root@localhost webserver1707]# ls
admin header img index.htm~
chinaunix.html header~ index.htm mymultiwebserver.c
[root@localhost webserver1707]# g++ -g -o mymultiwebserver mymultiwebserver.c -lpthread
mymultiwebserver.c: In function 鈥榲oid* start_routine(void*)鈥?
mymultiwebserver.c:253: 璀﹀憡錛氬垹闄?鈥榲oid*鈥?鏈畾涔?[root@localhost webserver1707]# ./mymultiwebserver socket....
bind....
listen....
accepting....
thread_creating....accepting....
the host is:127.0.0.1
THE REQUEST IS :GET / HTTP/1.1
cmd:GET
filepath:.//
extname://
string comparing
:::::::::::::start:::::::::::::::
cmd(GET)==GET
IN IsDIR
.// is a DIR
INWRITEHEAD:::::::extname is htm:::::::
HTTP/1.1 200 OK
Content-Type: text/html
thread_creating....accepting....
:::::::::::::end:::::::::::::::
thread_creating....accepting....
the host is:127.0.0.1
THE REQUEST IS :GET /img/sb.jpg HTTP/1.1
cmd:GET
filepath:.//img/sb.jpg
extname:jpg
string comparing
:::::::::::::start:::::::::::::::
cmd(GET)==GET
IN IsDIR
.//img/sb.jpg is a fileINWRITEHEAD:::::::extname is jpg:::::::
HTTP/1.1 200 OK
Content-Type: image/jpeg
:::::::::::::end:::::::::::::::
the host is:127.0.0.1
THE REQUEST IS :GET /img/gcc.png HTTP/1.1
cmd:GET
filepath:.//img/gcc.png
extname:png
string comparing
:::::::::::::start:::::::::::::::
cmd(GET)==GET
IN IsDIR
.//img/gcc.png is a fileINWRITEHEAD:::::::extname is png:::::::
HTTP/1.1 200 OK
Content-Type: image/png
:::::::::::::end:::::::::::::::
放一個index.htm文件在此程序所在的文件夾,打開瀏覽器,在地址欄輸入http://127.0.0.1:8848/,就可以看到網頁了~