#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define HOSTLEN 256
#define PORT 8080
#define BACKLOG 2
int make_server_socket(int portnum, int backlog)
{
struct sockaddr_in saddr;
int sock_id;
if ((sock_id = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("creating socket failed.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(sock_id, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&saddr,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(portnum);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock_id,(struct sockaddr *)&saddr,sizeof(struct sockaddr)) == -1)
{
perror("bind error.");
exit(1);
}
if ( listen(sock_id, backlog) != 0 )
return -1;
return sock_id;
}
setup(pthread_attr_t *attrp)
{
pthread_attr_init(attrp);//线程初始化 作用???
pthread_attr_setdetachstate(attrp,PTHREAD_CREATE_DETACHED);
//设置为PTHREAD_CREATE_DETACHED 以分离状态启动线程
//或者设置为PTHREAD_CREATE_JOINABLE,正常启动线程
/*
线程属性结构如下:
typedef struct
{
int detachstate; 线程的分离状态
int schedpolicy; 线程调度策略
struct sched_param schedparam; 线程的调度参数
int inheritsched; 线程的继承性
int scope; 线程的作用域
size_t guardsize; 线程栈末尾的警戒缓冲区大小
int stackaddr_set;
void * stackaddr; 线程栈的位置
size_t stacksize; 线程栈的大小
}pthread_attr_t;
*/
//time(&server_started);
//server_requests = 0;
//server_bytes_sent = 0;
}
void *handle_call(void *fdptr)
{//接到的是一个句柄
FILE *fpin;//定义一个指向文件的指针
char request[BUFSIZ];//定义一个缓存用于存放接收到的数据
int fd ;//再定义一个临时套接字文件描述符
fd = *(int *)fdptr;//将传过来的参数转成套接字文件描述符
free(fdptr);//释放内存
fpin = fdopen(fd, "r");//打开套接字文件描述符 而得到一个文件描述符
fgets(request,BUFSIZ,fpin); //将fpin里面的内容读入request数组中
//got a call on 4: request = GET /htdoc/readerchaxun.html?shuming=20040502215&Submit=ChaXun HTTP/1.1
//sanitized version is htdoc/readerchaxun.html?shuming=20040502215&Submit=ChaXun
printf("got a call on %d: request = %s", fd, request);//向终端打印得到的信息
skip_rest_of_header(fpin);//再一次的处理收到的信息 作用????
process_rq(request, fd);//处理客户请求
fclose(fpin);//最后关
}
skip_rest_of_header(FILE *fp)//这里的参数是文件描述符
{
char buf[BUFSIZ];//用于存放数据
while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 )
;//fgets和上面一样,strcmp 比较字符串是否相等 相等为1
}
process_rq( char *rq, int fd)
{
char cmd[BUFSIZ], arg[BUFSIZ];
if ( sscanf(rq, "%s%s", cmd, arg) != 2 )//把收到的信息以空格为分隔,分别存入两个数组之中
//后面的版本信息不接收 GET /index.html HTTP/1.0
return;
sanitize(arg);//对请求的路经分解 比如 /htdoc/index.html
printf("sanitized version is %s\n", arg);//把转化好的输出
//printf("the cmd is =",cmd);
//cmd=htdoc/readerchaxun.html?shuming=20040502215&Submit=Submit
//htdoc/readerchaxun.html?shuming=20040502215&userpassword=123456&Submit=ChaXun
/*
分析:先用get方法接收来的url字符串放入cmd数组之中
1、先判断有没有 “?”
如果有,就按原来的执行。
如果没有,就先把“?”
之后的字符串提起出来。放入另外一个数组data[]之中
再从data[]数组中按下面的格式提起出来.
*/
// 把它们分别提起出来:
//:htdoc/readerchaxun.html
//:shuming
//:20040502215
//:userpassword
//:123456
//:Submit
//:ChaXun
if ( strcmp(cmd,"GET") != 0 )//对第一个数组判断
not_implemented();//如果被调用,说明收到的不是GET 退出
else if ( built_in(arg, fd) )//给客户端以送确认信号
;
else if ( not_exist( arg ) )//提供文件名字,获取文件对应属性
do_404(arg, fd);
else if ( isadir( arg ) )//判断是不是当前目录?是
//do_ls( arg, fd );//读取文件目录信息
do_index(fd);//读取默认index.html
else
do_cat( arg, fd );//最后读取文件
}
sanitize(char *str)
{
char *src, *dest;//定义两个char 指针变量原和目的
src = dest = str;//对三个同学给传进来的值
//注意这里str是数组如:arg[]={""/","i","n","d","e","x",".","h","t","m","l"} /index.html
while( *src )
{
if( strncmp(src,"/../",4) == 0 )//字符串比较,相等反回0
src += 3;//目的是去掉/../
else if ( strncmp(src,"//",2) == 0 )
src++;//目的是去掉//
else
*dest++ = *src++;//将去掉后的src给dest
}
*dest = '\0';//最后这里得到的是一个完全的目录
if ( *str == '/' )
strcpy(str,str+1);//???
if ( str[0]=='\0' || strcmp(str,"./")==0 || strcmp(str,"./..")==0 )
strcpy(str,".");
}
built_in(char *arg, int fd)
{
FILE *fp;//定义一个文件描述符
if ( strcmp(arg,"status") != 0 )//status 是什么意思???
return 0;
http_reply(fd, &fp, 200, "OK", "text/plain",NULL);//发送确认信号
//fprintf(fp,"Server started: %s", ctime(&server_started));
//fprintf(fp,"Total requests: %d\n", server_requests);
//fprintf(fp,"Bytes sent out: %d\n", server_bytes_sent);
fclose(fp);
return 1;
}
http_reply(int fd, FILE **fpp, int code, char *msg, char *type, char *content)
{
FILE *fp = fdopen(fd, "w");//先打开客户端套接字文件描述符
//int bytes = 0;
if ( fp != NULL )
{//打开成功,向客户端写入信息
//bytes = fprintf(fp,"HTTP/1.0 %d %s\r\n", code, msg);
fprintf(fp,"HTTP/1.0 %d %s\r\n", code, msg);//向客户端写入信息
//bytes += fprintf(fp,"Content-type: %s\r\n\r\n", type);
fprintf(fp,"Content-type: %s\r\n\r\n", type);
if ( content )
//bytes += fprintf(fp,"%s\r\n", content);
fprintf(fp,"%s\r\n", content);
}
fflush(fp);//刷新fp
if ( fpp )
*fpp = fp;
else
fclose(fp);
//return bytes;
return 0;
}
not_implemented(int fd)
{
http_reply(fd,NULL,501,"Not Implemented","text/plain","That command is not implemented");
}
do_404(char *item, int fd)
{
http_reply(fd,NULL,404,"Not Found","text/plain","The item you seek is not here");
}
isadir(char *f)
{
struct stat info;
return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) );
//st_mode 文件对应的模式,文件,目录等
}
not_exist(char *f)
{
struct stat info;//定义一个结构体 有什么用??
return( stat(f,&info) == -1 );//通过文件描述符获取文件对应的属性
//提供文件名字,获取文件对应属性
}
/*
do_ls(char *dir, int fd)
{//arg fd 为传进来的参数
DIR *dirptr;
struct dirent *direntp;
FILE *fp;
//int bytes = 0;
//bytes = http_reply(fd,&fp,200,"OK","text/plain",NULL);
http_reply(fd,&fp,200,"OK","text/plain",NULL);
//bytes += fprintf(fp,"Listing of Directory %s\n", dir);
fprintf(fp,"Listing of Directory %s\n", dir);
if ( (dirptr = opendir(dir)) != NULL )
{//打开成功
while( direntp = readdir(dirptr) )
{
//bytes += fprintf(fp, "%s\n", direntp->d_name);
fprintf(fp, "%s\n", direntp->d_name);
}
closedir(dirptr);
}
fclose(fp);
//server_bytes_sent += bytes;
}
*/
char * file_type(char *f)
{//反回的是文件类型
char *cp;
if ( (cp = strrchr(f, '.' )) != NULL )//这里用strrchr 是提起 一点之后的字符串 再反回给cp
//strrchr()在串中查找指定字符的最后一个出现
return cp+1;//cp +1 是什么意思 ?? 指的是以一点之后的字符串
//不要反回一点再加字符串
return "";
}
do_index(int fd)
{
char *f1="index.html";
char *type = "text/html";
FILE *fpsock, *fpfile;//再定义两个文件描述符
int c;
fpsock = fdopen(fd, "w");//打开客户端套接字 反回一个文件描述符 用于写数据
fpfile = fopen( f1 , "r");//打开服务器端的文件 反回一个文件描述符 用于读取数据
if ( fpsock != NULL && fpfile != NULL )
{//如果两个都成功 才是做下面的动作
http_reply(fd,&fpsock,200,"OK",type,NULL);//通知客户端,准备接收数据
while( (c = getc(fpfile) ) != EOF )//读取数据 为什么不用read ???
{
putc(c, fpsock);//写入数据 为什么不可以用write ???
}
fclose(fpfile);//关
fclose(fpsock);//关
}
}
do_cat(char *f, int fd)
{//这里f=arg fd=fd
char *extension = file_type(f);//这里将得到的是文件的后缀
char *type = "text/plain";//定义并初始化这字符串
FILE *fpsock, *fpfile;//再定义两个文件描述符
int c;
if ( strcmp(extension,"html") == 0 )
type = "text/html";
else if ( strcmp(extension, "gif") == 0 )
type = "image/gif";
else if ( strcmp(extension, "jpg") == 0 )
type = "image/jpg";
else if ( strcmp(extension, "jpeg") == 0 )
type = "image/jpeg";
fpsock = fdopen(fd, "w");//打开客户端套接字 反回一个文件描述符 用于写数据
fpfile = fopen( f , "r");//打开服务器端的文件 反回一个文件描述符 用于读取数据
if ( fpsock != NULL && fpfile != NULL )
{//如果两个都成功 才是做下面的动作
http_reply(fd,&fpsock,200,"OK",type,NULL);//通知客户端,准备接收数据
while( (c = getc(fpfile) ) != EOF )//读取数据 为什么不用read ???
{
putc(c, fpsock);//写入数据 为什么不可以用write ???
//bytes++;
}
fclose(fpfile);//关
fclose(fpsock);//关
}
}
main(int ac, char *av[])
{
int sock, fd;
int *fdptr;
pthread_t worker;
pthread_attr_t attr;//定义一个线程变量
void *handle_call(void *);//反回????定义线程调用函数
sock = make_server_socket(PORT,BACKLOG);//反回客户端请求ID号
if ( sock == -1 ) { perror("making socket"); exit(2); }
setup(&attr);//设置线程以分离状态启动
while(1)
{
fd = accept( sock, NULL, NULL );//得到的是客户端请求的端口套接字描述符
//server_requests++;
fdptr = malloc(sizeof(int));//申请分配内存空间,放一个句柄
*fdptr = fd;//把请求的放在句柄
pthread_create(&worker,&attr,handle_call,fdptr);//创建一个线程
//传参数 fdptr 到handle_call之中
}
}