实现
一个web代理,并有多线程和缓存功能,所以一一来实现;
- 根据 write up 中所说,首先需要实现 HTTP/1.0 GET 请求的顺序代理:读取整个请求并解析请求(是否是有效HTTP请求),如果是则建立自己到适当 web服务器的连接,请求客户端指定对象,再将响应转发回客户端;注意:HTTP请求每行以\r\n结束,并以\r\n为尾行;
- 具体要做到将url解析为三部分:host,后半url,HTTP版本;
- 请求头中包含ua,host,connection,proxy-connection;
- 请求端口无论在url中还是默认的都必须正确;
- 处理过早关闭的连接,需要捕获SIGPIPE;
- 实现多线程工作(生产者-消费者);
- 实现缓存最近内存中使用的web对象(LRU策略);
- 设置缓存的最大内存,以及单个对象的最大内存;
handout给出了tiny服务器的源码,只需要在这个基础上进行改装;
Tiny解析
main函数:
1 | int main(int argc, char **argv) |
doit函数:
1 | void doit(int fd) |
serve_static函数:
1 | void serve_static(int fd, char *filename, int filesize) |
serve_dynamic函数:
1 | void serve_dynamic(int fd, char *filename, char *cgiargs) |
I . 顺序代理GET请求
writeup中的要求:
处理 HTTP/1.0 版本,如果遇到1.1,则需要将其作为1.0版本转发;
转发合法 HTTP 请求(实现中所示);
头中的 ua 和 两个 connection 都有给定的值:
1
2
3"User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305Firefox/10.0.3\r\n"
"Proxy-Connection: close"
"Connection: close"
-
实际上要做的,就是将doit内的操作变为转发与回复,而不是单纯回响;
那么需要将发送的包改写给目标服务器,之后把目标服务器的回响写给发送者;
要看uri中是否有端口那就应该解析uri,但和上面解析是不一样的,上面是在看读取的文件是静态还是动态;
主函数和tiny一样,只是需要在 listen之前加一条:
1 | signal(SIGPIPE,SIG_IGN); |
新建三个全局变量:
1 | //uri解析记录变量 |
doit:
1 | void doit(int fd) |
两个神奇函数:
1 | void parse_uri(char *uri) |
II . 多线程的并发
实现多线程使用 消费者-生产者 模型:
消费者和生产者共同使用一个 n个槽的优先缓冲区,生产者产生新的项目并插入缓冲区;消费者取出这些项目并使用;
因此两者的访问需要互斥,并且调度地访问:空状态(消费者等待),满状态(生产者等待);
在这个实验里,消费者就是服务端,接受各样的连接;生产者就是客户端,发送各样的连接;
实现缓冲区:
1 | typedef struct { |
客户端插入函数:
1 | void sbuf_insert(sbuf_t *sp, int item) |
服务端实现后移除项目的函数:
1 | int sbuf_remove(sbuf_t *sp) |
主函数(和tiny的main差不多):
1 | int main(int argc, char **argv) |
线程执行函数:
1 | void *thread(void *vargp) |
III . 缓存web对象
目的是为了让多次访问的web对象不用再连接服务器,直接响应;
这里会使用 读者-写者 模型 ,让线程从缓存中读和写:
只读的线程叫读者,只写的进程叫写者,读者可以和其他读者共享只读部分,写者需要有独立的访问;
这个模型有两种情况:
读者优先,写者优先;
这里使用读优先:
1 | int read_cnt; //记录读者数量 |
设置缓存区:
1 | typedef struct |
修改doit函数中的内容,得到请求后,判断uri是否在缓存中,不在就添加进去:
1 | void doit(int fd) |
总结
虽然迷迷糊糊的,但跟着线程走了一遍,多多少少学会了更多的东西:比如信号量的运用,线程创建和运作方式,以及状态机和模型的特点;但这个lab确实感受到了难度,等往后学的深入再返回看的话应该还会有收获;