UNP 学习笔记 第二十六章

一.概述

上接第5章,实现完多进程服务器后,开始实现多线程服务器.
多线程服务器最重要的问题就是同步互斥问题.
因此我的多线程服务器的学习步骤为:
	1.熟悉线程间的同步和对资源的互斥访问问题,主要用到互斥锁,信号量和条件变量
	2.线程方面重点API的学习.
	3.复习csapp第10章,
	4.大致翻看http图解这本书,复习html基础知识
	5.结合csapp第11章,实现对http处理的服务器,使得客户可以请求一个http页面.
	6.结合csapp第12章,将服务器改进为多线程.
	7.学习第15章,在上述服务器中加入Unix域协议,并尽可能多的加入其它线程间通信机制
至此多线程web服务器结束,下一步学习各种IO函数,同时实现基于EPOLL的事件驱动服务器

二. 看APUE线程的部分

三. 复习用gdb调试多线程

四.本书重点API的学习

基本线程创建终止等
1.   pthread_create
2.   pthread_join
3.   pthread_self
4.   pthread_detach
5.   pthread_exit
互斥锁
6.   pthread_mutex_lock
7.   pthread_mutex_unlock
条件变量
8.   pthread_cond_wait
9.   pthread_cond_signal
10.  pthread_cond_broadcast

五. 书上例子

使用线程的str_cli函数

整个26.4一节讨论了给线程传参数

  1. 首先是直接把int强制转换为void* <— 图26.3
    –>但是部分系统不支持这种转换
  2. 然后是把int* 强制转换为void* <— 26.4.1
    –>但是存在connfd被覆盖的问题
  3. 然后是每次连接都动态分配connfd <— 图26.4
    –>但是malloc和free是不可重入函数

线程特定数据和随后的例子略过

图26-17 感受并发编程对共享资源的访问问题

图26-18 感受通过加锁解决共享资源的访问问题

26.8节 重点阅读对条件变量内部具有解锁再加锁这一操作的解释

六. 实现具有线程池的多线程服务器

复习CSAPP第10章开始到最后

1.重点API

1. open
2. close
3. stat && fstat && struct stat 
4. S_ISREG && S_ISDIR && S_ISSOCK
5.  dup2
6. 

2.实现带缓冲的IO函数

rio.h

//there are robust IO functions.

#include <sys/types.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

#define MAX_BUFSIZE 8192

typedef struct{
  int   rio_fd;                 //file sockfd which connected to buffer
  int   rio_cnt;                //bytes that hasn't be read
  char* rio_bufptr;             //pointer
  char  rio_buf[MAX_BUFSIZE];   //buffer,store data
}rio_t;

//initializer
void rio_initb(rio_t* rp,int fd)
{
  rp->rio_fd = fd;
  rp->rio_cnt = 0;
  rp->rio_bufptr = rp->rio_buf;
}

static ssize_t rio_read(rio_t* rp,char* buf,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 != -1)
        return -1;
    }else if(rp->rio_cnt == 0){
      return 0;
    }else{
      rp->rio_bufptr = rp->rio_buf;
    }
  }
  int cnt = (size_t)rp->rio_cnt < n ? (int)rp->rio_cnt : n;
  memcpy(buf,rp->rio_bufptr,cnt);
  rp->rio_bufptr += cnt;
  rp->rio_cnt -= cnt;
  return cnt;
}

ssize_t rio_readnb(rio_t* rp,void* buf,size_t n)
{
  size_t nleft = n;
  ssize_t nread;
  char* bufp = (char*) buf;
  while(nleft)
  {
    if((nread = rio_read(rp,bufp,nleft)) < 0)
      return -1;
    else if(nread == 0)
      break;
    nleft -= nread;
    bufp += nread;
  }
  return n - nleft;
}

int rio_readlineb(rio_t* rp,void* buf,size_t maxlen)
{
  int     n,rc;
  char    c;
  char* bufp = (char*)buf;
  for(n = 1;(size_t) n < maxlen; ++n)
  {
    if((rc = rio_read(rp,&c,1)) == 1)
    {
      *bufp++ = c;
      if(c == '\n')
      {
        n++;
        break;
      }
    }else if(rc == 0){
      if(n == 1)
        return 0;
      else
        break;
    }else
      return -1;
  }
  *bufp = 0;
  return n - 1;
}

