linux服务器开发 2019.2.7(epoll服务端模型代码,strncasecmp函数,MSG_PEEK,scandir函数,编码解码,sscanf 函数,正则表达式,strftime 函数)

epoll服务端模型代码

 

main.c

#include 
#include 
#include 
#include "epoll_server.h"

int main(int argc, const char* argv[]){
    if(argc < 3){
        printf("eg: ./a.out port path\n");
        exit(1);
    }

    // 端口 字符串转整数
    int port = atoi(argv[1]);

    // 修改进程的工作目录, 方便后续操作
    int ret = chdir(argv[2]);
    if(ret == -1){
        perror("chdir error");
        exit(1);
    }
    
    // 启动epoll模型 
    epoll_run(port);

    return 0;
}

 

epoll_server.c

 

第一部分——初始化epoll树,初始化监听的描述符

#define MAXSIZE 2000

void epoll_run(int port){

    // 创建一个epoll树的根节点
    int epfd = epoll_create(MAXSIZE);
    if(epfd == -1){
        perror("epoll_create error");
        exit(1);
    }

    // 添加要监听的节点
    // 先添加监听lfd
    int lfd = init_listen_fd(port, epfd);

    // 委托内核检测添加到树上的节点
    struct epoll_event all[MAXSIZE];
    while(1){
        //epfd是epoll树的根节点,如果有树上某个文件描述符对应的缓冲区发生了变换,则会被拷贝到结构体数组all中
        // MAXSIZE是数组的大小, -1是一直阻塞,直到有节点发生变化的时候再发生返回,返回值ret是发生变化的文件描述符个数
        int ret = epoll_wait(epfd, all, MAXSIZE, -1);
        if(ret == -1){
            perror("epoll_wait error");
            exit(1);
        }

        // 遍历发生变化的节点
        for(int i=0; ievents & EPOLLIN)){
                // 不是读事件
                continue;
            }

            if(pev->data.fd == lfd){
                // 接受连接请求
                do_accept(lfd, epfd);
            }
            else{
                // 读数据
                do_read(pev->data.fd, epfd);
            }
        }
    }
}


//初始化监听,传入端口号和epoll书的根节点
int init_listen_fd(int port, int epfd) {

    // 创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1) {
        perror("socket error");
        exit(1);
    }

    // lfd绑定本地IP和port
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(port);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);

    // 端口复用
    int flag = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv));    //绑定
    if(ret == -1){
        perror("bind error");
        exit(1);
    }

    // 设置监听,最大是128,这里写个64是随意写的
    ret = listen(lfd, 64);
    if(ret == -1){
        perror("listen error");
        exit(1);
    }

    // lfd添加到epoll树上
    struct epoll_event ev;  //创建节点
    ev.events = EPOLLIN;
    ev.data.fd = lfd;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); //把节点挂上
    if(ret == -1){
        perror("epoll_ctl add lfd error");
        exit(1);
    }

    return lfd;
}

 

 

第二部分——接受连接请求

// 接受新连接处理
void do_accept(int lfd, int epfd){
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    int cfd = accept(lfd, (struct sockaddr*)&client, &len);
    if(cfd == -1){
        perror("accept error");
        exit(1);
    }

    // 打印客户端信息
    char ip[64] = {0};
    //inet_ntop大端整型转淀粉十进制ip地址
    printf("New Client IP: %s, Port: %d, cfd = %d\n",
           inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
           ntohs(client.sin_port), cfd);

    // 设置cfd为非阻塞(默认是阻塞的)
    int flag = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK; //非阻塞
    fcntl(cfd, F_SETFL, flag);

    // 得到的新节点挂到epoll树上
    struct epoll_event ev;
    ev.data.fd = cfd;
    // 边沿非阻塞模式
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    if(ret == -1){
        perror("epoll_ctl add cfd error");
        exit(1);
    }
}

 

 

第三部分——读数据

 

1、整体主函数

// 读数据
void do_read(int cfd, int epfd) {

	// 将浏览器发过来的数据, 读到buf中 
	char line[1024] = { 0 };
	// 读请求行
	int len = get_line(cfd, line, sizeof(line));

	//get_line调用的是recv,recv返回值为0则说明客户端断开了链接
	if (len == 0) {
		printf("客户端断开了连接...\n");
		// 关闭套接字, cfd从epoll上删除
		disconnect(cfd, epfd);
	}
	//recv失败了
	else if (len == -1) {
		perror("recv error");
		exit(1);
	}
	else {
		printf("请求行数据: %s", line);
		printf("============= 请求头 ============\n");
		// 还有数据没读完,继续读
		while (len) {
			char buf[1024] = { 0 };
			len = get_line(cfd, buf, sizeof(buf));
			printf("-----: %s", buf);
		}
		printf("============= The End ============\n");
	}

	// 请求行: get /xxx http/1.1
	// 判断是不是get请求  strncasecmp函数用于比较两个字符串,不区分大小写的比较前n个字符
	if (strncasecmp("get", line, 3) == 0) {
		// 处理http请求
		http_request(line, cfd);
		// 关闭套接字, cfd从epoll上del
		disconnect(cfd, epfd);
	}
}

用到的库函数

strncasecmp("get", line, 3)

