當前位置:
首頁 > 知識 > 搭建web伺服器(tiny web)

搭建web伺服器(tiny web)

伺服器端

搭建web伺服器(tiny web)

客戶端

搭建web伺服器(tiny web)

搭建web伺服器(tiny web)

代碼設置的默認路徑為桌面,hello.txt文件位於桌面,選擇埠的埠號為8000(命令行第二個參數),127.0.0.1代表回送地址,指本地機,需要先行確認可用(本人通過Apache打開)。

#include <iostream>

#include <unistd.h>

#include <cstddef>

#include <sys/socket.h>

#include <netdb.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/mman.h>

#include <string>

//保證robust讀寫

ssize_t rio_writen(int fd, void *userbuf, size_t n)

{

size_t nleft = n;

ssize_t nwrite;

char *bufp = (char*)userbuf;

while(nleft > 0)

{

if((nwrite = write(fd, bufp, nleft))<0)

{

if(errno == EINTR)

nwrite = 0;

else

return -1;

}

nleft -= nwrite;

bufp += nwrite;

}

return n;

}

//帶緩存版本

static const int RIO_BUFSIZE = 8192;

struct rio_t

{

int rio_fd; //文件描述符

int rio_cnt; //未讀位元組數

char *rio_bufptr; //下一個未讀位元組

char rio_buf[RIO_BUFSIZE];//內部緩衝

};

void rio_readinitb(rio_t *rp, int fd)

{

rp->rio_fd = fd;

rp->rio_cnt = 0;

rp->rio_bufptr = rp->rio_buf;

}

ssize_t rio_read(rio_t *rp, void *userbuf, size_t n)

{

while(rp->rio_cnt <= 0)

{

rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));

if(rp->rio_cnt < 0)

{

if(errno != EINTR)

return -1;

}

else if (rp->rio_cnt == 0)

return 0;

else

rp->rio_bufptr = rp->rio_buf;

}

size_t cnt = n > rp->rio_cnt ? rp->rio_cnt : n;

memcpy(userbuf, rp->rio_bufptr, cnt);

rp->rio_bufptr += cnt;

rp->rio_cnt -= cnt;

return cnt;

}

//讀文本行,複製到內存位置userbuf,最後在結尾添NULL(0)

ssize_t rio_readlineb(rio_t *rp, void *userbuf, size_t maxlen)

{

ssize_t readn;

char c;

char *buf = (char*)userbuf;

int i;

for(i = 1; i < maxlen; ++i)

{

readn = rio_read(rp, &c, 1);

if(readn < 0)

return -1;

else if (readn == 0)

{

if(i == 1)

return 0;

else

break;

}

else

{

*buf++ = c;

if(c == "
")

{

++i;

break;

}

}

}

*buf = 0;

return i-1;

}

//打開伺服器端監聽描述符

int open_listenfd(const char *port)

{

addrinfo hints, *listp, *p;

int listenfd, optval = 1;

memset(&hints, 0, sizeof(addrinfo));

hints.ai_socktype = SOCK_STREAM;

hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;

hints.ai_flags |= AI_NUMERICSERV;

getaddrinfo(NULL, port, &hints, &listp);

for(p = listp; p; p = p->ai_next)

{

if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)

continue;

setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));

if(bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)

break;

close(listenfd);

}

freeaddrinfo(listp);

if(!p)

return -1;

if(listen(listenfd, 3) < 0)

{

close(listenfd);

return -1;

}

return listenfd;

}

void doit(int fd); //http事務

void read_requesthdrs(rio_t *rp); //讀請求報頭並忽略

int parse_uri(char *uri, char *filename, char *cgiargs); //解析uri

void serve_static(int fd, char *filename, int filesize); //訪問靜態資源

void get_filetype(char *filename, char *filetype); //幾種支持的文件類型

void serve_dynamic(int fd, char *filename, char *cgiargs); //訪問動態資源

void clienterror(int fd, char *cause, int errnum, std::string shortmsg, std::string longmsg); //錯誤檢查

int MAXLINE = 100;