测试代码

从text.txt文件里按行读取每一行,并且输出到终端上

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include "rio.h"
#include <fcntl.h>

#define MAXLINE 8192

int main()
{
  int fd = open("text.txt",O_RDONLY,0);
  rio_t rio;
  char sendline[MAXLINE];

  rio_initb(&rio,fd);
  
  while((rio_readlineb(&rio,sendline,sizeof(sendline)) != 0))
  {
    write(STDOUT_FILENO,sendline,strlen(sendline));
  }
  return 0;
}

执行结果

在这里插入图片描述

3.读取文件元数据

查询和处理一个文件的st_mode位要会

4. IO重定向

先略微看看共享文件这一小节

测试代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int main()
{
  int fd = open("text.txt",O_RDONLY,0);
  dup2(fd,STDIN_FILENO);
  
  char buf[8192];
  
  while(fgets(buf,sizeof(buf),stdin))//对STDIN_FILENO重定向后,操作stdin也是操作fd了
    fputs(buf,stdout);

  return 0;
}

在这里插入图片描述

5.http图解看一遍,重点内容已标出.

6.学习CSAPP第9章,第11章的 TINY WEB

tiny.h

#include <sys/socket.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netdb.h>

#define MAXLINE 8192

typedef struct{
  int   rio_fd;
  int   rio_cnt;
  char* rio_bufptr;
  char  rio_buf[MAXLINE];
}rio_t;

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,char* buf,size_t nbytes)
{
  size_t cnt;
  while(rp->rio_cnt <= 0)
  {
    rp->rio_cnt = read(rp->rio_fd,rp->rio_buf,MAXLINE);
    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;
  }
  cnt = (size_t)rp->rio_cnt < nbytes ? (size_t)rp->rio_cnt : nbytes;
  memcpy(buf,rp->rio_bufptr,cnt);
  rp->rio_bufptr += cnt;
  rp->rio_cnt -= cnt;
  return cnt;
}

ssize_t rio_readlineb(rio_t* rp,char* buf,size_t maxlen)
{
  int     n,rc;
  char* bufp = buf, c;
  for(n = 1; (size_t)n < maxlen; ++n)
  {
    if((rc = rio_read(rp,&c,1)) == 1)
    {
      *bufp++ = c;
      if(c == '\n')
      {
        n++;
        break;
      }
    }else if(rc == 0){
      if(n == 1)
        return 0;
      else
        break;
    }else
      return -1;
  }
  *bufp = '\0';
  return n - 1;
}

ssize_t rio_writen(int fd,const char* buf,size_t nbytes)
{
  ssize_t nwritten;
  const char* p = buf;
  size_t nleft = nbytes;
  while(nleft > 0)
  {
    if((nwritten = write(fd,p,nleft)) <= 0)
    {
      if(errno == EINTR)
        nwritten = 0;
      else
        return -1;
    }
    nleft -= nwritten;
    p += nwritten;
  }
  return nbytes;
}

int open_client(const char* host,const char* port)
{
  int fd;
  struct addrinfo hints, *listp, *p;
  bzero(&hints,sizeof(hints));
  hints.ai_family = AF_INET;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = 0;
  
  getaddrinfo(host,port,&hints,&listp);
  for(p = listp; p; p = p->ai_next)
  {
    fd = socket(p->ai_family,p->ai_socktype,p->ai_protocol);
    if(fd < 0)
      continue;
    if((connect(fd,p->ai_addr,p->ai_addrlen)) == 0)
      break;
    close(fd);
  }
  freeaddrinfo(listp);
  if(!p)
    return -1;
  return fd;
}

int open_server(const char* host,const char* port)
{
  const int on = 1;
  int sockfd;
  struct addrinfo hints,*listp,*p;
  bzero(&hints,sizeof(hints));
  hints.ai_family = AF_UNSPEC;
  hints.ai_flags  = AI_PASSIVE;
  hints.ai_socktype = SOCK_STREAM;
  hints.ai_protocol = 0;

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

  for(p = listp; p; p = p->ai_next)
  {
    sockfd = socket(p->ai_family,p->ai_socktype,p->ai_protocol);
    if(sockfd < 0)
      continue;
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,(const void*)&on,sizeof(on));
    if((bind(sockfd,p->ai_addr,p->ai_addrlen)) == 0)
      break;
    close(sockfd);
  }
  freeaddrinfo(listp);
  if(!p)
    return -1;
  if(listen(sockfd,100) < 0){ //backlog == 100
    close(sockfd);
    return -1;
  }
  return sockfd;
}