strncasecmp函数用于比较两个字符串,不区分大小写的比较前n个字符

 

 

2、解析http请求消息的每一行内容

// 解析http请求消息的每一行内容
int get_line(int sock, char *buf, int size){
    int i = 0;
    char c = '\0';
    int n;
    //每次读一个字节,判断合理就放入缓冲区中
    while ((i < size - 1) && (c != '\n')){
        n = recv(sock, &c, 1, 0);
        if (n > 0){
            if (c == '\r'){
                //MSG_PEEK 使得recv以拷贝的方式从缓冲区中读取数据(否则读取之后,缓冲区中的数据就没了)
                //试探性的获取缓冲区中的数据量
                n = recv(sock, &c, 1, MSG_PEEK);
                //缓冲区中有数据并且结尾是 \n  ,则读取数据
                if ((n > 0) && (c == '\n')){
                    recv(sock, &c, 1, 0);
                } else {
                    c = '\n';
                }
            }
            buf[i] = c;
            i++;
        } else{
            c = '\n';
        }
    }
    buf[i] = '\0';

    //recv失败
    if (n == -1) {
        i = -1;
    }

    return i;
}

用到的库函数

recv(sock, &c, 1, MSG_PEEK);

MSG_PEEK 使得recv以拷贝的方式从缓冲区中读取数据(否则读取之后,缓冲区中的数据就没了)

 

 

第四部分——断开连接

// 断开连接的函数
void disconnect(int cfd, int epfd){
    int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    if(ret == -1){
        perror("epoll_ctl del cfd error");
        exit(1);
    }
    close(cfd);
}

 

第五部分——拆分http请求行

// http请求处理,传入request和
void http_request(const char* request, int cfd) {

	// 拆分http请求行
	//    get        /xxx        http/1.1
	char method[12], path[1024], protocol[12];
	//%[^ ]匹配遇到空格为止
	sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);

	printf("method = %s, path = %s, protocol = %s\n", method, path, protocol);

	// 转码 将不能识别的中文乱码 - > 中文
	// 解码 %23 %34 %5f
	decode_str(path, path);
	// 处理path  /xx(有可能是目录,也有可能是文件)
	// 去掉path中的/
	char* file = path + 1;
	// 如果没有指定访问的资源, 默认显示资源目录中的内容
	if (strcmp(path, "/") == 0) {
		// file的值, 资源目录的当前位置
		file = "./";
	}

	// 获取文件属性
	struct stat st;
	int ret = stat(file, &st);
	if (ret == -1) {
		// show 404
		send_respond_head(cfd, 404, "File Not Found", ".html", -1);
		send_file(cfd, "404.html");
	}

	// 判断是目录还是文件 path  /xx(有可能是目录,也有可能是文件)
	// 如果是目录
	if (S_ISDIR(st.st_mode)) {
		// 发送头信息
		send_respond_head(cfd, 200, "OK", get_file_type(".html"), -1);
		// 发送目录信息
		send_dir(cfd, file);
	}
	else if (S_ISREG(st.st_mode))
	{
		// 文件
		// 发送消息报头
		send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
		// 发送文件内容
		send_file(cfd, file);
	}
}

 

 

第六部分——发送http响应头

// 发送响应头  cfd(服务器和浏览器通讯的文件描述符),no(状态码)
//desp(对状态码的描述),type(Content-Type),len(发送的数据长度)
void send_respond_head(int cfd, int no, const char* desp, const char* type, long len) {
	char buf[1024] = { 0 };

	// 状态行 http不区分大小写
	sprintf(buf, "http/1.1 %d %s\r\n", no, desp);
	//防止buf溢出,先把里面的数据发送出去
	send(cfd, buf, strlen(buf), 0);

	// 消息报头
	sprintf(buf, "Content-Type:%s\r\n", type);
	sprintf(buf + strlen(buf), "Content-Length:%ld\r\n", len);
	send(cfd, buf, strlen(buf), 0);

	// 空行
	send(cfd, "\r\n", 2, 0);
}

 

第七部分——发送普通文件

// 发送文件
void send_file(int cfd, const char* filename){
	// 打开文件
	int fd = open(filename, O_RDONLY);
	if (fd == -1){
		// show 404
		return;
	}

	// 循环读文件
	char buf[4096] = { 0 };
	int len = 0;
	while ((len = read(fd, buf, sizeof(buf))) > 0){
		// 发送读出的数据
		send(cfd, buf, len, 0);
	}
	if (len == -1){
		perror("read file error");
		exit(1);
	}

	close(fd);
}

 

第八部分——读目录

第一种方式读目录——使用readdir读目录

    // 打开目录
	DIR* dir = opendir(dirname);
	if (dir == NULL){
		perror("opendir error");
		exit(1);
	}

	// 读目录
	struct dirent* ptr = NULL;
	while ((ptr = readdir(dir)) != NULL){
		char* name = ptr->d_name;
	}
	closedir(dir);

 

更好用的方式——使用函数scandir

#include 

int scandir(const char *dirp,                   
     struct dirent ***namelist,           
     int (*filter)(const struct dirent *), 
     int (*compar)(const struct dirent **, 
     const struct dirent **));

1、dirp

  • 当前要扫描的目录

