lab7 proxylab

lab7 proxylab_第1张图片

前情提要,如果看了书本,这个lab难度不高,但是如果不看书,难度还是挺高的,并且这个lab会用到cachelab中学到的东西,需要阅读

  1. 第十章:系统编程
  2. 第十一章:网络编程
  3. 第十二章:并发

实验介绍

  1. 使用代理完成客户端和服务器的连接(HTTP操作,socket通信)
    1. 接受客户端的连接,读并分析请求
    2. 将请求发送给服务器
    3. 读取服务器的回应,并将回应发送给对应的客户端
  2. 实现多线程的功能
  3. 增加cache功能

测试

测试:./driver.sh
50 15 15

第一部分:实现一个顺序的网络代理

任务要求

  1. 最开始,代理应该监听某个端口来等待连接的请求,这个端口通过命令行给出
  2. 一旦建立连接,代理应该读取并解析请求。它需要确定这个请求是否发送了一个合法的HTTP请求
  3. 如果这个请求合法,则发送给服务器,然后将服务器的response返回给客户

具体实现

  1. main函数打开一个监听的描述符,如果通过这个监听描述符accept成功了,则打开了一个用于通信的描述符fd,将fd作为doit的函数,调用doit
  2. doit函数与描述符b建立通信,读取客户端发来的请求,这个请求一定是以下两种形式之一
    1. 指定端口 GET http://www.cmu.edu:8080/hub/index.html HTTP/1.1
    2. 固定端口80 GET http://www.cmu.edu/hub/index.html HTTP/1.1
  3. 将上面收到的请求分解,主要是得到中间的url,然后将url分解,得到hostportpath,以指定端口为例,这三个分别是
    1. www.cmu.edu
    2. 8080
    3. /hub/index.html
  4. 根据上面得到的三个参数,构建发往服务器的request
  5. 这个request是HTTP格式(具体实现上就把这个放到一个字符数组就行了,每一行通过\r\n隔开,并且最后要多一行\r\n),由请求头和请求行组成,实验文档要求格式如下:
    1. GET /hub/index.html HTTP/1.0
    2. Host: www.cmu.edu
    3. User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:10.0.3) Gecko/20120305 Firefox/10.0.3
    4. Connection: close
    5. Proxy-Connection: close
  6. 与服务器建立连接,得到server_fd描述符,将上面已经生成好的request发往服务器
  7. 不断地读服务器返回的值,写入fd文件描述符
#include "csapp.h"
#include 
#include 
#include 
#include 
#include 

/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define MAXLINE 8192