serv.h

#include "tiny.h"
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

extern char** environ;

void doit(int connfd);
void read_requesthdrs(rio_t* rp,char* buf);
int parse_uri(char* uri,char* filename,char* cgiargs);
void get_filetype(char* filename,char* filetype);
void serve_static(int fd,char* filename,int filesize);
void serve_dynamic(int fd,char* filename,char* cgiargs);
void client_error(int fd,char* errnum,char* shortmsg,char* longmsg,char* cause);

void doit(int fd)
{
  char buf[MAXLINE];
  char filename[MAXLINE],cgiargs[MAXLINE];
  char uri[MAXLINE],methods[MAXLINE],version[MAXLINE]; 
  int is_static;
  struct stat st;
  rio_t rio;

  rio_readinitb(&rio,fd);
  rio_readlineb(&rio,buf,MAXLINE);
  
  printf("Request headers:\n");
  printf("%s",buf);

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

  if((strcasecmp(methods,"GET")))
  {
    client_error(fd,"400"," error","TinyWeb only support 'Get'",NULL);
    return;
  }
  
  read_requesthdrs(&rio,buf);
  printf("Headers:\n");
  printf("%s",buf);

  is_static = parse_uri(uri,filename,cgiargs);

  if(stat(filename,&st) < 0)
  {
    client_error(fd,"400","Invalid file","You request for a invalid file",NULL);
    return;
  }

  if(is_static)
  {
    if(!(S_ISREG(st.st_mode) ) || !(S_IRUSR & st.st_mode))
    {
      client_error(fd,"400","Permission denied","You don't have permission\n",NULL);
      return;
    }
    serve_static(fd,filename,st.st_size);
  }else{
    if(!(S_ISREG(st.st_mode) ) || !(S_IXUSR & st.st_mode))
    {
      client_error(fd,"400","Permission denied","You don't have permission\n",NULL);
      return;
    }
    serve_dynamic(fd,filename,cgiargs);
  }
}

void read_requesthdrs(rio_t* rp,char* buf)
{
  char tmp[MAXLINE];
  rio_readlineb(rp,tmp,MAXLINE);
  while(strcmp(tmp,"\r\n"))
  {
    sprintf(buf,"%s",tmp);
    rio_readlineb(rp,tmp,MAXLINE);
  }
  return;
}

int parse_uri(char* uri,char* filename,char* cgiargs)
{
  char *ptr;
  if(!(strstr(uri,"cgi-bin")))//static
  {
    strcpy(filename,"");
    strcat(filename,".");
    strcat(filename,uri);

    if(uri[strlen(uri) - 1] == '/')
      strcat(filename,"home.html");
    return 1;
  }else{
    ptr = index(uri,'?');
    if(ptr){
      strcpy(cgiargs,"");
      strcat(cgiargs,ptr + 1);
      *ptr = '\0';
    }else{
      strcpy(cgiargs,"");
    }
    strcpy(filename,".");
    strcat(filename,uri);
    return 0;
  }

}

void get_filetype(char* filename, char* filetype)
{
  if(strstr(filename,".html"))
    strcpy(filetype,"text/html");
  else if(strstr(filename,".jpg"))
    strcpy(filetype,"image/jpeg");
  else if(strstr(filename,".png"))
    strcpy(filetype,"image/png");
  else if(strstr(filename,".gif"))
    strcpy(filetype,"image/gif");
  else
    strcpy(filetype,"text/plain");
}