2、namelist

指向一块地址,这块地址是scandir内部开辟的存储空间。namelist指针指向一个数组,数组中存储目录项指针,目录项指针都指向一块内存,内存中存储了文件对应的信息。

用法:

  1. struct dirent** ptr; ——先定义一个二级指针变量(dirent**相当于一个数组)
  2. struct dirent* ptr[];——另一个定义一个二级指针变量的方式
  3. &ptr;——然后对二级指针变量取地址

3、filter

函数指针,搜索的时候,希望过滤掉某些目录,可以通过该回调函数来指定过滤的规则。不指定函数,则传入NULL

4、compar——文件名显示的时候, 指定排序规则

库里面的两个用于排序的函数

  • alphasort——按首字母排序
  • versionsort——按版本号排序

使用示例:

// 目录项二级指针
struct dirent** ptr;
int num = scandir(dirname, &ptr, NULL, alphasort);

 

实现代码

// 发送目录内容,拼写一个html表格
void send_dir(int cfd, const char* dirname) {

	// 拼一个html页面
table可以显示多行多列,便于显示文件信息 char buf[4096] = { 0 }; //目录名 sprintf(buf, "目录名: %s", dirname); //当前目录 sprintf(buf + strlen(buf), "

当前目录: %s

", dirname); /*-------------------------------获取当前目录中的所有内容---------------------------*/ char enstr[1024] = { 0 }; char path[1024] = { 0 }; // 目录项二级指针 struct dirent** ptr; int num = scandir(dirname, &ptr, NULL, alphasort); // 遍历 for (int i = 0; i < num; ++i){ //获取文件名 char* name = ptr[i]->d_name; // 拼接文件的完整路径 sprintf(path, "%s/%s", dirname, name); printf("path = %s ===================\n", path); struct stat st; stat(path, &st); encode_str(enstr, sizeof(enstr), name); // 如果是文件 if (S_ISREG(st.st_mode)){ sprintf(buf + strlen(buf), "", enstr, name, (long)st.st_size); } // 如果是目录 else if (S_ISDIR(st.st_mode)){ sprintf(buf + strlen(buf), "", enstr, name, (long)st.st_size); } //每循环一次就发送异常,清空buf,防止buf溢出 send(cfd, buf, strlen(buf), 0); memset(buf, 0, sizeof(buf)); // 字符串拼接 } sprintf(buf + strlen(buf), "
%s%ld
%s/%ld
"); send(cfd, buf, strlen(buf), 0); printf("dir message send OK!!!!\n"); }

 

第九部分——编码和解码

原因:

http协议中会对中文进行编码。中文如果是Unicode类型,则占三个字节。url在数据传输过程中不支持中文,需要转码。

linux服务器开发 2019.2.7(epoll服务端模型代码,strncasecmp函数,MSG_PEEK,scandir函数,编码解码,sscanf 函数,正则表达式,strftime 函数)_第1张图片

特殊字符

  • 要处理可见字符
    • 从space开始 - 32
    • 前0-31个不可见
  • 不需要转换的特殊字符
    • .
    • _
    • *
    • /
    • ~
    • 0-9
    • a-z
    • A-Z
  • 需要转换的字符使用其16进制的值前加%表示

 

实现代码

// 16进制数转化为10进制
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;
}

//字符转成16进制数
void encode_str(char* to, int tosize, const char* from) {
	int tolen;

	for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
		//isalnum判断字符是不是数字(判断字符是否需要解码)
		//http协议中, /_.~这四个不需要转
		if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
			//不需要转,则原封不动的拷贝一份
			*to = *from;
			++to;
			++tolen;
		}
		else {
			//字符转成十六进制数—— *from取字符值,然后与十六进制数字做按位与
			sprintf(to, "%%%02x", (int)*from & 0xff);
			//to永远指向字符从末尾
			to += 3;
			tolen += 3;
		}
	}
	*to = '\0';
}

//编码,用作回写浏览器的时候,将除字母、数字以及 /_.~以外的字符转义后回写
//将16进制数转换成字符
void decode_str(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]);
			//移除已经处理的两个字符
			from += 2;
		}else {
			*to = *from;
		}
	}
	*to = '\0';
}

 

第十部分——通过文件名获取文件的类型

// 通过文件名获取文件的类型
const char *get_file_type(const char *name){
	char* dot;

	// 自右向左查找‘.’字符, 如不存在返回NULL
	dot = strrchr(name, '.');
	if (dot == NULL)
		return "text/plain; charset=utf-8";
	if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
		return "text/html; charset=utf-8";
	if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
		return "image/jpeg";
	if (strcmp(dot, ".gif") == 0)
		return "image/gif";
	if (strcmp(dot, ".png") == 0)
		return "image/png";
	if (strcmp(dot, ".css") == 0)
		return "text/css";
	if (strcmp(dot, ".au") == 0)
		return "audio/basic";
	if (strcmp(dot, ".wav") == 0)
		return "audio/wav";
	if (strcmp(dot, ".avi") == 0)
		return "video/x-msvideo";
	if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
		return "video/quicktime";
	if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
		return "video/mpeg";
	if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
		return "model/vrml";
	if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
		return "audio/midi";
	if (strcmp(dot, ".mp3") == 0)
		return "audio/mpeg";
	if (strcmp(dot, ".ogg") == 0)
		return "application/ogg";
	if (strcmp(dot, ".pac") == 0)
		return "application/x-ns-proxy-autoconfig";

	return "text/plain; charset=utf-8";
}

 

