1 web服务器总体介绍:
使用浏览器作为客户端访问web服务器;
使用到的知识总结:
socket编程:
socket setsockopt bind listen read write send recv close
编写网络服务器常用的模型有哪些:
多进程版本,
多线程版本
多路IO复用:select poll epoll
第三方库: libevent
TCP/IP四层模型:
应用层--->传输层---->网络层---->数据链路层
本次案例所使用的协议:
TCP+http协议
熟悉掌握http协议的请求和应答协议.
了解html文件的编写
TCP协议重在数据的传输, 而http协议重在数据的解释
http协议请求报文格式:
1 请求行 GET /test.txt HTTP/1.1
2 请求行 健值对
3 空行 \r\n
4 数据
http协议响应消息格式:
1 状态行 200 表示成功, 404 表示请求的资源不存在
2 消息报头 健值对
3 空行 \r\n
4 响应正文
选择使用epoll模型作为web服务器:
1 创建socket
2 设置端口复用
3 绑定
4 监听
5 创建epoll树
6 将监听文件描述符上epoll树
7 while(1)
{
nready = epoll_wait();
if(nready<0)
{
}
for(i=0; i<nready; i++)
{
//若是有新的客户端连接到来,则接受新的客户端连接, 然后上epoll树
if(lfd==events[i].data.fd)
{
cfd = accept();
epoll_ctl(epfd, cfd, EPOLL_CTL_ADD, &ev);
}
else
{
http_request(cfd);
}
}
}
//处理客户端的请求
int http_request(int cfd)
{
//读取数据
//先读取一行, 可以获得浏览器想请求的文件
//然后循环将剩余的数据读完
//查看本地是否有这个文件
---若没有这个文件, 则组织应答信息并且将404.jpg文件内容发送给浏览器
---若有个这个文件, 则判断文件类型
----若是普通文件, 则组织应答消息并且将文件内容发送给浏览器
----若是目录, 则组织应答消息, 响应正文部分将目录下的所有文件组织
成文件列表的形式生成html文件(每个文件都是一个超连接), 然后发送给浏览器
}
read函数读通信文件描述符是阻塞的, 所以在while中循环读的时候若读完数据了会引起阻塞;
所以应该将通信文件描述符设置为非阻塞.
char *path = getenv(“HOME”);
遍历目录的函数:
1 opendir readdir closedir
2
int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **, const struct dirent **));
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
int main(void)
{
struct dirent **namelist;
int n;
n = scandir(".", &namelist, NULL, alphasort);
if (n < 0)
perror("scandir");
else {
while (n--) {
printf("%s\n", namelist[n]->d_name);
free(namelist[n]);
}
free(namelist);
}
}
<html><head><title>Index of ./</title></head>
<body bgcolor="#99cc99"><h4>Index of ./</h4>
<ul type=circle>
<--文件列表部分-->
<li>
<a href=filename>filename</a>
</li>
</ul>
<address><a href="http://www.itcast.com/">xhttpd</a></address>
</body></html>
SIGPIPE信号的产生: 若服务端传输数据的时候, 浏览器连接关闭, 此时由于是正在写一个
没有读端的连接, 此时内核会给web服务端发送SIGPIPE信号.
%E8%8B%A6%E7%93%9C.txt
e8 8b a6 e7 93 9c
e8---->e8
‘A’—>10
‘A’-‘A’+10
‘C’- ‘A’+10 = 12
(‘e’ - ‘a’ + 10)*16 + (‘8’-‘0’)
公式为c: r
if(c>=‘0’ && c<=‘9’)
{
r = c-‘0’;
}
else if(c>=‘A’ && c<=‘F’)
{
r = c-‘A’ + 10;
}
else ifif(c>=‘a’ && c<=‘f’)
{
r = c-‘a’ + 10;
}
ff—>15*16+15=255
7 web服务端开发:
html语法
http协议: 请求消息格式 响应消息格式
web服务端开发流程:
libevent作为web服务框架的流程
pub.h
#ifndef _PUB_H
#define _PUB_H
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
char *get_mime_type(char *name);
int get_line(int sock, char *buf, int size);
int hexit(char c);//16进制转10进制
void strencode(char* to, size_t tosize, const char* from);//编码
void strdecode(char *to, char *from);//解码
#endif
pub.c
#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{
char* dot;
dot = strrchr(name, '.'); //自右向左查找‘.’字符;如不存在返回NULL
/*
*charset=iso-8859-1 西欧的编码,说明网站采用的编码是英文;
*charset=gb2312 说明网站采用的编码是简体中文;
*charset=utf-8 代表世界通用的语言编码;
* 可以用到中文、韩文、日文等世界上所有语言编码上
*charset=euc-kr 说明网站采用的编码是韩文;
*charset=big5 说明网站采用的编码是繁体中文;
*
*以下是依据传递进来的文件名,使用后缀判断是何种文件类型
*将对应的文件类型按照http定义的关键字发送回去
*/
if (dot == (char*)0)
return "text/plain; charset=utf-8";
if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
return "text/html; charset=utf-8";
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcmp(dot, ".gif") == 0)
return "image/gif";
if (strcmp(dot, ".png") == 0)
return "image/png";
if (strcmp(dot, ".css") == 0)
return "text/css";
if (strcmp(dot, ".au") == 0)
return "audio/basic";
if (strcmp( dot, ".wav") == 0)
return "audio/wav";
if (strcmp(dot, ".avi") == 0)
return "video/x-msvideo";
if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
return "video/quicktime";
if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
return "video/mpeg";
if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
return "model/vrml";
if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
return "audio/midi";
if (strcmp(dot, ".mp3") == 0)
return "audio/mpeg";
if (strcmp(dot, ".ogg") == 0)
return "application/ogg";
if (strcmp(dot, ".pac") == 0)
return "application/x-ns-proxy-autoconfig";
return "text/plain; charset=utf-8";
}
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
* carriage return, or a CRLF combination. Terminates the string read
* with a null character. If no newline indicator is found before the
* end of the buffer, the string is terminated with a null. If any of
* the above three line terminators is read, the last character of the
* string will be a linefeed and the string will be terminated with a
* null character.
* Parameters: the socket descriptor
* the buffer to save the data in
* the size of the buffer
* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//获得一行数据,每行以\r\n作为结束标记
int get_line(int sock, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size - 1) && (c != '\n'))
{
n = recv(sock, &c, 1, 0);
/* DEBUG printf("%02X\n", c); */
if (n > 0)
{
if (c == '\r')
{
n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除
/* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return(i);
}
//下面的函数第二天使用
/*
* 这里的内容是处理%20之类的东西!是"解码"过程。
* %20 URL编码中的‘ ’(space)
* %21 '!' %22 '"' %23 '#' %24 '$'
* %25 '%' %26 '&' %27 ''' %28 '('......
* 相关知识html中的‘ ’(space)是 
*/
void strdecode(char *to, char *from)
{
for ( ; *from != '\0'; ++to, ++from) {
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) { //依次判断from中 %20 三个字符
*to = hexit(from[1])*16 + hexit(from[2]);//字符串E8变成了真正的16进制的E8
from += 2; //移过已经处理的两个字符(%21指针指向1),表达式3的++from还会再向后移一个字符
} else
*to = *from;
}
*to = '\0';
}
//16进制数转化为10进制, return 0不会出现
int hexit(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return 0;
}
//"编码",用作回写浏览器的时候,将除字母数字及/_.-~以外的字符转义后回写。
//strencode(encoded_name, sizeof(encoded_name), name);
void strencode(char* to, size_t tosize, const char* from)
{
int tolen;
for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
*to = *from;
++to;
++tolen;
} else {
sprintf(to, "%%%02x", (int) *from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}
wrap.c
#ifndef __WRAP_H_
#define __WRAP_H_
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);
int tcp4bind(short port,const char *IP);
#endif
wrap.c
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
//绑定错误显示和退出
void perr_exit(const char *s)
{
perror(s);
exit(-1);
}
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
int n;
again:
if ((n = accept(fd, sa, salenptr)) < 0) {
if ((errno == ECONNABORTED) || (errno == EINTR))//ECONNABORTED 代表连接失败 ETINTR 代表被信号打断
goto again;
else
perr_exit("accept error");
}
return n;
}
int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = bind(fd, sa, salen)) < 0)
perr_exit("bind error");
return n;
}
int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
int n;
if ((n = connect(fd, sa, salen)) < 0)
perr_exit("connect error");
return n;
}
int Listen(int fd, int backlog)
{
int n;
if ((n = listen(fd, backlog)) < 0)
perr_exit("listen error");
return n;
}
int Socket(int family, int type, int protocol)
{
int n;
if ((n = socket(family, type, protocol)) < 0)
perr_exit("socket error");
return n;
}
ssize_t Read(int fd, void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = read(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)//被信号打断应该继续读
goto again;
else
return -1;
}
return n;
}
ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
ssize_t n;
again:
if ( (n = write(fd, ptr, nbytes)) == -1) {
if (errno == EINTR)
goto again;
else
return -1;
}
return n;
}
int Close(int fd)
{
int n;
if ((n = close(fd)) == -1)
perr_exit("close error");
return n;
}
/*参三: 应该读取的字节数*/
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft; //usigned int 剩余未读取的字节数
ssize_t nread; //int 实际读到的字节数
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;
else
return -1;
} else if (nread == 0)
break;
nleft -= nread;//防止一次数据没有读完
ptr += nread;//指针需要向后移动
}
return n - nleft;
}
ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;
else
return -1;
}
nleft -= nwritten;
ptr += nwritten;
}
return n;
}
static ssize_t my_read(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100];//定义了100的缓冲区
if (read_cnt <= 0) {
again:
//使用缓冲区可以避免多次从底层缓冲读取数据--为了提高效率
if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {
if (errno == EINTR)
goto again;
return -1;
} else if (read_cnt == 0)
return 0;
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;//从缓冲区取数据
return 1;
}
//读取一行
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for (n = 1; n < maxlen; n++) {
if ( (rc = my_read(fd, &c)) == 1) {
*ptr++ = c;
if (c == '\n')//代表任务完成
break;
} else if (rc == 0) {//对端关闭
*ptr = 0;//0 = '\0'
return n - 1;
} else
return -1;
}
*ptr = 0;
return n;
}
int tcp4bind(short port,const char *IP)
{
struct sockaddr_in serv_addr;
int lfd = Socket(AF_INET,SOCK_STREAM,0);
bzero(&serv_addr,sizeof(serv_addr));//清空serv_addr地址 对比 memset()
if(IP == NULL){
//如果这样使用 0.0.0.0,任意ip将可以连接
serv_addr.sin_addr.s_addr = INADDR_ANY;
}else{
if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
perror(IP);//转换失败
exit(1);
}
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
Bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
return lfd;
}
webserver.c
//web服务端程序--使用epoll模型
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <string.h>
#include <signal.h>
#include <dirent.h>
#include "pub.h"
#include "wrap.h"
int http_request(int cfd, int epfd);
int main()
{
//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接,
//则web服务器就会收到SIGPIPE信号
struct sigaction act;
act.sa_handler = SIG_IGN;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
sigaction(SIGPIPE, &act, NULL);
//改变当前进程的工作目录
char path[255] = {0};
sprintf(path, "%s/%s", getenv("HOME"), "webpath");
chdir(path);
//创建socket--设置端口复用---bind
int lfd = tcp4bind(9999, NULL);
//设置监听
Listen(lfd, 128);
//创建epoll树
int epfd = epoll_create(1024);
if(epfd<0)
{
perror("epoll_create error");
close(lfd);
return -1;
}
//将监听文件描述符lfd上树
struct epoll_event ev;
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
int i;
int cfd;
int nready;
int sockfd;
struct epoll_event events[1024];
while(1)
{
//等待事件发生
nready = epoll_wait(epfd, events, 1024, -1);
if(nready<0)
{
if(errno==EINTR)
{
continue;
}
break;
}
for(i=0; i<nready; i++)
{
sockfd = events[i].data.fd;
//有客户端连接请求
if(sockfd==lfd)
{
//接受新的客户端连接
cfd = Accept(lfd, NULL, NULL);
//设置cfd为非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
//将新的cfd上树
ev.data.fd = cfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
}
else
{
//有客户端数据发来
http_request(sockfd, epfd);
}
}
}
}
int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{
char buf[1024] = {0};
sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
sprintf(buf+strlen(buf), "Content-Type:%s\r\n", fileType);
if(len>0)
{
sprintf(buf+strlen(buf), "Content-Length:%d\r\n", len);
}
strcat(buf, "\r\n");
Write(cfd, buf, strlen(buf));
return 0;
}
int send_file(int cfd, char *fileName)
{
//打开文件
int fd = open(fileName, O_RDONLY);
if(fd<0)
{
perror("open error");
return -1;
}
//循环读文件, 然后发送
int n;
char buf[1024];
while(1)
{
memset(buf, 0x00, sizeof(buf));
n = read(fd, buf, sizeof(buf));
if(n<=0)
{
break;
}
else
{
Write(cfd, buf, n);
}
}
}
int http_request(int cfd, int epfd)
{
int n;
char buf[1024];
//读取请求行数据, 分析出要请求的资源文件名
memset(buf, 0x00, sizeof(buf));
n = Readline(cfd, buf, sizeof(buf));
if(n<=0)
{
//printf("read error or client closed, n==[%d]\n", n);
//关闭连接
close(cfd);
//将文件描述符从epoll树上删除
epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
return -1;
}
printf("buf==[%s]\n", buf);
//GET /hanzi.c HTTP/1.1
char reqType[16] = {0};
char fileName[255] = {0};
char protocal[16] = {0};
sscanf(buf, "%[^ ] %[^ ] %[^ \r\n]", reqType, fileName, protocal);
//printf("[%s]\n", reqType);
printf("--[%s]--\n", fileName);
//printf("[%s]\n", protocal);
char *pFile = fileName;
if(strlen(fileName)<=1)
{
strcpy(pFile, "./");
}
else
{
pFile = fileName+1;
}
//转换汉字编码
strdecode(pFile, pFile);
printf("[%s]\n", pFile);
//循环读取完剩余的数据,避免产生粘包
while((n=Readline(cfd, buf, sizeof(buf)))>0);
//判断文件是否存在
struct stat st;
if(stat(pFile, &st)<0)
{
printf("file not exist\n");
//发送头部信息
send_header(cfd, "404", "NOT FOUND", get_mime_type(".html"), 0);
//发送文件内容
send_file(cfd, "error.html");
}
else //若文件存在
{
//判断文件类型
//普通文件
if(S_ISREG(st.st_mode))
{
printf("file exist\n");
//发送头部信息
send_header(cfd, "200", "OK", get_mime_type(pFile), st.st_size);
//发送文件内容
send_file(cfd, pFile);
}
//目录文件
else if(S_ISDIR(st.st_mode))
{
printf("目录文件\n");
char buffer[1024];
//发送头部信息
send_header(cfd, "200", "OK", get_mime_type(".html"), 0);
//发送html文件头部
send_file(cfd, "html/dir_header.html");
//文件列表信息
struct dirent **namelist;
int num;
num = scandir(pFile, &namelist, NULL, alphasort);
if (num < 0)
{
perror("scandir");
close(cfd);
epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
return -1;
}
else
{
while (num--)
{
printf("%s\n", namelist[num]->d_name);
memset(buffer, 0x00, sizeof(buffer));
if(namelist[num]->d_type==DT_DIR)
{
sprintf(buffer, "<li><a href=%s/>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
}
else
{
sprintf(buffer, "<li><a href=%s>%s</a></li>", namelist[num]->d_name, namelist[num]->d_name);
}
free(namelist[num]);
Write(cfd, buffer, strlen(buffer));
}
free(namelist);
}
//发送html尾部
sleep(10);
send_file(cfd, "html/dir_tail.html");
}
}
return 0;
}
//通过libevent编写的web服务器
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include "pub.h"
#include <event.h>
#include <event2/listener.h>
#include <dirent.h>
#define _WORK_DIR_ "%s/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"
int copy_header(struct bufferevent *bev,int op,char *msg,char *filetype,long filesize)
{
char buf[4096]={0};
sprintf(buf,"HTTP/1.1 %d %s\r\n",op,msg);
sprintf(buf,"%sContent-Type: %s\r\n",buf,filetype);
if(filesize >= 0){
sprintf(buf,"%sContent-Length:%ld\r\n",buf,filesize);
}
strcat(buf,"\r\n");
bufferevent_write(bev,buf,strlen(buf));
return 0;
}
int copy_file(struct bufferevent *bev,const char *strFile)
{
int fd = open(strFile,O_RDONLY);
char buf[1024]={0};
int ret;
while( (ret = read(fd,buf,sizeof(buf))) > 0 ){
bufferevent_write(bev,buf,ret);
}
close(fd);
return 0;
}
//发送目录,实际上组织一个html页面发给客户端,目录的内容作为列表显示
int send_dir(struct bufferevent *bev,const char *strPath)
{
//需要拼出来一个html页面发送给客户端
copy_file(bev,_DIR_PREFIX_FILE_);
//send dir info
DIR *dir = opendir(strPath);
if(dir == NULL){
perror("opendir err");
return -1;
}
char bufline[1024]={0};
struct dirent *dent = NULL;
while( (dent= readdir(dir) ) ){
struct stat sb;
stat(dent->d_name,&sb);
if(dent->d_type == DT_DIR){
//目录文件 特殊处理
//格式 <a href="dirname/">dirname</a><p>size</p><p>time</p></br>
memset(bufline,0x00,sizeof(bufline));
sprintf(bufline,"<li><a href='%s/'>%32s</a> %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
bufferevent_write(bev,bufline,strlen(bufline));
}
else if(dent->d_type == DT_REG){
//普通文件 直接显示列表即可
memset(bufline,0x00,sizeof(bufline));
sprintf(bufline,"<li><a href='%s'>%32s</a> %8ld</li>",dent->d_name,dent->d_name,sb.st_size);
bufferevent_write(bev,bufline,strlen(bufline));
}
}
closedir(dir);
copy_file(bev,_DIR_TAIL_FILE_);
//bufferevent_free(bev);
return 0;
}
int http_request(struct bufferevent *bev,char *path)
{
strdecode(path, path);//将中文问题转码成utf-8格式的字符串
char *strPath = path;
if(strcmp(strPath,"/") == 0 || strcmp(strPath,"/.") == 0){
strPath = "./";
}
else{
strPath = path+1;
}
struct stat sb;
if(stat(strPath,&sb) < 0){
//不存在 ,给404页面
copy_header(bev,404,"NOT FOUND",get_mime_type("error.html"),-1);
copy_file(bev,"error.html");
return -1;
}
if(S_ISDIR(sb.st_mode)){
//处理目录
copy_header(bev,200,"OK",get_mime_type("ww.html"),sb.st_size);
send_dir(bev,strPath);
}
if(S_ISREG(sb.st_mode)){
//处理文件
//写头
copy_header(bev,200,"OK",get_mime_type(strPath),sb.st_size);
//写文件内容
copy_file(bev,strPath);
}
return 0;
}
void read_cb(struct bufferevent *bev, void *ctx)
{
char buf[256]={0};
char method[10],path[256],protocol[10];
int ret = bufferevent_read(bev, buf, sizeof(buf));
if(ret > 0){
sscanf(buf,"%[^ ] %[^ ] %[^ \r\n]",method,path,protocol);
if(strcasecmp(method,"get") == 0){
//处理客户端的请求
char bufline[256];
write(STDOUT_FILENO,buf,ret);
//确保数据读完
while( (ret = bufferevent_read(bev, bufline, sizeof(bufline)) ) > 0){
write(STDOUT_FILENO,bufline,ret);
}
http_request(bev,path);//处理请求
}
}
}
void bevent_cb(struct bufferevent *bev, short what, void *ctx)
{
if(what & BEV_EVENT_EOF){//客户端关闭
printf("client closed\n");
bufferevent_free(bev);
}
else if(what & BEV_EVENT_ERROR){
printf("err to client closed\n");
bufferevent_free(bev);
}
else if(what & BEV_EVENT_CONNECTED){//连接成功
printf("client connect ok\n");
}
}
void listen_cb(struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *addr, int socklen, void *arg)
{
//定义与客户端通信的bufferevent
struct event_base *base = (struct event_base *)arg;
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev,read_cb,NULL,bevent_cb,base);//设置回调
bufferevent_enable(bev,EV_READ|EV_WRITE);//启用读和写
}
int main(int argc,char *argv[])
{
char workdir[256] = {0};
sprintf(workdir,_WORK_DIR_,getenv("HOME"));//HOME=/home/itheima
chdir(workdir);
struct event_base *base = event_base_new();//创建根节点
struct sockaddr_in serv;
serv.sin_family = AF_INET;
serv.sin_port = htons(9999);
serv.sin_addr.s_addr = htonl(INADDR_ANY);
struct evconnlistener * listener =evconnlistener_new_bind(base,
listen_cb, base, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, -1,
(struct sockaddr *)&serv, sizeof(serv));//连接监听器
event_base_dispatch(base);//循环
event_base_free(base); //释放根节点
evconnlistener_free(listener);//释放链接监听器
return 0;
}