目录
学习目标
内容回顾
完善网页服务器
中文乱码问题
服务器中断处理
读取目录文件
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进制的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';
}
服务器中断处理
如果发送过大文件时,如果传输过程中文件没有传输完成时,关闭浏览器,那么服务器会得到一个信号,如同管道一般,如果一边在写的时候将管道关闭了,那么会收到信号从而中断进程
//若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);
// 创建socket
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;
}
// 将监听文件描述符上树
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 OK
sprintf(buf, "HTTP/1.1 %s %s\r\n", code, msg);
// Content-Type:text/plain;charset=iso-8859-1
sprintf(buf + strlen(buf), "Content-Type:%s\r\n", fileType);
// Content-Length:32
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)
{
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[])
{
//获取进程ID
char *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 OK
sprintf(buf, "HTTP/1.1 %d %s\r\n", op, msg);
// Content-Type:text/plain;charset=iso-8859-1
sprintf(buf + strlen(buf), "Content-Type:%s\r\n", filetype);
// Content-Length:32
if (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)
{
//定义与客户端通信的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;
}