/* 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";

typedef struct {
  char host[MAXLINE];
  char port[MAXLINE];
  char path[MAXLINE];
} URI;

void cout_uri_format_error() { printf("wrong uri format\n"); }

void parse_line(URI *req_uri, char *uri) {
  char *host_start = strstr(uri, "://");
  if (host_start == NULL) {
    cout_uri_format_error();
    return;
  }
  host_start += 3;
  char *port_start = strstr(host_start, ":");
  char *path_start = strstr(host_start, "/");
  if (path_start == NULL) {
    cout_uri_format_error();
    return;
  }
  strcpy(req_uri->path, path_start);
  *path_start = '\0';
  if (port_start != NULL) {
    strcpy(req_uri->port, port_start + 1);
    *port_start = '\0';
  } else {
    strcpy(req_uri->port, "80");
  }
  strcpy(req_uri->host, host_start);
  return;
}

void build_req_server(char *req_server, URI *req_uri) {
  sprintf(req_server, "GET %s HTTP/1.0\r\n", req_uri->path);
  sprintf(req_server, "%sHost: %s\r\n", req_server, req_uri->host);
  sprintf(req_server, "%s%s", req_server, user_agent_hdr);
  sprintf(req_server, "%sConnection: close\r\n", req_server);
  sprintf(req_server, "%sProxy-Connection: close\r\n", req_server);
  sprintf(req_server, "%s\r\n", req_server);
}

void doit(int fd) {
  // 初始化rio类函数的缓冲区
  rio_t rio;
  Rio_readinitb(&rio, fd);
  // 读入这一行http请求
  char buf[MAXLINE];
  Rio_readlineb(&rio, buf, MAXLINE);
  printf("Request headers:\n");
  printf("%s", buf);
  char method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  // 解析这一行http请求,总共三个部分
  if (sscanf(buf, "%s %s %s", method, uri, version) != 3) {
    printf("HTTP Requset Format Wrong!\n");
    return;
  }
  // 判断是否是GET请求,这个比较函数忽略大小写,get也行
  if (strcasecmp(method, "GET")) {
    printf("method: %s not implemented\n", method);
    return;
  }
  // 至此,已经完成了对客户端请求的解析,接下来要构造出对服务器的请求
  // 首先解析我们的uri,得到host port path
  URI *req_uri = (URI *)malloc(sizeof(URI));
  parse_line(req_uri, uri);
  // 根据我们的信息,构造出真正的发往服务器的请求
  char req_server[MAXLINE];
  build_req_server(req_server, req_uri);
  // 开始连接服务器
  int server_fd = Open_clientfd(req_uri->host, req_uri->port);
  if (server_fd < 0) {
    printf("connection failed\n");
    return;
  }
  // 连接成功,设置缓冲区,将request写入
  rio_t server_rio;
  Rio_readinitb(&server_rio, server_fd);
  Rio_writen(server_fd, req_server, strlen(req_server));
  // 等待服务器的返回,并写入客户端的fd中
  size_t rec_bytes;
  while ((rec_bytes = Rio_readlineb(&server_rio, buf, MAXLINE)) != 0) {
    printf("proxy received %d bytes\n", (int)rec_bytes);
    Rio_writen(fd, buf, rec_bytes);
  }
  Close(server_fd);
}

int main(int argc, char **argv) {

  if (argc != 2) {
    fprintf(stderr, "usage: %s \n", argv[0]);
    exit(1);
  }
  // 监听请求连接的端口
  int listenfd = Open_listenfd(argv[1]);
  // 与客户端进行连接
  int connfd;
  char hostname[MAXLINE], port[MAXLINE];
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;
  while (1) {
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)(&clientaddr), &clientlen);
    Getnameinfo((SA *)(&clientaddr), clientlen, hostname, MAXLINE, port,
                MAXLINE, 0);
    printf("Accepted connection from(%s,%s)\n", hostname, port);
    doit(connfd);
    Close(connfd);
  }

  return 0;
}

第二部分:并发

任务要求

  1. 实现并发即可,没有要求用什么样的方式

具体实现

  1. 采用生产者消费者的方式,和书上第12.5.5节的代码几乎完全一样
  2. 需要在main函数中加入一个Signal(SIGPIPE, SIG_IGN);以屏蔽SIGPIPE信号。我不太清楚不屏蔽会怎么样,可能是不屏蔽的话,客户端如果意外挂了,会导致代理服务器一起挂了
#define SUBFSIZE 16
#define NTHREADS 4

typedef struct {
  int *buf;
  int n;
  int front;
  int rear;
  sem_t mutex;
  sem_t slots;
  sem_t items;
} sbuf_t;

sbuf_t sbuf;

void sbuf_init(sbuf_t *sp, int n) {
  sp->buf = Calloc(n, sizeof(int));
  sp->n = n;
  sp->front = sp->rear = 0;
  Sem_init(&sp->mutex, 0, 1);
  Sem_init(&sp->slots, 0, n);
  Sem_init(&sp->items, 0, 0);
}

void sbuf_deinit(sbuf_t *sp) { Free(sp->buf); }

void sbuf_insert(sbuf_t *sp, int item) {
  P(&sp->slots);
  P(&sp->mutex);
  sp->buf[(++sp->rear) % (sp->n)] = item;
  V(&sp->mutex);
  V(&sp->items);
}

int sbuf_remove(sbuf_t *sp) {
  P(&sp->items);
  P(&sp->mutex);
  int item = sp->buf[(++sp->front) % (sp->n)];
  V(&sp->mutex);
  V(&sp->slots);
  return item;
}

void *thread(void *vargp) {
  Pthread_detach(Pthread_self());
  while (1) {
    int connfd = sbuf_remove(&sbuf);
    doit(connfd);
    Close(connfd);
  }
}

int main(int argc, char **argv) {

  if (argc != 2) {
    fprintf(stderr, "usage: %s \n", argv[0]);
    exit(1);
  }
  // 监听请求连接的端口
  Signal(SIGPIPE, SIG_IGN);
  int listenfd = Open_listenfd(argv[1]);

  // 线程池
  sbuf_init(&sbuf, SUBFSIZE);
  pthread_t pid;
  for (int i = 0; i < NTHREADS; i++) {
    Pthread_create(&pid, NULL, thread, NULL);
  }

  // 与客户端进行连接
  int connfd;
  char hostname[MAXLINE], port[MAXLINE];
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;
  while (1) {
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)(&clientaddr), &clientlen);
    sbuf_insert(&sbuf, connfd);
  }

  return 0;
}

第三部分:cache

任务要求

  1. 这里说是cache,还不如说是一个大号的哈希表,以uri为键,以对应的资源为值。然后对这个哈希表的长度有点要求,大概10个表项。因为题目要求#define MAX_CACHE_SIZE 1049000#define MAX_OBJECT_SIZE 102400,其中object的意思就是一行,差不多就是十倍的样子
  2. 如果某个uri对应的资源太大了, 那就不考虑加入cache
  3. 对这个cahce需要实现并发访问,即加上锁,这里加入读写锁

具体实现

  1. 结合cachelab中cache的结构,还需要额外加上data字段
  2. 如果要实现真正的LRU,在并发访问的基础上,还需要对timestamp也加锁,否则就要用原子类型的变量
  3. 这个实现结合代码来看,思路还是比较清晰的,不再赘述
    我在这里犯了两个小错,结果导致debug了好久
  4. cacheline中的tagdata的长度是不一样的,我一开始把data长度弄成了MAXLINE,结果0分
  5. doit中我们用uri去读cache以及写cache,但是我们在doitparse_line函数里,是修改了uri的,因此要给uri搞一个备份,否则在写cache的时候,就错了
/* Recommended max cache and object sizes */
#define MAX_CACHE_SIZE 1049000
#define MAX_OBJECT_SIZE 102400
#define MAXLINE 8192

