Linux网络编程 第八天

news2024/9/22 9:48:16

目录

学习目标

内容回顾

完善网页服务器

中文乱码问题

 服务器中断处理

读取目录文件

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/440525.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL中distinct和group by性能比较

distinc的使用 用法 select distinct columns from table_name where where_conditions;示例&#xff1a; DISTINCT 用于返回唯一不同的值&#xff08;即去重后的值&#xff09; &#xff0c;使用时需要放在查询语句中第一个查询字段前使用。如果列有NULL值&#xff0c;会将所…

C语言/C++随机数生成,程序运行时间计时器(含高精度计时器),包括Windows环境与Linux环境

&#x1f38a;【数据结构与算法】专题正在持续更新中&#xff0c;各种数据结构的创建原理与运用✨&#xff0c;经典算法的解析✨都在这儿&#xff0c;欢迎大家前往订阅本专题&#xff0c;获取更多详细信息哦&#x1f38f;&#x1f38f;&#x1f38f; &#x1fa94;本系列专栏 -…

工厂模式概述

通常有三种形态: 简单工厂模式&#xff0c;不属于23种设计模式之一 工厂方法模式&#xff0c;是23种设计模式之一 抽象工厂模式&#xff0c;是23种设计模式之一 1.简单工厂模式是工厂模式的一种特殊实现&#xff0c;又被称为静态工厂方法模式 2.简单工厂模式解决的问题:客户端不…

【Verilog HDL】FPGA-Verilog文件的基本结构

&#x1f389;欢迎来到FPGA专栏~Verilog文件的基本结构 ☆* o(≧▽≦)o *☆嗨~我是小夏与酒&#x1f379; ✨博客主页&#xff1a;小夏与酒的博客 &#x1f388;该系列文章专栏&#xff1a;FPGA学习之旅 文章作者技术和水平有限&#xff0c;如果文中出现错误&#xff0c;希望大…

SSM框架MyBatis 三种分页查询 PageHlper的使用以及五个参数的简单解释

SSM框架MyBatis 三种简单的分页查询 1. 基础分页查询&#xff08;环境在第一天的配置中有&#xff09; mapper也就是dao //查询总数Select("select count(*) from book;")int selectCount();//分页查询Select("select * from book limit #{currpage},#{size}&q…

windows快捷键汇总

Windows 系统中有很多常用的快捷键&#xff0c;这些快捷键可以帮助我们快速完成一些操作&#xff0c;提高我们的工作效率。下面是一些使用 Windows 快捷键的好处和长期利弊&#xff1a; 好处&#xff1a; 可以快速完成一些操作&#xff0c;提高工作效率。可以让我们的工作更加…

2023年RHCE第二次作业

1.配置ntp时间服务器&#xff0c;确保客户端主机能和服务主机同步时间 2.配置ssh免密登陆&#xff0c;能够通过客户端主机通过redhat用户和服务端主机基于公钥验证方式进行远程连接 1配置Chrony服务器 先下载chrony--------dnf install -y chrony 查看和配置chrony.conf文件 …

Centos7 安装mysql 8.0.32版本(解压glibc版本)

Centos 7 安装 MySQL 8.0.32 glibc 版本总结 Centos7中安装MySQL服务时&#xff0c;首先需要卸载掉mariadb&#xff0c;mariadb可能会与MySQL产生冲突。 1、卸载mariadb 查找mariadb是否已经安装&#xff08;默认已经安装&#xff09; rpm -qa | grep mariadb接下来将查找到…

QWidget改变背景图的方法和坑

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、添加背景图资源文件二、使用 QPalette三、重写paintEvent() 函数四、使用QT的设计师界面总结 前言 本篇文章将讲解QWidget改变背景图的方法和会遇到的问题…

Flutter插件开发-(基础篇)

在开发flutter项目的时分通常会运用一些三方的的packages或许plugin&#xff0c;二者的区别&#xff1a;packages主要是包括的Dart代码块&#xff0c;而plugin则包括iOS和android的代码。 因此来说创立plugin和packages的流程是相似的&#xff0c;下面就以创立plugin为例进行展…