整体epoll_server.c代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "epoll_server.h"

#define MAXSIZE 2000

void epoll_run(int port) {

	// 创建一个epoll树的根节点
	int epfd = epoll_create(MAXSIZE);
	if (epfd == -1) {
		perror("epoll_create error");
		exit(1);
	}

	// 添加要监听的节点
	// 先添加监听lfd
	int lfd = init_listen_fd(port, epfd);

	// 委托内核检测添加到树上的节点
	struct epoll_event all[MAXSIZE];
	while (1) {
		//epfd是epoll树的根节点,如果有树上某个文件描述符对应的缓冲区发生了变换
        //则会被拷贝到结构体数组all中
		// MAXSIZE是数组的大小, -1是一直阻塞,直到有节点发生变化的时候再发生返回
        //返回值ret是发生变化的文件描述符个数
		int ret = epoll_wait(epfd, all, MAXSIZE, -1);
		if (ret == -1) {
			perror("epoll_wait error");
			exit(1);
		}

		// 遍历发生变化的节点
		for (int i = 0; i < ret; ++i) {
			// 只处理读事件, 其他事件默认不处理
			struct epoll_event *pev = &all[i];
			if (!(pev->events & EPOLLIN)) {
				// 不是读事件
				continue;
			}

			if (pev->data.fd == lfd) {
				// 接受连接请求
				do_accept(lfd, epfd);
			}
			else {
				// 读数据
				do_read(pev->data.fd, epfd);
			}
		}
	}
}

// 读数据
void do_read(int cfd, int epfd) {

	// 将浏览器发过来的数据, 读到buf中 
	char line[1024] = { 0 };
	// 读请求行
	int len = get_line(cfd, line, sizeof(line));

	//get_line调用的是recv,recv返回值为0则说明客户端断开了链接
	if (len == 0) {
		printf("客户端断开了连接...\n");
		// 关闭套接字, cfd从epoll上删除
		disconnect(cfd, epfd);
	}
	//recv失败了
	else if (len == -1) {
		perror("recv error");
		exit(1);
	}
	else {
		printf("请求行数据: %s", line);
		printf("============= 请求头 ============\n");
		// 还有数据没读完,继续读
		while (len) {
			char buf[1024] = { 0 };
			len = get_line(cfd, buf, sizeof(buf));
			printf("-----: %s", buf);
		}
		printf("============= The End ============\n");
	}

	// 请求行: get /xxx http/1.1
	// 判断是不是get请求  strncasecmp函数用于比较两个字符串,不区分大小写的比较前n个字符
	if (strncasecmp("get", line, 3) == 0) {
		// 处理http请求
		http_request(line, cfd);
		// 关闭套接字, cfd从epoll上del
		disconnect(cfd, epfd);
	}
}

// 断开连接的函数
void disconnect(int cfd, int epfd) {
	int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
	if (ret == -1) {
		perror("epoll_ctl del cfd error");
		exit(1);
	}
	close(cfd);
}

// http请求处理,传入request和
void http_request(const char* request, int cfd) {

	// 拆分http请求行
	//    get        /xxx        http/1.1
	char method[12], path[1024], protocol[12];
	//%[^ ]匹配遇到空格为止
	sscanf(request, "%[^ ] %[^ ] %[^ ]", method, path, protocol);

	printf("method = %s, path = %s, protocol = %s\n", method, path, protocol);

	// 转码 将不能识别的中文乱码 - > 中文
	// 解码 %23 %34 %5f
	decode_str(path, path);
	// 处理path  /xx(有可能是目录,也有可能是文件)
	// 去掉path中的/
	char* file = path + 1;
	// 如果没有指定访问的资源, 默认显示资源目录中的内容
	if (strcmp(path, "/") == 0) {
		// file的值, 资源目录的当前位置
		file = "./";
	}

	// 获取文件属性
	struct stat st;
	int ret = stat(file, &st);
	if (ret == -1) {
		// show 404
		send_respond_head(cfd, 404, "File Not Found", ".html", -1);
		send_file(cfd, "404.html");
	}

	// 判断是目录还是文件
	// 如果是目录
	if (S_ISDIR(st.st_mode)) {
		// 发送头信息
		send_respond_head(cfd, 200, "OK", get_file_type(".html"), -1);
		// 发送目录信息
		send_dir(cfd, file);
	}
	else if (S_ISREG(st.st_mode))
	{
		// 文件
		// 发送消息报头
		send_respond_head(cfd, 200, "OK", get_file_type(file), st.st_size);
		// 发送文件内容
		send_file(cfd, file);
	}
}

