服务器端

  客户端

必备:搭建web服务器(tiny web)

  代码设置的默认路径为桌面,hello.txt文件位于桌面,选择端口的端口号为8000(命令行第二个参数),127.0.0.1代表回送地址,指本地机,需要先行确认可用(本人通过Apache打开)。

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  //保证robust读写

  ssize_t rio_writen(int fd, void *userbuf, size_t n)

  {

  size_t nleft = n;

  ssize_t nwrite;

  char *bufp = (char*)userbuf;

  while(nleft > 0)

  {

  if((nwrite = write(fd, bufp, nleft))<0)

  {

  if(errno == EINTR)

  nwrite = 0;

  else

  return -1;

  }

  nleft -= nwrite;

  bufp += nwrite;

  }

  return n;

  }

  //带缓存版本

  static const int RIO_BUFSIZE = 8192;

  struct rio_t

  {

  int rio_fd; //文件描述符

  int rio_cnt; //未读字节数

  char *rio_bufptr; //下一个未读字节

  char rio_buf[RIO_BUFSIZE];//内部缓冲

  };

  void rio_readinitb(rio_t *rp, int fd)

  {

  rp->rio_fd = fd;

  rp->rio_cnt = 0;

  rp->rio_bufptr = rp->rio_buf;

  }

  ssize_t rio_read(rio_t *rp, void *userbuf, size_t n)

  {

  while(rp->rio_cnt <= 0)

  {

  rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));

  if(rp->rio_cnt < 0)

  {

  if(errno != EINTR)

  return -1;

  }

  else if (rp->rio_cnt == 0)

  return 0;

  else

  rp->rio_bufptr = rp->rio_buf;

  }

  size_t cnt = n > rp->rio_cnt ? rp->rio_cnt : n;

  memcpy(userbuf, rp->rio_bufptr, cnt);

  rp->rio_bufptr += cnt;

  rp->rio_cnt -= cnt;

  return cnt;

  }

  //读文本行,复制到内存位置userbuf,最后在结尾添NULL(0)

  ssize_t rio_readlineb(rio_t *rp, void *userbuf, size_t maxlen)

  {

  ssize_t readn;

  char c;

  char *buf = (char*)userbuf;

  int i;

  for(i = 1; i < maxlen; ++i)

  {

  readn = rio_read(rp, &c, 1);

  if(readn < 0)

  return -1;

  else if (readn == 0)

  {

  if(i == 1)

  return 0;

  else

  break;

  }

  else

  {

  *buf++ = c;

  if(c == '\n')

  {

  ++i;

  break;

  }

  }

  }

  *buf = 0;

  return i-1;

  }

  //打开服务器端监听描述符

  int open_listenfd(const char *port)

  {

  addrinfo hints, *listp, *p;

  int listenfd, optval = 1;

  memset(&hints, 0, sizeof(addrinfo));

  hints.ai_socktype = SOCK_STREAM;

  hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;

  hints.ai_flags |= AI_NUMERICSERV;

  getaddrinfo(NULL, port, &hints, &listp);

  for(p = listp; p; p = p->ai_next)

  {

  if((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0)

  continue;

  setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));

  if(bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)

  break;

  close(listenfd);

  }

  freeaddrinfo(listp);

  if(!p)

  return -1;

  if(listen(listenfd, 3) < 0)

  {

  close(listenfd);

  return -1;

  }

  return listenfd;

  }

  void doit(int fd); //http事务

  void read_requesthdrs(rio_t *rp); //读请求报头并忽略

  int parse_uri(char *uri, char *filename, char *cgiargs); //解析uri

  void serve_static(int fd, char *filename, int filesize); //访问静态资源

  void get_filetype(char *filename, char *filetype); //几种支持的文件类型

  void serve_dynamic(int fd, char *filename, char *cgiargs); //访问动态资源

  void clienterror(int fd, char *cause, int errnum, std::string shortmsg, std::string longmsg); //错误检查

  int MAXLINE = 100;

  int main(int argc, const char * argv[]) {

  // insert code here...

  int listenfd, connfd;

  char hostname[MAXLINE], port[MAXLINE];

  socklen_t clientlen;

  sockaddr_storage clientaddr;

  if(argc!=2) //两个命令行参数,可执行文件名和端口号

  {

  fprintf(stderr, "usage: %s \n", argv[0]);

  exit(1);

  }

  listenfd = open_listenfd(argv[1]); //打开监听描述符,argv[1]为端口号,这里选择8000

  while(1)

  {

  clientlen = sizeof(clientaddr);

  connfd = accept(listenfd, (sockaddr*)&clientaddr, &clientlen); //等待连接请求,创建已连接描述符

  getnameinfo((sockaddr*)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); //获得请求客户端的信息

  //客户端主机名,端口号(端口号为临时分配的一个值)

  printf("Accepted connection from (%s, %s)\n", hostname, port);

  doit(connfd);

  close(connfd);

  printf("按任意键继续");

  getchar();

  }

  }

  void doit(int fd)

  {

  int is_static;

  struct stat sbuf;

  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];

  char filename[MAXLINE], cgiargs[MAXLINE];

  rio_t rio;

  rio_readinitb(&rio, fd);

  rio_readlineb(&rio, buf, MAXLINE);//读请求行

  printf("Request headers:\n");

  printf("%s\n", buf);

  sscanf(buf, "%s %s %s",method, uri, version);

  //只支持GET方法

  if(strcasecmp(method, "GET"))

  {

  clienterror(fd, method, 501, "Not implemented", "Tiny does not implement this method");

  return;

  }

  read_requesthdrs(&rio);//读请求报头

  is_static = parse_uri(uri, filename, cgiargs);//解析URI,判断静态访问还是动态

  //通过文件名获得文件信息,存于sbuf

  if(stat(filename, &sbuf) < 0)

  {

  clienterror(fd, filename, 404, "Not Found", "Tiny couldn't find the file");

  return;

  }

  //静态访问

  if(is_static)

  {

  if(!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))

  {

  clienterror(fd, filename, 403, "Forbidden", "Tiny couldn't read the file");

  return;

  }

  serve_static(fd, filename, sbuf.st_size);

  }

  else

  {

  if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))

  {

  clienterror(fd, filename, 403, "Forbidden", "Tiny couldn't run the CGI program");

  return;

  }

  //动态访问

  serve_dynamic(fd, filename, cgiargs);

  }

  }

  void clienterror(int fd, char *cause, int errnum, std::string shortmsg, std::string longmsg)

  {

  char buf[MAXLINE], body[MAXLINE];

  sprintf(body, "Tiny ERROR ");  sprintf(body, "%s\r\n", body);

  sprintf(body, "%s%d: %s\r\n", body, errnum, shortmsg.c_str());

  sprintf(body, "%s

%s: %s\r\n", body, longmsg.c_str(), cause);  sprintf(body, "%s


The Tiny Web server\r\n", body);

  sprintf(buf, "HTTP/1.0 %d %s\r\n",errnum, shortmsg.c_str());

  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 read_requesthdrs(rio_t *rp)

  {

  char buf[MAXLINE];

  rio_readlineb(rp, buf, MAXLINE);

  //找到最后一行,为空行(\r\n)

  while(strcmp(buf, "\r\n"))

  {

  rio_readlineb(rp, buf, MAXLINE);

  }

  return;

  }

  int parse_uri(char *uri, char *filename, char *cgiargs)

  {

  char *ptr;

  //认为动态资源存放于cgi-bin目录

  if(!strstr(uri, "cgi-bin"))

  {

  strcpy(cgiargs, "");

  strcpy(filename, "/Users/wanghao/Desktop");//设置桌面为默认路径

  strcat(filename, uri);

  if(uri[strlen(uri)-1] == '/')//路径最后为/时添加的默认路径

  strcat(filename, "home.html");

  return 1;

  }

  else

  {

  ptr = index(uri, '?');

  if(ptr)

  {

  strcpy(cgiargs, ptr+1);

  *ptr = '\0';

  }

  else

  {

  strcpy(cgiargs, "");

  }

  strcpy(filename, "/Users/wanghao/Desktop");

  strcat(filename, uri);

  return 0;

  }

  }

  void get_filetype(char *filename, char *filetype)

  {

  if(strstr(filename, ".html"))

  strcpy(filetype, "text/html");

  else if(strstr(filename, ".gif"))

  strcpy(filetype, "image/gif");

  else if(strstr(filename, ".png"))

  strcpy(filetype, "image/png");

  else if(strstr(filename, ".jpg"))

  strcpy(filetype, "image/jpg");

  else

  strcpy(filetype, "text/plain");

  }

  void serve_static(int fd, char *filename, int filesize)

  {

  char *srcp, filetype[MAXLINE], buf[MAXLINE];

  get_filetype(filename, filetype);

  sprintf(buf, "HTTP/1.0 200 OK\r\n");

  sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);

  sprintf(buf, "%sConnection: close\r\n", buf);

  sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);

  sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);

  rio_writen(fd, buf, strlen(buf));

  printf("Response headers:\n");

  printf("%s", buf);

  //只读方式打开文件,将文件映射到虚拟内存

  int srcfd = open(filename, O_RDONLY, 0);

  srcp = (char*)mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);

  close(srcfd);

  rio_writen(fd, srcp, filesize);//输出到客户端

  munmap(srcp, filesize);//内存回收

  }

  void serve_dynamic(int fd, char *filename, char *cgiargs)

  {

  char buf[MAXLINE], *emptylist[] = {NULL};

  char *envp[]={0,NULL};

  sprintf(buf, "HTTP/1.0 200 OK\r\n");

  rio_writen(fd, buf, strlen(buf));

  sprintf(buf, "Server: Tiny Web Server\r\n");

  rio_writen(fd, buf, strlen(buf));

  //开子进程,在子进程执行动态访问

  if(fork() == 0)

  {

  setenv("QUERY_STRING", cgiargs, 1);//获得cgi参数

  dup2(fd, STDOUT_FILENO);//重定位

  execve(filename, emptylist, envp);

  }

  wait(NULL);

  }