c_cpp Linux的下epoll的模型实现简单的HTTP服务器
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c_cpp Linux的下epoll的模型实现简单的HTTP服务器相关的知识,希望对你有一定的参考价值。
#ifndef _PUB_H
#define _PUB_H
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <ctype.h>
int tcp4bind(short port,const char *IP);
char *get_mime_type(char *name);//通过文件名字获得文件类型
int get_line(int sock, char *buf, int size);//获取一行
int hexit(char c);//16进制转10进制
void strencode(char* to, size_t tosize, const char* from);//编码
void strdecode(char *to, char *from);//解码
#endif
#include "pub.h"
//通过文件名字获得文件类型
char *get_mime_type(char *name)
{
char* dot;
dot = strrchr(name, '.'); //自右向左查找‘.’字符;如不存在返回NULL
/*
*charset=iso-8859-1 西欧的编码,说明网站采用的编码是英文;
*charset=gb2312 说明网站采用的编码是简体中文;
*charset=utf-8 代表世界通用的语言编码;
* 可以用到中文、韩文、日文等世界上所有语言编码上
*charset=euc-kr 说明网站采用的编码是韩文;
*charset=big5 说明网站采用的编码是繁体中文;
*
*以下是依据传递进来的文件名,使用后缀判断是何种文件类型
*将对应的文件类型按照http定义的关键字发送回去
*/
if (dot == (char*)0)
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";
}
/**********************************************************************/
/* Get a line from a socket, whether the line ends in a newline,
* carriage return, or a CRLF combination. Terminates the string read
* with a null character. If no newline indicator is found before the
* end of the buffer, the string is terminated with a null. If any of
* the above three line terminators is read, the last character of the
* string will be a linefeed and the string will be terminated with a
* null character.
* Parameters: the socket descriptor
* the buffer to save the data in
* the size of the buffer
* Returns: the number of bytes stored (excluding null) */
/**********************************************************************/
//获得一行数据,每行以\r\n作为结束标记
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);
/* DEBUG printf("%02X\n", c); */
if (n > 0)
{
if (c == '\r')
{
n = recv(sock, &c, 1, MSG_PEEK);//MSG_PEEK 从缓冲区读数据,但是数据不从缓冲区清除
/* DEBUG printf("%02X\n", c); */
if ((n > 0) && (c == '\n'))
recv(sock, &c, 1, 0);
else
c = '\n';
}
buf[i] = c;
i++;
}
else
c = '\n';
}
buf[i] = '\0';
return(i);
}
int tcp4bind(short port,const char *IP)
{
struct sockaddr_in serv_addr;
int lfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&serv_addr,sizeof(serv_addr));
if(IP == NULL){
//如果这样使用 0.0.0.0,任意ip将可以连接
serv_addr.sin_addr.s_addr = INADDR_ANY;
}else{
if(inet_pton(AF_INET,IP,&serv_addr.sin_addr.s_addr) <= 0){
perror(IP);//转换失败
exit(1);
}
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
if(bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0){
perror("bind err");
exit(-1);
}
return lfd;
}
/*
* 这里的内容是处理%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]);
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';
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <dirent.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include "pub.h"
#define MAX_CONNSIZE 1024
typedef struct _EventData
{
int fd;
int epfd;
uint32_t events;
void(*callback)(struct _EventData* pthis);
}EventData;
int send_header(int cfd, int xcode, char *xmsg, char *fileType, long lFileSize);
int send_file(int cfd, int resfd);
int send_dir(int cfd, DIR* dirfd);
void cb_response(EventData* pthis);
void cb_accept(EventData* pthis);
void send_404(int cfd);
int main(int argc, const char* argv[])
{
//切换工作目录到 $HOME/webroot
chdir(getenv("HOME"));
chdir("./webroot");
int lfd = tcp4bind(8080, NULL);//绑定socket --> bind
listen(lfd, 128);//监听
int epfd = epoll_create(1);//创建根节点
//将lfd上树
struct epoll_event ev;
ev.events = EPOLLIN;//设置events
ev.data.ptr = malloc(sizeof(EventData));//设置data
((EventData*)ev.data.ptr)->fd = lfd;
((EventData*)ev.data.ptr)->epfd = epfd;
((EventData*)ev.data.ptr)->events = ev.events;
((EventData*)ev.data.ptr)->callback = cb_accept;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
//fcntl(lfd, F_SETFL, fcntl(lfd, F_GETFL) | O_NONBLOCK);//设置lfd为非阻塞
//循环wait
struct epoll_event evs[MAX_CONNSIZE + 1]; //最大连接数+lfd
int nready;
while (1)
{
nready = epoll_wait(epfd, evs, MAX_CONNSIZE, -1);//等待
for (int i = 0; i < nready; i++)//循环处理事件
{
EventData* currev = evs[i].data.ptr;
currev->callback(currev);
}
}
return 0;
}
void send_404(int cfd)
{
int fd404 = open("404.html", O_RDONLY);
if (fd404 == -1)
{
perror("open 404.html");
return;
}
//发送404
struct stat st;
fstat(fd404, &st);
send_header(cfd, 404, "Not Found", get_mime_type("404.html"), st.st_size);
//发送内容
//open()
send_file(cfd, fd404);
close(fd404);
}
int send_header(int cfd, int xcode, char *xmsg, char *fileType, long lFileSize)
{
char buf[1024];
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "HTTP/1.1 %d %s\r\n", xcode, xmsg);
send(cfd, buf, strlen(buf), 0);//发送响应第一行
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "Content-Type:%s\r\n", fileType);
send(cfd, buf, strlen(buf), 0);//发送响应第二行
if (lFileSize > 0)//如果文件大小>0就发送长度数
{
memset(buf, 0x00, sizeof(buf));
sprintf(buf, "Content-Length:%ld\r\n", lFileSize);
send(cfd, buf, strlen(buf), 0);//发送响应文件长度
}
send(cfd, "\r\n", 2, 0);//发送空行
return 0;
}
int send_file(int cfd, int resfd)
{
int ret;
char buf[256] = { 0x00 };
do
{
ret = read(resfd, buf, sizeof(buf));
if (ret != -1)//成功读到就发送
{
send(cfd, buf, ret, 0);
}
} while ((ret == -1 && errno == EINTR) || ret > 0);//被信号打断,重复进行
return 0;
}
int send_dir(int cfd, DIR* dirfd)
{
char headHtml[] = { "<!DOCTYPE html>"
"<html lang = \"en\">"
"<head>"
"<meta charset = \"UTF-8\">"
"<meta name = \"viewport\" content = \"width=device-width, initial-scale=1.0\">"
"<meta http - equiv = \"X-UA-Compatible\" content = \"ie=edge\">"
"<title>dir list</title>"
"</head>"
"<body bgcolor = \"#99cc99\">"
"<h1>Index of / </h1>"
"<ul>" };
char tailHtml[] = { "</ul>"
"<h3><a href=\" / \">返回主页</a></h3>"
"</body>"
"</html>" };
send(cfd, headHtml, strlen(headHtml), 0);//发送头
struct dirent *item;
while ((item = readdir(dirfd)) != NULL)//循环读目录项
{
char li[256] = { 0x00 };// = { "<li><a href = "">< /a>< /li> " }
if (item->d_type == DT_DIR)//是目录
{
sprintf(li, "<li><a href = \"%s/\">%s</a></li>", item->d_name, item->d_name);
}
else
{
sprintf(li, "<li><a href = \"%s\">%s</a></li>", item->d_name, item->d_name);
}
send(cfd, li, strlen(li), 0);//发送li
}
send(cfd, tailHtml, strlen(tailHtml), 0);//发送尾
return 0;
}
void cb_response(EventData* pthis)
{
char reqLine[512] = { 0x00 };
int ret = get_line(pthis->fd, reqLine, sizeof(reqLine));//获取第一行
if (ret <= 0)//读出错,关闭客户端,下树
{
close(pthis->fd);//关闭客户端
struct epoll_event ev;
ev.data.ptr = pthis;//设置data
ev.events = pthis->events;
epoll_ctl(pthis->epfd, EPOLL_CTL_DEL, pthis->fd, &ev);//下树
free(pthis);//释放data结构体
if (ret == -1)
{
perror("recv err");
}
return;
}
strdecode(reqLine, reqLine);
write(STDOUT_FILENO, reqLine, ret);//将读取到的请求行打印
//读取并丢弃其他行
char buf[256] = { 0x00 };
int ret_oth = 0;
do
{
memset(buf, 0x00, sizeof(buf));
ret_oth = get_line(pthis->fd, buf, sizeof(buf));
if (ret_oth > 0)
{
strdecode(buf, buf);
write(STDOUT_FILENO, buf, ret_oth);//将读取到的请求内容打印
}
} while (ret_oth > 0 || errno == EINTR);
if (ret_oth <= 0 && errno != EAGAIN)//对端关闭或出错
{
close(pthis->fd);//关闭客户端
struct epoll_event ev;
ev.data.ptr = pthis;//设置data
ev.events = pthis->events;
epoll_ctl(pthis->epfd, EPOLL_CTL_DEL, pthis->fd, &ev);//下树
free(pthis);//释放data结构体
if (ret_oth == -1)
{
perror("recv err");
}
return;
}
//解析第一行数据
char method[8] = { 0x00 };
char res[256] = { 0x00 };
sscanf(reqLine, "%s %s", method, res);
//printf("method:[%s]\nres:[%s]\n", method, res);
if (strncasecmp(method, "get", 3) == 0)//GET请求
{
char *filename;
if (res[0] == '/')//去掉第一个'/'
{
filename = res + 1;
}
if (filename[0] == '\0')//如果是空串,直接等于当前路径
{
memset(filename, 0x00, sizeof(res));
strncpy(filename, "./", 2);
}
//如何判断文件(目录)是否存在
struct stat st;
int ret_stat = stat(filename, &st);
if (ret_stat != 0)//如果打开失败,返回404
{
send_404(pthis->fd);
perror("stat err");
return;
}
else//可以发送
{
int resfd = open(filename, O_RDONLY);
if (resfd == -1)//无法打开,返回404
{
send_404(pthis->fd);
perror("open err");
return;
}
if (S_ISREG(st.st_mode))//代表普通文件
{
//发送头
send_header(pthis->fd, 200, "OK", get_mime_type(filename), st.st_size);
//发送内容
send_file(pthis->fd, resfd);
close(resfd);
}
if (S_ISDIR(st.st_mode))//代表目录文件
{
if (strncmp(filename, "..", 2) == 0)//请求webroot的上级目录
{
send_404(pthis->fd);
return;
}
DIR* resfd = opendir(filename);//打开目录
if (resfd == NULL)
{
send_404(pthis->fd);
return;
}
//发送头
//printf("will send dir\n");
send_header(pthis->fd, 200, "OK", get_mime_type("dir.html"), -1);
send_dir(pthis->fd, resfd);//发送目录内容
closedir(resfd);
}
}
}
else//其他请求
{
//暂不处理
return;
}
}
void cb_accept(EventData* pthis)
{
int cfd = accept(pthis->fd, NULL, NULL);//接受新连接
if (cfd < 0)
{
perror("accept");
close(cfd);//关闭客户端
return;
}
//将cfd上树
struct epoll_event ev;
ev.events = EPOLLIN;//设置events
ev.data.ptr = malloc(sizeof(EventData));//设置data
((EventData*)ev.data.ptr)->fd = cfd;
((EventData*)ev.data.ptr)->epfd = pthis->epfd;
((EventData*)ev.data.ptr)->events = ev.events;
((EventData*)ev.data.ptr)->callback = cb_response;
epoll_ctl(pthis->epfd, EPOLL_CTL_ADD, cfd, &ev);
fcntl(cfd, F_SETFL, fcntl(cfd, F_GETFL) | O_NONBLOCK);//设置cfd为非阻塞
}
以上是关于c_cpp Linux的下epoll的模型实现简单的HTTP服务器的主要内容,如果未能解决你的问题,请参考以下文章