int main(int argc, const char * argv[]) {

// insert code here...

int listenfd, connfd;

char hostname[MAXLINE], port[MAXLINE];

socklen_t clientlen;

sockaddr_storage clientaddr;

if(argc!=2) //兩個命令行參數,可執行文件名和埠號

{

fprintf(stderr, "usage: %s <port>
", argv[0]);

exit(1);

}

listenfd = open_listenfd(argv[1]); //打開監聽描述符,argv[1]為埠號,這裡選擇8000

while(1)

{

clientlen = sizeof(clientaddr);

connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen); //等待連接請求,創建已連接描述符

getnameinfo((sockaddr*)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); //獲得請求客戶端的信息

//客戶端主機名,埠號(埠號為臨時分配的一個值)

printf("Accepted connection from (%s, %s)
", hostname, port);

doit(connfd);

close(connfd);

printf("按任意鍵繼續");

getchar();

}

}

void doit(int fd)

{

int is_static;

struct stat sbuf;

char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];

char filename[MAXLINE], cgiargs[MAXLINE];

rio_t rio;

rio_readinitb(&rio, fd);

rio_readlineb(&rio, buf, MAXLINE);//讀請求行

printf("Request headers:
");

printf("%s
", buf);

sscanf(buf, "%s %s %s",method, uri, version);

//只支持GET方法

if(strcasecmp(method, "GET"))

{

clienterror(fd, method, 501, "Not implemented", "Tiny does not implement this method");

return;

}

read_requesthdrs(&rio);//讀請求報頭

is_static = parse_uri(uri, filename, cgiargs);//解析URI,判斷靜態訪問還是動態

//通過文件名獲得文件信息,存於sbuf

if(stat(filename, &sbuf) < 0)

{

clienterror(fd, filename, 404, "Not Found", "Tiny couldn"t find the file");

return;

}

//靜態訪問

if(is_static)

{

if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))

{

clienterror(fd, filename, 403, "Forbidden", "Tiny couldn"t read the file");

return;

}

serve_static(fd, filename, sbuf.st_size);

}

else

{

if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))

{

clienterror(fd, filename, 403, "Forbidden", "Tiny couldn"t run the CGI program");

return;

}

//動態訪問

serve_dynamic(fd, filename, cgiargs);

}

}

void clienterror(int fd, char *cause, int errnum, std::string shortmsg, std::string longmsg)

{

char buf[MAXLINE], body[MAXLINE];

sprintf(body, "<html><title>Tiny ERROR</title>");

sprintf(body, "%s<body bgcolor=""ffffff"">
", body);

sprintf(body, "%s%d: %s
", body, errnum, shortmsg.c_str());

sprintf(body, "%s<p>%s: %s
", body, longmsg.c_str(), cause);

sprintf(body, "%s<hr><em>The Tiny Web server</em>
", body);

sprintf(buf, "HTTP/1.0 %d %s
",errnum, shortmsg.c_str());

rio_writen(fd, buf, strlen(buf));

sprintf(buf, "Content-type: text/html
");

rio_writen(fd, buf, strlen(buf));

sprintf(buf, "Content-length: %d

", (int)strlen(body));

rio_writen(fd, buf, strlen(buf));

rio_writen(fd, body, strlen(body));

}

void read_requesthdrs(rio_t *rp)

{

char buf[MAXLINE];

rio_readlineb(rp, buf, MAXLINE);

//找到最後一行,為空行(

while(strcmp(buf, "
"))

{

rio_readlineb(rp, buf, MAXLINE);

}

return;

}

int parse_uri(char *uri, char *filename, char *cgiargs)

{

char *ptr;

//認為動態資源存放於cgi-bin目錄

if(!strstr(uri, "cgi-bin"))

{

strcpy(cgiargs, "");

strcpy(filename, "/Users/wanghao/Desktop");//設置桌面為默認路徑

strcat(filename, uri);

if(uri[strlen(uri)-1] == "/")//路徑最後為/時添加的默認路徑

strcat(filename, "home.html");

return 1;

}

else

{

ptr = index(uri, "?");

if(ptr)

{

strcpy(cgiargs, ptr+1);

*ptr = "";

}

else

{

strcpy(cgiargs, "");

}

strcpy(filename, "/Users/wanghao/Desktop");

strcat(filename, uri);

return 0;

}

}

void get_filetype(char *filename, char *filetype)

{

if(strstr(filename, ".html"))

strcpy(filetype, "text/html");

else if(strstr(filename, ".gif"))

strcpy(filetype, "image/gif");

else if(strstr(filename, ".png"))

strcpy(filetype, "image/png");

else if(strstr(filename, ".jpg"))

strcpy(filetype, "image/jpg");

else

strcpy(filetype, "text/plain");

}

void serve_static(int fd, char *filename, int filesize)

{

char *srcp, filetype[MAXLINE], buf[MAXLINE];

get_filetype(filename, filetype);

sprintf(buf, "HTTP/1.0 200 OK
");

sprintf(buf, "%sServer: Tiny Web Server
", buf);

sprintf(buf, "%sConnection: close
", buf);

sprintf(buf, "%sContent-length: %d
", buf, filesize);

sprintf(buf, "%sContent-type: %s

", buf, filetype);

rio_writen(fd, buf, strlen(buf));

printf("Response headers:
");

printf("%s", buf);

//只讀方式打開文件,將文件映射到虛擬內存

int srcfd = open(filename, O_RDONLY, 0);

srcp = (char*)mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);

close(srcfd);

rio_writen(fd, srcp, filesize);//輸出到客戶端

munmap(srcp, filesize);//內存回收

}

void serve_dynamic(int fd, char *filename, char *cgiargs)

{

char buf[MAXLINE], *emptylist[] = {NULL};

char *envp[]={0,NULL};

sprintf(buf, "HTTP/1.0 200 OK
");

rio_writen(fd, buf, strlen(buf));

sprintf(buf, "Server: Tiny Web Server
");

rio_writen(fd, buf, strlen(buf));

//開子進程,在子進程執行動態訪問

if(fork() == 0)

{

setenv("QUERY_STRING", cgiargs, 1);//獲得cgi參數

dup2(fd, STDOUT_FILENO);//重定位

execve(filename, emptylist, envp);

}

wait(NULL);

}

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 程序員小新人學習 的精彩文章:

Nginx 日誌和變數
jsp頁面內嵌另一個jsp公共頁面

TAG:程序員小新人學習 |