目录
1.Proxy Shell的总体流程图如图所示
2.getaddrinfo和getnameinfo
其中在open_listenfd函数中,使用了getaddrinfo函数,这个函数封装了包括大小端转换和点分十进制转int型等多个函数的功能,并且有DNS解析的功能,能够调用完以后直接创建套接字。而这个函数的返回值就是一系列与套接字有关的结构体,可以直接使用其中的结构体创建套接字。
对应的想要获取主机的IP地址等信息,可以使用getnameinfo函数实现,避免大小端和IP的转换。函数的具体内容可以看CSAPP的656页。
3.定义一个结构体指针和定义一个结构体变量的区别
在自己写程序时,遇到这样一个问题,定义一个结构体变量不需要初始化就可以直接赋值,而定义一个结构体指针需要初始化才可以给它赋值,举例如下:
struct stu
{
char name[256];
int age;
}
//定义结构体变量后可直接赋值
struct stu val;
val.name="zhangsan";
//定义结构体指针不能直接赋值,下面程序是错误的,会报内存溢出的错误
struct stu *ptr;
ptr->name="zhangsan";
//但是定义动态指针是可以直接赋值的
//比如在C++中这样的定义方法是与结构体变量定义类似的,这个ptr是动态变量指针,
//并且在栈上分配了sizeof(stu)的空间
stu *ptr(new stu);
//又比如在C语言中
struct stu* ptr=(struct stu*)malloc(sizeof(struct stu));
原因是当定义一个变量时,系统会分配sizeof(struct stu)大小的内存给这个变量,但是当只定义一个结构体指针时,系统只会分配4个字节的大小给内存,所以肯定不能直接赋值,所以一般定义变量应该定义变量,这样就可以直接使用。或者在C++中使用动态内存指针的定义。
4.代理服务器缓存实现方法
在缓存的实现中,使用了LRU思想,如果缓存已满,就将使用最少的缓存去除,添加新缓存。具体实现就是使用双向链表,示意图如下
每次加入的元素都加在head_ptr下面,如果元素个数已满,就首先删除tail_ptr指向的元素。每次查找到元素时,也把该元素移到head_ptr下面,所以越靠近head_ptr的就是越近使用的,而越靠近tail_ptr的就是越早使用的,所以就把越早使用的删去。
5.在C语言中,结构体声明与C++有不一样的地方:
typedef struct stu
{
int age;
char name[20];
}student;
//在C语言中,以下的定义方法是可行的
struct stu a;
student a;
//在C++中,以下的定义方式是可行的
struct stu a;
student a;
stu a;
6.http数据包的具体格式
一个http请求数据包,主要由请求行,请求报头,空行和请求体四部分构成,其中每一个行都是由"\r\n"结尾的,结构如图所示:
其中请求行是必须的,
请求报头的格式为“报头名称:报头数据”可能没有请求报头,也有可能有多个请求报头
空行也就是“\r\n”,也是必须的,如果有报头就在报头后面,如果没有报头就是在请求行后面
请求体主要是在post方法下会有,主要是参数的传递。
一个http响应数据包,和请求数据包差不多,主要由响应行,响应报头,空行和响应体四部分构成
其中唯一不一样的就是响应行,格式是“HTTP版本 状态码 状态消息”
代码详解
/*主程序*/
#include <stdio.h>
#include "csapp.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/wait.h>
#include <errno.h>
#include <pthread.h>
#include <fcntl.h>
#include "cache.h"
/* Recommended max cache and object sizes */
//#define MAX_CACHE_SIZE 1049000
//#define MAX_OBJECT_SIZE 10240
#define USECACHE 1
pthread_mutex_t mutex=PTHREAD_MUTEX_INITIALIZER;
CacheHead cacheinfo;
/* You won't lose style points for including this long line in your code */
static const char *user_agent_hdr = "User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3\r\n";
void doit(int fd);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);
void build_request_header(rio_t *rp, char *method, char *other, char *request);
void parse_uri(char *uri, char **hostname, char **other, int *port);
int connect_endserver(char *hostname, int port);
void *thr(void *arg);
void build_response_header(rio_t *rp, char *request);
int main(int argc, char** argv)
{
int listenfd;
char host[MAXLINE],port[MAXLINE];
struct sockaddr_storage clientaddr;
socklen_t addrlen=sizeof(clientaddr);
InitCache(&cacheinfo);
printf("%s", user_agent_hdr);
if (argc!=2)
{
printf("arguments are not enough\n");
return 0;
}
listenfd = open_listenfd(argv[1]);
if (listenfd<=0)
{
printf("open_listenfd is error");
return -1;
}
while (1)
{
int acceptfd = Accept(listenfd, (SA*)&clientaddr, &addrlen);
if (acceptfd>=0)
{
Getnameinfo((SA*)&clientaddr, addrlen, host, MAXLINE, port, MAXLINE,0);
printf("Accept connection from %s:%s", host, port);
pthread_t tid;
pthread_create(&tid, NULL, thr, (void *)&acceptfd);
}
}
FreeAllObj(&cacheinfo);
return 0;
}
void *thr(void *arg)
{
int acceptfd=*(int *)arg;
pthread_detach(pthread_self());
pthread_mutex_lock(&mutex);
doit(acceptfd);
close(acceptfd);
printf("tid:%lu\n", (long signed int)pthread_self());
pthread_mutex_unlock(&mutex);
return NULL;
}
void doit(int listenfd)
{
rio_t rp, server_rio;//这里之所以用变量而不是用指针,是因为定义一个变量就会把整个变量的空间都分配出来,
//但是定义指针只会分配四个字节用于存放指针变量指向的地址值,如果这里定义了指针变量就会在
//Rio_readinitb函数中出现内存溢出,因为这个结构体的空间根本没有定义出来。
char buf[MAXLINE],method[MAXLINE],uri[MAXLINE],version[MAXLINE];
char request[MAXBUF];
char *hostname,*other;
int port,clientfd;
Rio_readinitb(&rp, listenfd);
Rio_readlineb(&rp, buf, MAXLINE);
printf("request headers:\n");
printf("%s\n",buf);
sscanf(buf,"%s %s %s", method, uri, version);
if (strcmp(method,"GET"))
clienterror(listenfd, method, "501", "Not Implemented","Tiny does not implement this method");
parse_uri(uri, &hostname, &other, &port);
if (hostname==NULL||other==NULL)
{
printf("parse_uri is error\n");
return;
}
#ifdef USECACHE//带缓存的代理服务器
if (!FindObj(&cacheinfo, uri, request))//在缓存中没有找到对应内容
{
printf("--connect server--\n");
build_request_header(&rp, method, other, request);
if ((clientfd=connect_endserver(hostname, port))<0)
{
printf("connect_endserver is error\n");
return;
}
Rio_writen(clientfd, request, strlen(request));
Rio_readinitb(&server_rio, clientfd);
build_response_header(&server_rio, request);
if (AddObj(&cacheinfo, uri, request)<0)
{
printf("AddObj is error\n");
return;
}
Close(clientfd);
}
printf("-----------request--------------------\n");
printf("%s\n",request);
printf("------------count--------------\n");
printf("%d\n", cacheinfo.count);
Rio_writen(listenfd, request, strlen(request));
#else//不带缓存的代理服务器
build_request_header(&rp, method, other, request);
if ((clientfd=connect_endserver(hostname, port))<0)
{
printf("connect_endserver is error\n");
return;
}
Rio_writen(clientfd, request, strlen(request));
Rio_readinitb(&server_rio, clientfd);
int n;
while ((n=Rio_readlineb(&server_rio, buf, MAXLINE))!=0)
{
printf("proxy received message: %s", buf);
Rio_writen(listenfd, buf, strlen(buf));
}
#endif
}
void clienterror(int fd, char *cause, char *errnum,
char *shortmsg, char *longmsg)
{
char buf[MAXLINE], body[MAXBUF];
/* Build the HTTP response body */
sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);
sprintf(body, "%s<hr><em>The Tiny Web proxy</em>\r\n", body);
/* Print the HTTP response */
sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
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 build_request_header(rio_t *rp, char *method, char *other, char *request)
{
sprintf(request, "%s %s HTTP/1.0\n", method, other);
char buf[MAXLINE];
do
{
Rio_readlineb(rp,buf,MAXLINE);
//printf("%s",buf);
strcat(request, buf);
}while(strcmp(buf,"\r\n"));
printf("%s", request);
}
void build_response_header(rio_t *rp, char *request)
{
memset(request, 0, strlen(request));
char buf[MAX_OBJECT_SIZE];
int n;
while ((n=Rio_readlineb(rp, buf, MAXLINE))!=0)
{
strcat(request, buf);
}
printf("%s", request);
}
void parse_uri(char *uri, char **hostname, char **other, int *port)
{
*port=80;//默认端口为80
static char temp[256];
char *other_buf;//带_buf的都是临时存放的变量
char *hostname_buf;
char *port_buf;
strcpy(temp, uri);
if ((hostname_buf=strchr(temp, '/'))==NULL)//找到第一个反斜杠,往后两个位置就是主机名
{
printf("temp solve1 error\n");
return;
}
if (strlen(hostname_buf)<3)
{
printf("temp solve2 error\n");
return;
}
hostname_buf+=2;
other_buf=strchr(hostname_buf,'/');//找到主机名后面的反斜杠,斜杠后面就是具体参数值
//*other_buf='\0';
//other_buf++;
if ((port_buf=strchr(hostname_buf, ':'))!=NULL)//在主机名中找是否存在冒号,如果有,就改变端口值
{
*port_buf='\0';
port_buf++;
*port=atoi(port_buf);
}
*other=other_buf;
*hostname=hostname_buf;
}
int connect_endserver(char *hostname, int port)
{
char PortStr[256];
sprintf(PortStr,"%d",port);
return open_clientfd(hostname, PortStr);
}
/*cache.h*/
#include <stdio.h>
#include "csapp.h"
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 10240
#define MAX_COUNT 10
typedef struct CACHE
{
char uri[MAXLINE];
char obj[MAX_OBJECT_SIZE];
struct CACHE *pre_ptr;
struct CACHE *next_ptr;
}Cache;
//在C语言中,结构体如果不加typedef,就要用struct CACHE来命名
//但C++中可以
typedef struct CACHEHEAD
{
int count;
Cache *head_ptr;
Cache *tail_ptr;
}CacheHead;
int InitCache(CacheHead *cacheinfo);
int AddObj(CacheHead *cacheinfo, char *uri, char *obj);
int FindObj(CacheHead *cacheinfo, char *uri, char *obj);
int FreeAllObj(CacheHead *cacheinfo);
/*cache.c*/
#include "cache.h"
//采用LRU的思想,双向链表,最近使用的放在链表前面,以前使用的放在链表后面
int InitCache(CacheHead *cacheinfo)
{
FreeAllObj(cacheinfo);
cacheinfo->count=0;
cacheinfo->head_ptr=NULL;
cacheinfo->tail_ptr=NULL;
return 0;
}
//cache_signal用malloc去申请
int AddObj(CacheHead *cacheinfo, char *uri, char *obj)
{
if (strlen(obj)>MAX_OBJECT_SIZE)
{
printf("obj is lager, add fail\n");
return -1;
}
Cache *signal_cache=(Cache*)malloc(sizeof(Cache));
if (signal_cache==NULL)
{
printf("malloc is error\n");
return -2;
}
strcpy(signal_cache->uri, uri);
strcpy(signal_cache->obj, obj);
if (cacheinfo->count==0)//内存链表上没有元素
{
signal_cache->pre_ptr=NULL;
signal_cache->next_ptr=NULL;
cacheinfo->count++;
cacheinfo->head_ptr=signal_cache;
cacheinfo->tail_ptr=signal_cache;
}else if ((cacheinfo->count>0)&&(cacheinfo->count<MAX_COUNT))//链表上元素数目在0-10之间
{
signal_cache->pre_ptr=NULL;
signal_cache->next_ptr=cacheinfo->head_ptr;
cacheinfo->head_ptr->pre_ptr=signal_cache;
cacheinfo->count++;
cacheinfo->head_ptr=signal_cache;
}else if(cacheinfo->count>=MAX_COUNT)//链表上挂满元素
{
cacheinfo->head_ptr->pre_ptr=signal_cache;
signal_cache->pre_ptr=NULL;
signal_cache->next_ptr=cacheinfo->head_ptr;
cacheinfo->head_ptr=signal_cache;
Cache *temp=cacheinfo->tail_ptr;
cacheinfo->tail_ptr=temp->pre_ptr;
temp->pre_ptr->next_ptr=NULL;
free(temp);
}
return 0;
}
int FindObj(CacheHead *cacheinfo, char *uri, char *obj)
{
Cache *signal_cache=cacheinfo->head_ptr;
/*在链表中寻找是否存在这个uri*/
while ((signal_cache!=NULL)&&(strcmp(signal_cache->uri, uri)))
{
signal_cache=signal_cache->next_ptr;
}
if (signal_cache==NULL)
{
return 0;
}
/*如果找到了就把该元素移到第一个去*/
strcpy(obj, signal_cache->obj);
if (cacheinfo->head_ptr==signal_cache)//找到的元素在第一个位置,什么都不用改变
{}else if(cacheinfo->tail_ptr==signal_cache)//找到的元素在最后一个位置,移到最前面去
{
cacheinfo->tail_ptr=signal_cache->pre_ptr;
signal_cache->next_ptr=cacheinfo->head_ptr;
signal_cache->pre_ptr=NULL;
cacheinfo->head_ptr=signal_cache;
}
else//找到的元素在中间位置,移到第一个去
{
signal_cache->pre_ptr->next_ptr=signal_cache->next_ptr;
signal_cache->next_ptr->pre_ptr=signal_cache->pre_ptr;
cacheinfo->head_ptr->pre_ptr=signal_cache;
signal_cache->pre_ptr=NULL;
signal_cache->next_ptr=cacheinfo->head_ptr;
cacheinfo->head_ptr=signal_cache;
}
return 1;
}
int FreeAllObj(CacheHead *cacheinfo)
{
Cache *signal_cache=cacheinfo->head_ptr;
Cache *temp;
while(signal_cache)
{
temp=signal_cache->next_ptr;
free(signal_cache);
signal_cache=temp;
}
cacheinfo->head_ptr=NULL;
cacheinfo->tail_ptr=NULL;
return 0;
}