一.概述
上接第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一节讨论了给线程传参数
- 首先是直接把int强制转换为void* <— 图26.3
–>但是部分系统不支持这种转换 - 然后是把int* 强制转换为void* <— 26.4.1
–>但是存在connfd被覆盖的问题 - 然后是每次连接都动态分配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都提到的,这样存在着很严重的同步问题.
尝试在下一章进行解决.