linux安装tiny proxy(搭建web服务器tinyweb)
linux安装tiny proxy(搭建web服务器tinyweb)#include <sys/stat.h>#include <netdb.h>#include <unistd.h>#include <cstddef>#include <sys/socket.h>
服务器端
客户端
代码设置的默认路径为桌面,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 == '\n')
{
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>\n" 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)\n" 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:\n");
printf("%s\n" 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"">\r\n" body);
sprintf(body "%s%d: %s\r\n" body errnum shortmsg.c_str());
sprintf(body "%s<p>%s: %s\r\n" body longmsg.c_str() cause);
sprintf(body "%s<hr><em>The Tiny Web server</em>\r\n" body);
sprintf(buf "HTTP/1.0 %d %s\r\n" errnum shortmsg.c_str());
rio_writen(fd buf strlen(buf));
sprintf(buf "Content-type: text/html\r\n");
rio_writen(fd buf strlen(buf));
sprintf(buf "Content-length: %d\r\n\r\n" (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);
//找到最后一行,为空行(\r\n)
while(strcmp(buf "\r\n"))
{
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 = '\0';
}
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\r\n");
sprintf(buf "%sServer: Tiny Web Server\r\n" buf);
sprintf(buf "%sConnection: close\r\n" buf);
sprintf(buf "%sContent-length: %d\r\n" buf filesize);
sprintf(buf "%sContent-type: %s\r\n\r\n" buf filetype);
rio_writen(fd buf strlen(buf));
printf("Response headers:\n");
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\r\n");
rio_writen(fd buf strlen(buf));
sprintf(buf "Server: Tiny Web Server\r\n");
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);
}