Spring原理学习(六):Spring实现动态代理时对jdk和cglib的选择

目录 〇、前言 一、AOP中的一些基本概念 二、两个切面的概念 三、advisor的使用 3.1 前置知识 3.2 使用步骤 四、spring对jdk和cglib的统一 〇、前言 对jdk和cglib 实现动态代理的原理不清楚的兄弟们&#xff0c;可以参考前文&#xff1a;Spring原理学习&#xff08;…

Python+Qt人脸识别职工录入管理系统

程序示例精选 PythonQt人脸识别职工录入管理系统 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<PythonQt人脸识别职工录入管理系统>>编写代码&#xff0c;代码整洁&#xff0c…

【FTP】——文件传输协议

文章目录 1.FTP简介1.1 FTP概述1.2 FTP主动模式1.3 FTP被动模式 2. 实例&#xff1a;匿名用户访问FTP服务3. 实例&#xff1a;本地用户访问FTP服务 1.FTP简介 1.1 FTP概述 FTP服务——用来传输文件的协议。 FTP服务器默认使用TCP协议的20、21端口与客户端进行通信. 20端口…

【学习笔记】Linux基础

Linux基础 一、操作系统1、什么是操作系统2、Linux操作系统3、Linux系统目录&#xff0c;Linux倒挂树型目录结构&#xff1a;4、安装Xshell与Xftp5、Linux文件操作命令6、vim文本编辑器&#xff08;1&#xff09;vim三种模式&#xff08;2&#xff09;vim重要快捷键&#xff08…

Dell Inspiron 5570电脑 Hackintosh 黑苹果efi引导文件

原文来源于黑果魏叔官网&#xff0c;转载需注明出处。&#xff08;下载请直接百度黑果魏叔&#xff09; 硬件型号驱动情况 主板Dell Inspiron 5570 处理器Intel(R) Core(TM) i7-8550U CPU 1.80GHz已驱动 内存8 GB 2400 MHz DDR4已驱动 硬盘samsung ssd 850 evo 250 go已驱…

汽车跨界还能这么玩?解锁汽车跨界新模式

跨界&#xff0c;是一种商业思维&#xff0c;是出圈方式&#xff0c;是进入流量红海的手段之一。近几年来&#xff0c;国内掀起了一股跨界风&#xff0c;“跨界增值宣传”似乎成为了品牌年轻化的必经之路&#xff0c;众多品牌通过跨界的方式实现互相补足、用户渗透&#xff0c;…

#Chrome扩展程序开发教程--01:基本概念介绍

#Chrome扩展程序开发教程--01&#xff1a;基本概念介绍 引言1、什么是扩展程序&#xff1f;2、Web技术3、Chrome 扩展程序API4、扩展程序架构 引言 本系列博客旨在带来最新的Chrome扩展程序开发入门教程。 1、什么是扩展程序&#xff1f; 通过向Chrome浏览器添加自定义特性和功…

Docker容器---网络、容器操作

Docker容器---网络、容器操作 一、docker实现原理二、docker网路模式1、Host模式2、container模式3、none模式4、bridge模式 三、自定义网络1、查看网络模式列表2、查看容器信息3、指定分配IP地址4、自定义网络固定IP 四、暴露端口五、容器端口映射1、创建端口映射 六、资源控制…

wordpress建站/demo

bidewang.co/ele 建站的另一种途径Sign in – My Account tanrenchang.atspace.cc 登录 ‹ Fashion trading platform — WordPress cPanel 是一个用于管理网站的虚拟主机控制面板。 使用 cPanel&#xff0c;可以轻松管理电子邮件帐户、数据库、FTP 用户以及与托管、设置和管…

windows10下使用minGW64 编译krita源码报错

系列文章目录 文章目录 系列文章目录前言一、错误原因二、使用步骤1.引入库 前言 collect2.exe: error: ld returned 1 exit status mingw32-make[2]: *** [plugins\color\lcms2engine\CMakeFiles\kritalcmsengine.dir\build.make:614: bin/kritalcmsengine.dll] Error 1 ming…