// 发送目录内容,拼写一个html表格
void send_dir(int cfd, const char* dirname) {

	// 拼一个html页面
table可以显示多行多列,便于显示文件信息 char buf[4096] = { 0 }; //目录名 sprintf(buf, "目录名: %s", dirname); //当前目录 sprintf(buf + strlen(buf), "

当前目录: %s

", dirname); /*-------------------------------获取当前目录中的所有内容---------------------------*/ char enstr[1024] = { 0 }; char path[1024] = { 0 }; // 目录项二级指针 struct dirent** ptr; int num = scandir(dirname, &ptr, NULL, alphasort); // 遍历 for (int i = 0; i < num; ++i){ //获取文件名 char* name = ptr[i]->d_name; // 拼接文件的完整路径 sprintf(path, "%s/%s", dirname, name); printf("path = %s ===================\n", path); struct stat st; stat(path, &st); encode_str(enstr, sizeof(enstr), name); // 如果是文件 if (S_ISREG(st.st_mode)){ sprintf(buf + strlen(buf), "", enstr, name, (long)st.st_size); } // 如果是目录 else if (S_ISDIR(st.st_mode)){ sprintf(buf + strlen(buf), "", enstr, name, (long)st.st_size); } //每循环一次就发送异常,清空buf,防止buf溢出 send(cfd, buf, strlen(buf), 0); memset(buf, 0, sizeof(buf)); // 字符串拼接 } sprintf(buf + strlen(buf), "
%s%ld
%s/%ld
"); send(cfd, buf, strlen(buf), 0); printf("dir message send OK!!!!\n"); #if 0 // 打开目录 DIR* dir = opendir(dirname); if (dir == NULL){ perror("opendir error"); exit(1); } // 读目录 struct dirent* ptr = NULL; while ((ptr = readdir(dir)) != NULL){ char* name = ptr->d_name; } closedir(dir); #endif } // 发送响应头 cfd(服务器和浏览器通讯的文件描述符),no(状态码) //desp(对状态码的描述),type(Content-Type),len(发送的数据长度) void send_respond_head(int cfd, int no, const char* desp, const char* type, long len) { char buf[1024] = { 0 }; // 状态行 http不区分大小写 sprintf(buf, "http/1.1 %d %s\r\n", no, desp); //防止buf溢出,先把里面的数据发送出去 send(cfd, buf, strlen(buf), 0); // 消息报头 sprintf(buf, "Content-Type:%s\r\n", type); sprintf(buf + strlen(buf), "Content-Length:%ld\r\n", len); send(cfd, buf, strlen(buf), 0); // 空行 send(cfd, "\r\n", 2, 0); } // 发送文件 void send_file(int cfd, const char* filename){ // 打开文件 int fd = open(filename, O_RDONLY); if (fd == -1){ // show 404 return; } // 循环读文件 char buf[4096] = { 0 }; int len = 0; while ((len = read(fd, buf, sizeof(buf))) > 0){ // 发送读出的数据 send(cfd, buf, len, 0); } if (len == -1){ perror("read file error"); exit(1); } close(fd); } // 解析http请求消息的每一行内容 int get_line(int sock, char *buf, int size) { int i = 0; char c = '\0'; int n; //每次读一个字节,判断合理就放入缓冲区中 while ((i < size - 1) && (c != '\n')) { n = recv(sock, &c, 1, 0); if (n > 0) { if (c == '\r') { //MSG_PEEK 使得recv以拷贝的方式从缓冲区中读取数据(否则读取之后,缓冲区中的数据就没了) //试探性的获取缓冲区中的数据量 n = recv(sock, &c, 1, MSG_PEEK); //缓冲区中有数据并且结尾是 \n ,则读取数据 if ((n > 0) && (c == '\n')) { recv(sock, &c, 1, 0); } else { c = '\n'; } } buf[i] = c; i++; } else { c = '\n'; } } buf[i] = '\0'; //recv失败 if (n == -1) { i = -1; } return i; } // 接受新连接处理 void do_accept(int lfd, int epfd) { struct sockaddr_in client; socklen_t len = sizeof(client); int cfd = accept(lfd, (struct sockaddr*)&client, &len); if (cfd == -1) { perror("accept error"); exit(1); } // 打印客户端信息 char ip[64] = { 0 }; //inet_ntop大端整型转淀粉十进制ip地址 printf("New Client IP: %s, Port: %d, cfd = %d\n", inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)), ntohs(client.sin_port), cfd); // 设置cfd为非阻塞(默认是阻塞的) int flag = fcntl(cfd, F_GETFL); flag |= O_NONBLOCK; //非阻塞 fcntl(cfd, F_SETFL, flag); // 得到的新节点挂到epoll树上 struct epoll_event ev; ev.data.fd = cfd; // 边沿非阻塞模式 ev.events = EPOLLIN | EPOLLET; int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); if (ret == -1) { perror("epoll_ctl add cfd error"); exit(1); } } //初始化监听,传入端口号和epoll书的根节点 int init_listen_fd(int port, int epfd) { // 创建监听的套接字 int lfd = socket(AF_INET, SOCK_STREAM, 0); if (lfd == -1) { perror("socket error"); exit(1); } // lfd绑定本地IP和port struct sockaddr_in serv; memset(&serv, 0, sizeof(serv)); serv.sin_family = AF_INET; serv.sin_port = htons(port); serv.sin_addr.s_addr = htonl(INADDR_ANY); // 端口复用 int flag = 1; setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag)); int ret = bind(lfd, (struct sockaddr*)&serv, sizeof(serv)); //绑定 if (ret == -1) { perror("bind error"); exit(1); } // 设置监听,最大是128,这里写个64是随意写的 ret = listen(lfd, 64); if (ret == -1) { perror("listen error"); exit(1); } // lfd添加到epoll树上 struct epoll_event ev; //创建节点 ev.events = EPOLLIN; ev.data.fd = lfd; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); //把节点挂上 if (ret == -1) { perror("epoll_ctl add lfd error"); exit(1); } return lfd; } // 16进制数转化为10进制 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; } //字符转成16进制数 void encode_str(char* to, int tosize, const char* from) { int tolen; for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) { //isalnum判断字符是不是数字(判断字符是否需要解码) //http协议中, /_.~这四个不需要转 if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) { //不需要转,则原封不动的拷贝一份 *to = *from; ++to; ++tolen; } else { //字符转成十六进制数—— *from取字符值,然后与十六进制数字做按位与 sprintf(to, "%%%02x", (int)*from & 0xff); //to永远指向字符从末尾 to += 3; tolen += 3; } } *to = '\0'; } //编码,用作回写浏览器的时候,将除字母、数字以及 /_.~以外的字符转义后回写 //将16进制数转换成字符 void decode_str(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]); //移除已经处理的两个字符 from += 2; }else { *to = *from; } } *to = '\0'; } // 通过文件名获取文件的类型 const char *get_file_type(const char *name){ char* dot; // 自右向左查找‘.’字符, 如不存在返回NULL dot = strrchr(name, '.'); if (dot == NULL) return "text/plain; charset=utf-8"; if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0) return "text/html; charset=utf-8"; if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0) return "image/jpeg"; if (strcmp(dot, ".gif") == 0) return "image/gif"; if (strcmp(dot, ".png") == 0) return "image/png"; if (strcmp(dot, ".css") == 0) return "text/css"; if (strcmp(dot, ".au") == 0) return "audio/basic"; if (strcmp(dot, ".wav") == 0) return "audio/wav"; if (strcmp(dot, ".avi") == 0) return "video/x-msvideo"; if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0) return "video/quicktime"; if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0) return "video/mpeg"; if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0) return "model/vrml"; if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0) return "audio/midi"; if (strcmp(dot, ".mp3") == 0) return "audio/mpeg"; if (strcmp(dot, ".ogg") == 0) return "application/ogg"; if (strcmp(dot, ".pac") == 0) return "application/x-ns-proxy-autoconfig"; return "text/plain; charset=utf-8"; }

 

 

 

epoll_server.h

#ifndef _EPOLL_SERVER_H
#define _EPOLL_SERVER_H

int init_listen_fd(int port, int epfd);
void epoll_run(int port);
void do_accept(int lfd, int epfd);
void do_read(int cfd, int epfd);
int get_line(int sock, char *buf, int size);
void disconnect(int cfd, int epfd);
void http_request(const char* request, int cfd);
void send_respond_head(int cfd, int no, const char* desp, const char* type, long len);
void send_file(int cfd, const char* filename);
void send_dir(int cfd, const char* dirname);
void encode_str(char* to, int tosize, const char* from);
void decode_str(char *to, char *from);
const char *get_file_type(const char *name);

#endif

 

 

 

 

正则表达式

 

http://deerchao.net/tutorials/regex/regex.htm

https://www.jb51.net/tools/regexsc.htm

 

正则表达式速查表

                                   

字符

描述

\

将下一个字符标记为一个特殊字符、或一个原义字符、或一个向后引用、或一个八进制转义符。例如,“n"匹配字符"n"。"\n"匹配一个换行符。串行"\\"匹配"\"而"\("则匹配"("。

^

匹配输入字符串的开始位置。如果设置了RegExp对象的Multiline属性,^也匹配“\n"或"\r"之后的位置。

$

匹配输入字符串的结束位置。如果设置了RegExp对象的Multiline属性,$也匹配“\n"或"\r"之前的位置。

*

匹配前面的子表达式零次或多次。例如,zo*能匹配“z"以及"zoo"。*等价于{0,}。

+

匹配前面的子表达式一次或多次。例如,“zo+"能匹配"zo"以及"zoo",但不能匹配"z"。+等价于{1,}。

?

匹配前面的子表达式零次或一次。例如,“do(es)?"可以匹配"does"或"does"中的"do"。?等价于{0,1}。

{n}

n是一个非负整数。匹配确定的n次。例如,“o{2}"不能匹配"Bob"中的"o",但是能匹配"food"中的两个o。

{n,}

n是一个非负整数。至少匹配n次。例如,“o{2,}"不能匹配"Bob"中的"o",但能匹配"foooood"中的所有o。"o{1,}"等价于"o+"。"o{0,}"则等价于"o*"。

{n,m}

mn均为非负整数,其中n<=m。最少匹配n次且最多匹配m次。例如,“o{1,3}"将匹配"fooooood"中的前三个o。"o{0,1}"等价于"o?"。请注意在逗号和两个数之间不能有空格。

?

当该字符紧跟在任何一个其他限制符(*,+,?,{n},{n,},{n,m})后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串。例如,对于字符串“oooo","o+?"将匹配单个"o",而"o+"将匹配所有"o"。

.

匹配除“\n"之外的任何单个字符。要匹配包括"\n"在内的任何字符,请使用像"(.|\n)"的模式。

(pattern)

匹配pattern并获取这一匹配。所获取的匹配可以从产生的Matches集合得到,在VBScript中使用SubMatches集合,在JScript中则使用$0…$9属性。要匹配圆括号字符,请使用“\("或"\)"。

(?:pattern)

匹配pattern但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用或字符“(|)"来组合一个模式的各个部分是很有用。例如"industr(?:y|ies)"就是一个比"industry|industries"更简略的表达式。

(?=pattern)

正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如,“Windows(?=95|98|NT|2000)"能匹配"Windows2000"中的"Windows",但不能匹配"Windows3.1"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始。

(?!pattern)

正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。这是一个非获取匹配,也就是说,该匹配不需要获取供以后使用。例如“Windows(?!95|98|NT|2000)"能匹配"Windows3.1"中的"Windows",但不能匹配"Windows2000"中的"Windows"。预查不消耗字符,也就是说,在一个匹配发生后,在最后一次匹配之后立即开始下一次匹配的搜索,而不是从包含预查的字符之后开始

(?<=pattern)

反向肯定预查,与正向肯定预查类拟,只是方向相反。例如,“(?<=95|98|NT|2000)Windows"能匹配"2000Windows"中的"Windows",但不能匹配"3.1Windows"中的"Windows"。

(?

反向否定预查,与正向否定预查类拟,只是方向相反。例如“(?

x|y

匹配x或y。例如,“z|food"能匹配"z"或"food"。"(z|f)ood"则匹配"zood"或"food"。

[xyz]

字符集合。匹配所包含的任意一个字符。例如,“[abc]"可以匹配"plain"中的"a"。

[^xyz]

负值字符集合。匹配未包含的任意字符。例如,“[^abc]"可以匹配"plain"中的"p"。

[a-z]

字符范围。匹配指定范围内的任意字符。例如,“[a-z]"可以匹配"a"到"z"范围内的任意小写字母字符。

[^a-z]

负值字符范围。匹配任何不在指定范围内的任意字符。例如,“[^a-z]"可以匹配任何不在"a"到"z"范围内的任意字符。

\b

匹配一个单词边界,也就是指单词和空格间的位置。例如,“er\b"可以匹配"never"中的"er",但不能匹配"verb"中的"er"。

\B

匹配非单词边界。“er\B"能匹配"verb"中的"er",但不能匹配"never"中的"er"。

\cx

匹配由x指明的控制字符。例如,\cM匹配一个Control-M或回车符。x的值必须为A-Z或a-z之一。否则,将c视为一个原义的“c"字符。

\d

匹配一个数字字符。等价于[0-9]。

\D

匹配一个非数字字符。等价于[^0-9]。

\f

匹配一个换页符。等价于\x0c和\cL。

\n

匹配一个换行符。等价于\x0a和\cJ。

\r

匹配一个回车符。等价于\x0d和\cM。

\s

匹配任何空白字符,包括空格、制表符、换页符等等。等价于[ \f\n\r\t\v]。

\S

匹配任何非空白字符。等价于[^ \f\n\r\t\v]。

\t

匹配一个制表符。等价于\x09和\cI。

\v

匹配一个垂直制表符。等价于\x0b和\cK。

\w

匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]"。

\W

匹配任何非单词字符。等价于“[^A-Za-z0-9_]"。

\xn

匹配n,其中n为十六进制转义值。十六进制转义值必须为确定的两个数字长。例如,“\x41"匹配"A"。"\x041"则等价于"\x04&1"。正则表达式中可以使用ASCII编码。.

\num

匹配num,其中num是一个正整数。对所获取的匹配的引用。例如,“(.)\1"匹配两个连续的相同字符。

\n

标识一个八进制转义值或一个向后引用。如果\n之前至少n个获取的子表达式,则n为向后引用。否则,如果n为八进制数字(0-7),则n为一个八进制转义值。

\nm

标识一个八进制转义值或一个向后引用。如果\nm之前至少有nm个获得子表达式,则nm为向后引用。如果\nm之前至少有n个获取,则n为一个后跟文字m的向后引用。如果前面的条件都不满足,若nm均为八进制数字(0-7),则\nm将匹配八进制转义值nm

\nml

如果n为八进制数字(0-3),且ml均为八进制数字(0-7),则匹配八进制转义值nml。

\un

匹配n,其中n是一个用四个十六进制数字表示的Unicode字符。例如,\u00A9匹配版权符号(©)。

 

常用正则表达式

用户名

/^[a-z0-9_-]{3,16}$/

密码

/^[a-z0-9_-]{6,18}$/

密码2

(?=^.{8,}$)(?=.*\d)(?=.*\W+)(?=.*[A-Z])(?=.*[a-z])(?!.*\n).*$ (由数字/大写字母/小写字母/标点符号组成,四种都必有,8位以上)

十六进制值

/^#?([a-f0-9]{6}|[a-f0-9]{3})$/

电子邮箱

/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/

/^[a-z\d]+(\.[a-z\d]+)*@([\da-z](-[\da-z])?)+(\.{1,2}[a-z]+)+$/或\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*

URL

/^(https?:\/\/)?([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$/ 或 [a-zA-z]+://[^\s]*

IP 地址

/((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)/

/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/ 或 ((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)

HTML 标签

/^<([a-z]+)([^<]+)*(?:>(.*)<\/\1>|\s+\/>)$/或<(.*)(.*)>.*<\/\1>|<(.*) \/>

删除代码\\注释

(?

匹配双字节字符(包括汉字在内)

[^\x00-\xff]

汉字(字符)

[\u4e00-\u9fa5]

Unicode编码中的汉字范围

/^[\u2E80-\u9FFF]+$/

中文及全角标点符号(字符)

[\u3000-\u301e\ufe10-\ufe19\ufe30-\ufe44\ufe50-\ufe6b\uff01-\uffee]

日期(年-月-日)

(\d{4}|\d{2})-((0?([1-9]))|(1[1|2]))-((0?[1-9])|([12]([1-9]))|(3[0|1]))

日期(月/日/年)

((0?[1-9]{1})|(1[1|2]))/(0?[1-9]|([12][1-9])|(3[0|1]))/(\d{4}|\d{2})

时间(小时:分钟, 24小时制)

((1|0?)[0-9]|2[0-3]):([0-5][0-9])

中国大陆固定电话号码

(\d{4}-|\d{3}-)?(\d{8}|\d{7})

中国大陆手机号码

1\d{10}

1[0-9]{10}

中国大陆邮政编码

[1-9]\d{5}

 

中国大陆身份证号(15位或18位)

\d{15}(\d\d[0-9xX])?

非负整数(正整数或零)

\d+

正整数

[0-9]*[1-9][0-9]*

负整数

-[0-9]*[1-9][0-9]*

整数

-?\d+

小数

(-?\d+)(\.\d+)?

空白行

\n\s*\r 或者 \n\n(editplus) 或者 ^[\s\S ]*\n 

QQ号码

[1-9]\d{4,}

不包含abc的单词

\b((?!abc)\w)+\b

匹配首尾空白字符

^\s*|\s*$

编辑常用

以下是针对特殊中文的一些替换(editplus)

^[0-9].*\n 

^[^第].*\n 

^[习题].*\n

^[\s\S ]*\n 

^[0-9]*\. 

^[\s\S ]*\n 

*]>

href="javascript:if\(confirm\('(.*?)'\)\)window\.location='(.*?)'"

.[^<>]*

 

[\s\S]*?

 

 

 

sscanf 函数——可用于字符串拆分

函数描述:  读取格式化的字符串中的数据)。

函数原型:

int sscanf(     
        const char *buffer,     
        const char *format, [ argument ] ...   

); 
  • format——拆分规则
  • argument——拆好串的内存地址

 

1、取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。

1

2

sscanf("123456 abcdedf""%[^  ]", buf);

printf("%s\n", buf);

结果为:123456

2、取仅包含指定字符集的字符串。如在下例中,取仅包含1到9和小写字母的字符串。

1

2

sscanf("123456abcdedfBCDEF""%[1-9a-z]", buf);

printf("%s\n", buf);

结果为:123456abcdedf

3、取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。

1

2

sscanf("123456abcdedfBCDEF""%[^A-Z]", buf);

printf("%s\n", buf);

结果为:123456abcdedf

 

 

 

strftime 函数——格式化时间

 

头文件: time.h

函数功能:  将时间格式化,或者说格式化一个时间字符串。

函数原型:

size_t strftime(
		char *strDest,            //格式化之后的数据存在strDest数组中
		size_t maxsize,
		const char *format,       //格式化成什么样
		const  struct tm *timeptr //待格式化数据
);

format

  • %a 星期几的简写
  • %A 星期几的全称
  • %b 月份的简写
  • %B 月份的全称
  • %c 标准的日期的时间串
  • %C 年份的前两位数字
  • %d 十进制表示的每月的第几天
  • %D 月/天/年
  • %e 在两字符域中,十进制表示的每月的第几天
  • %F 年-月-日
  • %g 年份的后两位数字,使用基于周的年
  • %G 年份,使用基于周的年
  • %h 简写的月份名
  • %H 24小时制的小时
  • %I 12小时制的小时
  • %j 十进制表示的每年的第几天
  • %m 十进制表示的月份
  • %M 十时制表示的分钟数
  • %p 本地的AM或PM的等价显示
  • %r 12小时的时间
  • %R 显示小时和分钟:hh:mm
  • %S 十进制的秒数
  • %t 水平制表符
  • %T 显示时分秒:hh:mm:ss
  • %u 每周的第几天,星期一为第一天 (值从1到7,星期一为1)
  • %U 第年的第几周,把星期日作为第一天(值从0到53)
  • %V 每年的第几周,使用基于周的年
  • %w 十进制表示的星期几(值从0到6,星期天为0)
  • %W 每年的第几周,把星期一做为第一天(值从0到53)
  • %x 标准的日期串
  • %X 标准的时间串
  • %y 不带世纪的十进制年份(值从0到99)
  • %Y 带世纪部分的十制年份
  • %z,%Z 时区名称,如果不能得到时区名称则返回空字符。

你可能感兴趣的:(Linux,服务器开发)