void serve_static(int fd,char* filename,int filesize)
{
  char buf[MAXLINE];
  char filetype[MAXLINE];
  get_filetype(filename,filetype);
  
  sprintf(buf,"HTTP/1.0 200 OK\r\n");
  sprintf(buf,"%sServer: TinyWeb\r\n",buf);
  sprintf(buf,"%sConnection: close\r\n",buf);
  sprintf(buf,"%sContent-type: %s\r\n",buf,filetype);
  sprintf(buf,"%sContent-length: %d\r\n\r\n",buf,filesize);
  rio_writen(fd,buf,strlen(buf));
  printf("Response headers:\n");
  printf("%s",buf);

  int srcfd = open(filename,O_RDONLY,0);
  char* srcp =(char*) mmap(NULL,filesize,PROT_READ,MAP_PRIVATE,srcfd,0);
  rio_writen(fd,srcp,filesize);
  close(srcfd);
  munmap(srcp,filesize);
}


void serve_dynamic(int fd,char* filename,char* cgiargs)
{
  char *emptylist[] = {NULL},buf[MAXLINE];

  sprintf(buf,"HTTP/1.0 200 OK\r\n");
  sprintf(buf,"%sServer: Tiny Web Server\r\n",buf);
  rio_writen(fd,buf,strlen(buf));

  if(fork() == 0)
  {
    setenv("QUERY_STRING",cgiargs,1);
    dup2(fd,STDOUT_FILENO);
    execve(filename,emptylist,environ);
  }
  wait(NULL);
}


void client_error(int fd,char* errnum,char* shortmsg,char* longmsg,char* cause)
{
  char buf[MAXLINE],body[MAXLINE];
  sprintf(body,"<html><title>TinyWeb Error</title>");
  sprintf(body,"%s<body bgcolor=""ffffff"">\r\n",body);
  sprintf(body,"%s%s: %s\r\n",body,errnum,shortmsg);
  sprintf(body,"%s<p>%s: %s\r\n",body,longmsg,cause);
  sprintf(body,"%s<hr><em>TinyWeb</em>\r\n",body);

  sprintf(buf,"HTTP/1.1 %s %s\r\n",errnum,shortmsg);
  sprintf(buf,"%sContent-type: text/html\r\n",buf);
  sprintf(buf,"%sContent-length: %d\r\n\r\n",buf,(int)strlen(body));
  
  rio_writen(fd,buf,strlen(buf));
  rio_writen(fd,body,strlen(body));

}


serv.c

#include "serv.h"

int
main(int argc, char** argv)
{
  if(argc != 2){
    fprintf(stderr,"Please enter port\n");
    exit(0);
  }
  int listenfd = open_server(NULL,argv[1]);
  //ipv4
  struct sockaddr_in sin;
  socklen_t len;
  char host[MAXLINE];
  int connfd;

  while(1)
  {
    len = sizeof(sin);
    connfd = accept(listenfd,(struct sockaddr*) &sin,&len);

    getsockname(connfd,(struct sockaddr* )&sin,&len);
    inet_ntop(AF_INET,(const void*)&sin.sin_addr,host,MAXLINE);
    printf("%s:%d has connected!\n",host,sin.sin_port);

    doit(connfd);
    close(connfd);
  }
  return 0;
}

效果展示:请求首页

在这里插入图片描述
在这里插入图片描述

效果展示: 请求图片

在这里插入图片描述
在这里插入图片描述

总结

1. 毫无错误处理
2. 毫无边界处理
3. 这是一个迭代服务器,一次只能处理一个客户的请求.
4. 接下来,对其改进.

7.改进为多线程服务器

仅对以上代码做出了一点修改:

//serv.h中做出以下修改,即,将doit函数的返回值和参数更改为线程例程的格式
void* doit(void* args)
{
  char buf[MAXLINE];
  char filename[MAXLINE],cgiargs[MAXLINE];
  char uri[MAXLINE],methods[MAXLINE],version[MAXLINE]; 
  int is_static;
  struct stat st;
  rio_t rio;
  int fd = *(int*)args;

 
//serv.c中做出以下修改.现在每当有新连接到达时,便会使用新的线程去处理.
  while(1)
  {
    len = sizeof(sin);
    connfd = accept(listenfd,(struct sockaddr*) &sin,&len);

    getsockname(connfd,(struct sockaddr* )&sin,&len);
    inet_ntop(AF_INET,(const void*)&sin.sin_addr,host,MAXLINE);
    printf("%s:%d has connected!\n",host,sin.sin_port);

    pthread_create(&tid,NULL,doit,&connfd);
  }
 

如UNP和csapp都提到的,这样存在着很严重的同步问题.
尝试在下一章进行解决.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值