Linux网络编程 第八天
目录
学习目标
内容回顾
完善网页服务器
中文乱码问题
服务器中断处理
读取目录文件
BS模式示意图
Web服务器开发流程图
日志服务器
Libevent下的网页服务器
学习目标
第八天主要是在第七天的基础上,完善网页服务器的设计,学习日志服务器以及libevent下的网页版服务器开发。
内容回顾
网络编程部分整体回顾:
1 协议的概念
2 OSI7层模型: 物数网传会表应
3 TCP/IP四层模型: 应用层 传输层 网络层 数据链路层
4 几种常见的协议格式, TCP UDP ARP IP
5 socket API编程:
网络字节序 主机字节序
字节序常用到的函数: htons htonl ntohs ntohl
IP地址转换函数: inet_pton inet_ntop
INADDR_ANY
socket API常用的函数:
socket bind listen accept connect read | recv send|write close setsockopt
编写简单版本的服务端程序和客户端程序的流程:
5 三次握手和四次挥手 滑动窗口
TCP 状态转换图
半关闭: close和shutdown的区别
心跳包
同步和异步
阻塞和非阻塞
6 高并发服务器模型:
多进程版本
多线程版本
多路IO复用技术: select poll epoll (ET LT 边缘非阻塞模式)
epoll反应堆
线程池
UDP通信
本地socket通信
第三方库: libevent
事件驱动, 和epoll反应堆一样
普通事件
bufferevent
连接监听器
7 web服务端开发:
html语法
http协议: 请求消息格式 响应消息格式
web服务端开发流程:
libevent作为web服务框架的流程
完善网页服务器
中文乱码问题
中文字符转换问题
如 苦瓜 %E8%8B%A6%E7%93%9C
苦的编码为 E8 8B A6
瓜的编码为 E7 93 9C
解析中文需要将%去除之后,获取当前字符16进制大小后赋值给char类型 这样获取的就是实际的16进制大小 而不能直接把字符赋值给char类型
如%E7 ———> (E- 'A' + 10) * 16 + 7 以此类推
处理代码如下:
// 下面的函数第二天使用
/ 这里的内容是处理%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进制的E8from += 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';
}
服务器中断处理
如果发送过大文件时,如果传输过程中文件没有传输完成时,关闭浏览器,那么服务器会得到一个信号,如同管道一般,如果一边在写的时候将管道关闭了,那么会收到信号从而中断进程
//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, //则web服务器就会收到SIGPIPE信号 man 7 signal查看信号详情 默认处理会关闭进程//如果不做控制会导致服务器自动关闭 故此有两种方法可以解决 第一种是默认忽略该信号 第二种是收到信号时不做任何处理或者阻塞信号//sigemptyset将某个信号清0 sigaction为信号捕捉函数 捕获SIGPIPE信号后执行act.sa_handler = SIG_IGN;忽略改信号//sa_handler为处理函数struct sigaction act;act.sa_handler = SIG_IGN;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGPIPE, &act, NULL);//signal (SIGPIPE, SIG_IGN);屏蔽SIGPIPE信号 SIG_IGN表示忽略的信号
读取目录文件
// alphasort内部按字母排序函数 也可自己定义函数
num = scandir(pFile, &namelist, NULL, alphasort);//未获取到文件 关闭文件描述符
if (num < 0)
{perror("scandir");close(cfd);epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);exit(EXIT_FAILURE);
}
else
{
while (num--)
{printf("%s\\n", namelist[num]->d_name);// 拼凑中间列表内容数据memset(buffer, 0x00, sizeof(buffer));// 如果为目录则发送 %s/ 普通文件则是/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, sizeof(buffer));
}free(namelist);
}// 发送html尾数据send_file(cfd, "html/dir_tail.html");
}
BS模式示意图
Web服务器开发流程图
完整代码:这里不对pub.c 和wrap.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"/*
html列表格式
<html><head><title>my first html page</title><meta http-equiv="content-Type" content="text/html; charset=utf8"></head><body><ul><li><a href=test.log> test.log </a></li><li><a href=test.log> test.log </a></li><li><a href=test.log> test.log </a></li></ul></body>
</html>
*//*
中文字符转换问题
如 苦瓜 %E8%8B%A6%E7%93%9C
苦的编码为 E8 8B A6
瓜的编码为 E7 93 9C
解析中文需要将%去除之后,获取当前字符16进制大小后赋值给char类型 这样获取的就是实际的16进制大小 而不能直接把字符赋值给char类型
如%E7 ———> (E- 'A' + 10) * 16 + 7 以此类推
*/// 处理http请求函数
int http_request(int cfd, int epfd);// 发送头部信息函数
int send_header(int cfd, char *code, char *msg, char *fileType, int len);// //kill -l可以查看信号
// void signalhander(int signo)
// {
// printf("signo = %d\\n", signo);
// }// 发送文件内存函数
int send_file(int cfd, char *fileName);
int main()
{//验证收到的信号//signal(SIGPIPE, signalhander);//若web服务器给浏览器发送数据的时候, 浏览器已经关闭连接, //则web服务器就会收到SIGPIPE信号 man 7 signal查看信号详情 默认处理会关闭进程//如果不做控制会导致服务器自动关闭 故此有两种方法可以解决 第一种是默认忽略该信号 第二种是收到信号时不做任何处理或者阻塞信号//sigemptyset将某个信号清0 sigaction为信号捕捉函数 捕获SIGPIPE信号后执行act.sa_handler = SIG_IGN;忽略改信号//sa_handler为处理函数struct sigaction act;act.sa_handler = SIG_IGN;sigemptyset(&act.sa_mask);act.sa_flags = 0;sigaction(SIGPIPE, &act, NULL);//signal (SIGPIPE, SIG_IGN);屏蔽SIGPIPE信号 SIG_IGN表示忽略的信号// 改变文件运行目录 将目录修改为具有文件的目录下char path[255] = {0};sprintf(path, "%s/%s", getenv("HOME"), "test3/网络编程/webpath");chdir(path);// 创建socketint lfd = tcp4bind(9999, NULL);// 设置监听Listen(lfd, 128);// 创建epoll树int epfd = epoll_create(1024);if (epfd < 0){perror("epoll create error");close(lfd);return -1;}// 将监听文件描述符上树struct epoll_event ev;ev.data.fd = lfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);int nready;struct epoll_event events[1024];int cfd;int socketfd;while (1){nready = epoll_wait(epfd, events, 1024, -1);printf("nready = %d\\n", nready);if (nready < 0){if (errno == EINTR){continue;}break;}for (int i = 0; i < nready; i++){socketfd = events[i].data.fd;// 如果新的客户端到来,那么返回的fd等于监听描述符if (socketfd == lfd){// 开始接收数据cfd = Accept(lfd, NULL, NULL);// 设置cfd为非阻塞int flag = fcntl(cfd, F_GETFL);flag |= O_NONBLOCK;fcntl(cfd, F_SETFL, flag);// 将描述符上树ev.data.fd = cfd;ev.events = EPOLLIN;epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);}else{// 有新的数据过来http_request(socketfd, epfd);}}}
}// 发送头部信息函数
/*
HTTP/1.1 200 OK
Content-Type:text/plain;charset=iso-8859-1(必选项) 告诉服务器发送的什么类型
Content-Length:32 //要么不传 传了就要传对
*/
int send_header(int cfd, char *code, char *msg, char *fileType, int len)
{char buf[1024] = {0};//\\r\\n换行 HTTP/1.1 200 OKsprintf(buf, "HTTP/1.1 %s %s\\r\\n", code, msg);// Content-Type:text/plain;charset=iso-8859-1sprintf(buf + strlen(buf), "Content-Type:%s\\r\\n", fileType);// Content-Length:32if (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){printf("file open error\\n");return -1;}// 循环读取文件char buf[1024];int n;while (1){memset(buf, 0x00, sizeof(buf));n = read(fd, buf, sizeof(buf));if (n <= 0){close(fd);break;}else{Write(cfd, buf, n);}}
}// 处理http请求函数
int http_request(int cfd, int epfd)
{int n;char buf[1024];memset(buf, 0x00, sizeof(buf));// 按行读取数据 读取第一行数据// 里面调用了read函数对信息继续读取n = Readline(cfd, buf, sizeof(buf));if (n <= 0){// 关闭文件描述符close(cfd);// 下epoll树epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);return -1;}// 接收第一行请求头信息// //Get /hanzi.c HTTP/1.1// %[^ ] %[^ ] %[^/r/n] 截取之后的值分别为 //Get /hanzi.c HTTP/1.1// sccanf() 第一个参数 原字符串 "%[^ ]" 表示截取第一个出现 的字符串char reqType[16] = {0};char filename[256] = {0};char httppro[16] = {0};sscanf(buf, "%[^ ] %[^ ] %[^/r/n]", reqType, filename, httppro);// printf("reqType = %s\\n", reqType);//printf("filename = %s\\n", filename);// printf("httppro = %s\\n", httppro);// 如果没有输入或者输入/ 那么访问根目录char *pFile = filename;//转换汉字编码strdecode(pFile, pFile);printf("filename = %s\\n", pFile);if (strlen(filename) <= 1){strcpy(pFile, "./");}else{// 因为上面获取的文件名具有 / 因此需要对/进行去除再访问pFile = filename + 1;}// 将剩下的数据读完 否则可能会出现粘包 如果数据不读完 下次会继续发送// 因为这里是阻塞函数 所以要将cfd设置为非阻塞状态 否则会阻塞在该处while (n = Readline(cfd, buf, sizeof(buf)) > 0){};// 判断文件是否存在struct stat st;// 文件不存在if (stat(pFile, &st) < 0){printf("file is not exit\\n");// 发送头部信息 get_mime_type获取文件类型 error.html为错误文件信息send_header(cfd, "404", "Not Found", get_mime_type(".html"), 0);// 发送内容send_file(cfd, "error.html");}else // 文件存在 man 2 stat查看文件的属性 包括文件类型和大小等信息{// 普通文件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)){// 使用strdir函数获取文件目录下的文件struct dirent namelist;int num;char buffer[1024];// 发送头部信息send_header(cfd, "200", "OK", get_mime_type(".html"), 0);// 发送html头数据send_file(cfd, "html/dir_header.html");// alphasort内部按字母排序函数 也可自己定义函数num = scandir(pFile, &namelist, NULL, alphasort);//未获取到文件 关闭文件描述符if (num < 0){perror("scandir");close(cfd);epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);exit(EXIT_FAILURE);}else{while (num--){printf("%s\\n", namelist[num]->d_name);// 拼凑中间列表内容数据memset(buffer, 0x00, sizeof(buffer));// 如果为目录则发送 %s/ 普通文件则是/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, sizeof(buffer));}free(namelist);}// 发送html尾数据send_file(cfd, "html/dir_tail.html");}}
}
日志服务器
写日志就是写文件 主要记录错误信息、警告信息等
日志具有级别 日志级别越高 写的次数越低
日志一般书写在文件里面,反复的读取文件是非常消耗资源的,因此写日志的时候需要进行轮播地写入文件之中
避免资源的浪费
日志服务器一般公式都是写好的,我们只需学会如何使用即可。
log.h
#ifndef LOG_H
#define LOG_H
/* */#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <math.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>/*
写日志就是写文件 主要记录错误信息、警告信息等
日志具有级别 日志级别越高 写的次数越低
日志一般书写在文件里面,反复的读取文件是非常消耗资源的,因此写日志的时候需要进行轮播地写入文件之中
避免资源的浪费
*/#define LOG_PROCNAME 0x00000001 /* msglog 输出日志时打印程序名 */
#define LOG_PID 0x00000010 /* msglog 输出日志时打印进程 PID */
#define LOG_PERROR 0x00000100 /* msglog 是否把告警内容输出到stderr */
#define NLO_PROCNAME 0x11111110 /* msglog 不输出程序名 */
#define NLO_PID 0x11111101 /* msglog 不输出进程 PID */
#define NLO_PERROR 0x11111011 /* msglog 不输出告警到stderr *///日志级别
#define MSG_INFO 0x00000001 /* msglog 输出到告警日志文件中 */
#define MSG_WARN 0x00000010 /* msglog 输出到普通日志文件中 */
#define MSG_BOTH MSG_INFO|MSG_WARN /* msglog 输出到普通和告警日志文件中 *///文件存储日期及格式信息
#define LOG_MESSAGE_FILE "/home/itheima/log/tcpsvr" /* 系统程序运行日志信息文件 */
#define LOG_MESSAGE_DFMT "%m-%d %H:%M:%S" /* 日志信息时间格式字串 */
#define LOG_POSTFIX_MESS "%y%m" /* 程序运行日志信息文件后缀 */
#define LOG_WARNING_FILE "/home/itheima/log/log.sys_warn" /* 系统程序运行告警日志文件 */
#define LOG_WARNING_DFMT "%m-%d %H:%M:%S" /* 告警信息时间格式字串 */
#define LOG_POSTFIX_WARN "" /* 程序运行告警日志文件后缀 *//* */
int msglog(int mtype, char *outfmt, ...);//写日志函数
int msgLogFormat(int mopt, char *mdfmt, int wopt, char *wdfmt);//对日志格式化
int msgLogOpen(char *ident, char *mpre, char *mdate, char *wpre, char *wdate);//打开日志文件
int msgLogClose(void);//关闭日志文件long begusec_process(void); /* 设置开始时间 0=ok */
long getusec_process(void); /* 返回usecond 从 begusec_process历时 */int msgInit(char *pName);
#endif
/* */
/* */
/* */
/* */
log.c
#include "log.h"static int msgopt, wanopt;
static char msgdatefmt[100], wandatefmt[100], ident_name[100];
static struct timeval be_stime;
static FILE *msgfile = NULL, *wanfile = NULL;
/* */
/* */
/* */
//日志文件初始化,也可以通过msgLogOpen进行初始化
int msgInit(char *pName)
{if (msgLogOpen(pName, LOG_MESSAGE_FILE, LOG_POSTFIX_MESS,LOG_WARNING_FILE, LOG_POSTFIX_WARN) == 0){msgLogFormat(LOG_PROCNAME|LOG_PID, LOG_MESSAGE_DFMT, LOG_PROCNAME|LOG_PID, LOG_WARNING_DFMT);}else {printf("can not create log!\\n");return -1;}return 0;
}
/* */
//参数1 文件名
//参数2 系统程序运行日志信息文件路径
//参数3 程序运行日志信息文件后缀
//参数4 系统程序运行告警日志文件路径
//参数5 程序运行告警日志文件后缀
int msgLogOpen(char *ident, char *mpre, char *mdate, char *wpre, char *wdate) /* 打开日志 */
{time_t now_time;char openfilename[200], timestring[100];//获取当前的时间now_time = time(NULL);if ((!msgfile) && (*mpre)){strcpy(openfilename, mpre);if (*mdate){strftime(timestring, sizeof(timestring), mdate, localtime(&now_time));strcat(openfilename, ".");strcat(openfilename, timestring);}if ((msgfile = fopen(openfilename, "a+b")) == NULL){ /* 如果没有应该把目录建上 */printf("openfilename=%s\\n", openfilename);return -1;}setlinebuf(msgfile);}if ((!wanfile) && (*wpre)){strcpy(openfilename, wpre);if (*wdate){strftime(timestring, sizeof(timestring), wdate, localtime(&now_time));strcat(openfilename, ".");strcat(openfilename, timestring);}if ((wanfile = fopen(openfilename, "a+b")) == NULL){return -1;}setlinebuf(wanfile);}if ((msgfile) && (wanfile)){if (*ident){strcpy(ident_name, ident);} else {ident_name[0] = '\\0';}msgopt = LOG_PROCNAME|LOG_PID; /* 设置默认信息输出信息选项 */wanopt = LOG_PROCNAME|LOG_PID; /* 设置默认告警输出信息选项 */strcpy(msgdatefmt, "%m-%d %H:%M:%S"); /* 默认信息输出时间格式 MM-DD HH24:MI:SS */strcpy(wandatefmt, "%m-%d %H:%M:%S"); /* 默认告警输出时间格式 MM-DD HH24:MI:SS */msglog(MSG_INFO,"File is msgfile=[%d],wanfile=[%d].",fileno(msgfile),fileno(wanfile));return 0;} else {return -1;}
}
/* */
/* 自定义日志输出函数系列,可以按普通信息及告警信息分类输出程序日志 */
// 参数为可变参数
int msglog(int mtype, char *outfmt, ...)
{time_t now_time;va_list ap;//变参的列表char logprefix[1024], tmpstring[1024];time(&now_time);if (mtype & MSG_INFO){ /*strftime会将localtime(&now_time)按照msgdatefmt格式,输出到logprefix.*/strftime(logprefix, sizeof(logprefix), msgdatefmt, localtime(&now_time));strcat(logprefix, " ");/*static int msgopt,wanopt;*/if (msgopt&LOG_PROCNAME){strcat(logprefix, ident_name);strcat(logprefix, " ");}if (msgopt&LOG_PID){sprintf(tmpstring, "[%6d]", getpid());strcat(logprefix, tmpstring);}fprintf(msgfile, "%s: ", logprefix);//读取可变的参数 将可变的参数写到msgfile中va_start(ap, outfmt);vfprintf(msgfile, outfmt, ap);va_end(ap);fprintf(msgfile, "\\n");}if (mtype & MSG_WARN){strftime(logprefix, sizeof(logprefix), wandatefmt, localtime(&now_time));strcat(logprefix, " ");/*#define LOG_PROCNAME 0x00000001*/ /* msglog 输出日志时打印程序名 */if (wanopt & LOG_PROCNAME){strcat(logprefix, ident_name);strcat(logprefix, " ");}if (wanopt & LOG_PID){sprintf(tmpstring, "[%6d]", getpid());strcat(logprefix, tmpstring);}fprintf(wanfile, "%s: ", logprefix);va_start(ap, outfmt);vfprintf(wanfile, outfmt, ap);va_end(ap);fprintf(wanfile, "\\n");if (wanopt & LOG_PERROR){fprintf(stderr, "%s: ", logprefix);va_start(ap, outfmt);vfprintf(stderr, outfmt, ap);va_end(ap);fprintf(stderr, "\\n");}}return 0;
}
/* */
int msgLogFormat(int mopt, char *mdfmt, int wopt, char *wdfmt) /* 设置日志格式及选项 */
{if (mopt >= 0){msgopt = mopt;} else {msgopt = msgopt & mopt;}if (wopt >= 0){wanopt = wopt;} else {wanopt = wanopt & wopt;}if (*mdfmt) strcpy(msgdatefmt, mdfmt);if (*wdfmt) strcpy(wandatefmt, wdfmt);return 0;
}
/* */
int msgLogClose(void) /* 关闭日志文件 */
{if (msgfile) fclose(msgfile);if (wanfile) fclose(wanfile);return 0;
}
/* */
long begusec_process(void) /* 设置开始时间 0=ok */
{gettimeofday(&be_stime,NULL);return 0;
}
/* */
long getusec_process(void) /* 返回usecond 从 begusec_process历时 */
{struct timeval ed_stime;gettimeofday(&ed_stime,NULL);return ((ed_stime.tv_sec-be_stime.tv_sec)*1000000+ed_stime.tv_usec-be_stime.tv_usec);
}
/* */
/* */
/* */
/* */
测试代码如下
#include "log.h"int main(int argc,char *argv[])
{//获取进程IDchar *pName = argv[0];//将. ..忽视pName +=2;printf("pName is %s\\n",pName);//初始化名称msgInit("log_demo");//打印Log信息msglog(MSG_INFO,"begin run program....");sleep(2);//打印log信息msglog(MSG_BOTH,"begin to game over...%ld",time(NULL));//关闭文件msgLogClose();return 0;
}
Libevent下的网页服务器
libevent下的服务器和epoll下的类似,相对来说要简易一些,并且可以实现跨平台,内部库函数已经封装好,使用起来更加地稳定。
代码如下:
//通过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/test3/网络编程/webpath"
#define _DIR_PREFIX_FILE_ "html/dir_header.html"
#define _DIR_TAIL_FILE_ "html/dir_tail.html"/*
libevent
两个缓冲区 三个回调函数
gcc -o event_wb event_wb.c pub.c wrap.c -levent
*/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");char buf[1024] = {0};//\\r\\n换行 HTTP/1.1 200 OKsprintf(buf, "HTTP/1.1 %d %s\\r\\n", op, msg);// Content-Type:text/plain;charset=iso-8859-1sprintf(buf + strlen(buf), "Content-Type:%s\\r\\n", filetype);// Content-Length:32if (filesize > 0){sprintf(buf + strlen(buf), "Content-Length:%ld\\r\\n", 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)
{//将中文问题转码成utf-8格式的字符串strdecode(path, path);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)
{//定义与客户端通信的buffereventstruct 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;
}