PART1
proxy相当于客户端client和服务器server的结合体:一方面接收用户传来的请求,另一方面修改用户请求并转换为正式的传给server的请求,接收请求再返回给用户。注意用户传来的请求和实际请求有差异,详见实验指导2.1.
整体流程类似这样:proxy监听某一端口,用户传过来一个request,格式如下:
GET uri HTTP/1.1 \r\n
Host: www.ruc.edu.cn (可能没有) \r\n
User-Agent: xxx \r\n
...
\r\n (空行)
用浏览器访问应该也会得到这样的格式。
用csapp.c提供的Accept函数就能接收信息;仿照tiny.c在proxy.c中加入getnameinfo函数,在服务器端挂起后在本地ip访问(192.144.187.76: 11584, 11584是我的端口号),在proxy端得到这样的信息:

前面一串为getnameinfo得到的ip和port,后面为用inet_ntoa(clientaddr.sin_addr) 和clientaddr.sin_port得到的ip和port.
106.120.213.88为北京ip,而88.213.120.106在沙特,显然后者是正常的电脑序,前者为网络序。
用accept返回的socket传递信息,你可以把其当作文件fd而用rio来读写.
按实验指导处理header后,将其传递给real server,方法是用(csapp) open_clientfd(host, port)建立与server的连接,用返回的fd进行类似的操作。由于实验要求为线性处理请求,不用fork个进程,传递完结束就好了。
还有个重要的tip: 传给open_clientfd的host不带端口("localhost:11584"),而header里的host带端口(“localhost:11585");没有端口要默认port="80"(char*);csapp.c的unix_error会直接exit(0),为了防止proxy down掉把它删掉就好了。
其他就没有什么难度了,写一下我遇到的bug:strcat和strcpy混用,strcpy后没在末尾插入'\0'导致strcat插到很奇怪的地方;多次调用同一个函数,栈空间地址相同,上一次的数组还没清空,而地址相同,可能导致首次strcat也插入到很奇怪的地方... 得出的经验教训是一定不要首先用strcat,不要混用这两个东西..
实验结果如下(访问动态地址):


PART2
升级为多线程,我原本打算开个线程池做调度队列,被同学吐槽太麻烦= =原来随便设个局部变量传入就行了,,
关键的detached(写在线程调用函数内部):
void* work(void* vfd)
{
pthread_detach(pthread_self());
...
把struct转为void*是不合法的,所以如果你不想存struct,就用一个int作参数吧,反正只用传入一个fd就行。
PART3
加入缓存,下次用户再向你问同样的uri你只要返回缓存就行,我比较懒,就用了一堆数组和二维数组作为缓存,最大可存uri数为100.
为了处理多线程下的缓存同步问题,我最开始给整个缓存加了一个大锁,也就是访问每一项缓存都要抢占这个大锁;读并行-写阻塞采用ppt的方法:设置最大读的信号量t,每次读操作t--, 写操作占用所有信号量。这样写完后cache项能拿15分,但并发项反而得不到分。
经过反复调试得到原因如下:测试数据并发两个线程,然后故意堵塞先的线程的写操作(不知道怎么做到的),再执行第二个线程的写操作,这样由于它们共用一个锁,会出现卡住出不了结果的情况。
解决方法:缓存的每个项分别用一个单独的锁,这样总共有100个锁,访问时各自独立;再加一把整个缓存的大锁,用于保证访问整个缓存并修改的函数(LRU策略的更新函数)的原子性。
与访问整个缓存相关的函数的锁策略如下:
int ifincache(char* uri); //判断uri是否在cache里,由于是只读不用放锁
int alloccache(char* uri) //分配新的cache位置给uri,采用LRU策略
{
P(&mutex2); //全局更新操作,加大锁
for(int i = 0; ...) //找合适的i
{
for(int j = 0;j < THREAD_READ_MAXNUM; ++j) P(&mutex[i]); //写操作,全堵塞
...//读操作
for(int j = 0;j < THREAD_READ_MAXNUM; ++j) V(&mutex[i]);
}
V(&mutex2);
return ...;
}
从这里也可以看出为什么实验指导说"不必遵循严格LRU”:写操作全堵过程中可能有新的更新导致它的时间更新为最新了,优先级降低。
还有一些细节:线程1用ifincache查询uri存在后切换到线程2,线程2删除缓存中的uri,切换为线程1就会读取出错。解决方法是在P(&mutex[i])后再读取一下uri判断一下是否真的没有变化,如果有变化说明发生这样的情况,直接退出。由于写操作一定全堵塞,这么写可以证明是正确的。
还有很多细节省略,,
还有一个tip:关闭sigpipe:
Signal(SIGPIPE, SIG_IGN);
实验结果贴图:

反思总结
写之前对socket编程一知半解,虽然写之后也不太懂Accept, listen_connfd等函数的内部实现,但对网络编程的一些基本原理已经有点懂了;只有自己真正去写一个proxy,才会明白各种可能遇到的问题和坑,而这些经验也是很珍贵的,因为:只有你写出来,东西才是你的。
测试数据好水




Comments | NOTHING