typedef struct {
  int is_valid;
  char tag[MAXLINE];
  char data[MAX_OBJECT_SIZE];
  long long access_time;
  int read_cnt;
  sem_t read_lock;
  sem_t write_lock;
} cacheline;

#define MAX_CACHE_LINES 10
cacheline Cache[MAX_CACHE_LINES];

sem_t time_mutex;
long long time_stamp = 1;

void init_cache() {
  for (int i = 0; i < MAX_CACHE_LINES; i++) {
    Cache[i].is_valid = 0;
    Cache[i].access_time = 0;
    Cache[i].read_cnt = 0;
    Sem_init(&Cache[i].read_lock, 0, 1);
    Sem_init(&Cache[i].write_lock, 0, 1);
  }
  Sem_init(&time_mutex, 0, 1);
}

void read_in(int i) {
  P(&Cache[i].read_lock);
  if (Cache[i].read_cnt == 0) {
    P(&Cache[i].write_lock);
  }
  Cache[i].read_cnt++;
  V(&Cache[i].read_lock);
}

void read_out(int i) {
  P(&Cache[i].read_lock);
  if (Cache[i].read_cnt == 1) {
    V(&Cache[i].write_lock);
  }
  Cache[i].read_cnt--;
  V(&Cache[i].read_lock);
}

int read_cache(int fd, char *uri) {
  int flag = 0;
  for (int i = 0; i < MAX_CACHE_LINES; i++) {
    read_in(i);
    if (Cache[i].is_valid && !strcmp(uri, Cache[i].tag)) {
      flag = 1;
      P(&time_mutex);
      Cache[i].access_time = time_stamp++;
      V(&time_mutex);
      Rio_writen(fd, Cache[i].data, strlen(Cache[i].data));
    }
    read_out(i);
    if (flag) {
      return 0;
    }
  }
  return -1;
}

void write_cache(char *uri, char *data) {
  int has_empty = -1;
  int lru_evict = 0;
  for (int i = 0; i < MAX_CACHE_LINES; i++) {
    read_in(i);
    if (Cache[i].is_valid == 0) {
      has_empty = i;
    }
    if (Cache[i].access_time < Cache[lru_evict].access_time) {
      lru_evict = i;
    }
    read_out(i);
    if (has_empty != -1) {
      break;
    }
  }
  int write_index = (has_empty == -1) ? lru_evict : has_empty;
  P(&Cache[write_index].write_lock);
  Cache[write_index].is_valid = 1;
  P(&time_mutex);
  Cache[write_index].access_time = time_stamp++;
  V(&time_mutex);
  strcpy(Cache[write_index].tag, uri);
  strcpy(Cache[write_index].data, data);
  V(&Cache[write_index].write_lock);
}

你可能感兴趣的:(CSAPP,linux)