CSAPP proxy lab
csapp proxylab
目的: 实现一个客户到和服务器中间的代理。
- 用来转发请求和响应
- 同时处理多个请求
- 缓存对象(未实现)
转发请求和响应
作为服务端接收请求
HTTP请求格式
GET / HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept-Language: en-US,en;q=0.9
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
==注意:每一行以\r\n结尾,最后一行是\r\n==
请求行(GET / HTTP/1.1),三个部分 :
1. GET,表示方法
1. /,资源的路径,这里请求www.baidu.com 地址的根目录资源
1. HTTP/1.1,HTTP协议版本
请求头:请求行下面行都是请求头的内容k :v形式
请求体: 发送请求的自定义参数(本例中没有)
接收请求可以理解成,通过socket的文件描述符,一行一行读取,直到最后一行是\r\n
//读到最后一行是\r\n结束
while(strcmp(buf, "\r\n")) {
requestHeader *curRequestHeader =(requestHeader *) Malloc(sizeof(requestHeader));
//读一行
Rio_readlineb(rp, buf, MAXLINE);
}
读取第一行例子

改造请求
http是一个字符串,根据需要封装成结构体,之后再把请求行和请求头改成需要转发的地址
请求头host,和port是我们要改造的,这代表转发目的地的地址和端口
作为客户端发请求
-
构造新的请求头字符串,拼接请求行,请求头,(请求体)
- 通过 host 和 port 获取目的服务器的文件描述符
- 通过文件描述符写出去(发送请求)
//1.请求行 buffer
//GET / HTTP/1.1\r\n
sprintf(buf, "%s %s %s\r\n", oldMethod, proxyPath, oldVersion);
//2.请求头 buffer
//strcat()
for (int i = 0; i < myrequest->headerLen; ++i) {
char *newHead=(char *)Malloc(sizeof(char));
sprintf(newHead, "%s: %s\r\n", myrequest->header[i]->name, myrequest->header[i]->value);
strcat(buf,newHead);
Free(newHead);
//Rio_writen(fd, buf, strlen(buf));
}
strcat(buf,"\r\n");
printf("request=>\n %s",buf);
//2. 文件描述符fd
int clientfd = Open_clientfd(proxyHost, proxyPortStr);
//3. 发送请求
Rio_writen(clientfd,buf,strlen(buf));
流程如下

同时处理多个请求
一个线程,发送请求如果阻塞了,proxy无法响应别的请求,可以有多种方法处理解决
- fork进程:子进程的方式消耗太大
- 多路复用: epoll可以,但是无法利用cpu多核心,效率低
- 线程: 轻量,又利用了cpu核心
实现线程池
1线程池
构造线程,执行run方法,无限循环从阻塞队列获取fd,再对fd进行读写
核心方法 void doit(int connFd) 实现请求转发
blockQueue myqueue;
//初始化线程,并运行
void init(){
nthreads = INIT_THREAD_N;
initConnFdArray(&myqueue, SBUFSIZE);
create_threads(nthreads);
}
void doit(int connFd);
void *run(void)
{
//Pthread_detach(pthread_self());
while (1) {
usleep(1000*330);
int connfd = poll(&myqueue);
//实现请求转发的核心方法
doit(connfd);
Close(connfd);
//pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); // 可以取消
}
}
void create_threads(int end)
{
for (int i = 0; i < end; i++) {
// create thread
Pthread_create(&(threads[i].tid), NULL, run, NULL);
}
}
2阻塞队列
构造线程数组(循环队列),利用生产者消费者模式,线程不断从阻塞队列获取文件描述符,操作文件描述符实现代理通信。
阻塞队列,存放客户端新来的文件描述符。
信号量mutex: 互斥存取
信号量empty: 用来线程同步消费
信号量full: 用来线程同步生产
typedef struct {
int connFdArray[SBUFSIZE];
int n;
//头
int front;
//尾
int rear;
//当前容量
sem_t mutex;
sem_t full;
sem_t empty;
int size;
} blockQueue;
void initConnFdArray(blockQueue* queue,int n){
sem_unlink("empty");
sem_unlink("full");
sem_unlink("mutexLock");
// 初始化sem_t指针
//queue->slots = *(sem_t *)malloc(sizeof(sem_t));
// queue->items = *(sem_t *)malloc(sizeof(sem_t));
// queue->mutex = *(sem_t *)malloc(sizeof(sem_t));
queue->size=0;
queue->n=n;
queue->front=0;
queue->rear=0;
Sem_init(&queue->mutex, 0, 1); /* Binary semaphore for locking */
Sem_init(&queue->empty, 0, n); /* Initially, buf has n empty slots */
Sem_init(&queue->full, 0, 0); /* Initially, buf has zero data items */
for (int i = 0; i < n; ++i) {
queue->connFdArray[i] = -1;
}
}
void offer(blockQueue* queue,int connFd){
P(&queue->empty); /* Wait for available slot */
P(&queue->mutex);
int tailIndex= queue->rear; /* Lock the buffer */
queue->connFdArray[tailIndex] = connFd;
queue->rear= (tailIndex+1) % (queue->n); /* Insert the item */
++queue->size;
V(&queue->mutex); /* Unlock the buffer */
V(&queue->full); /* Announce available item */
}
int poll(blockQueue* queue){
int item;
P(&queue->full); /* Wait for available item */
//pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL); // 不可取消
P(&queue->mutex); /* Lock the buffer */
int headIndex =(queue->front);
item = queue->connFdArray[headIndex]; /* Remove the item */
headIndex++;
if (headIndex==queue->n)
{
headIndex=0;
}
queue->front=headIndex;
--queue->size;
V(&queue->mutex); /* Unlock the buffer */
V(&queue->empty); /* Announce available slot */
return item;
}
####
缓存对象
时间有限,这是cache lab内容就不再实现
结果

代码地址