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)是&nbsp
 */
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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值