1、epoll上添加内容
是目录进入目录,要是是文件显示,图片可以下载
只是在以前的epoll服务器版本上,对读、写,添加了一些http协议的东西。
1、先写一个epoll非堵塞ET模式服务器,然后对读写进行加东西
2、读:浏览器发来请求,读取第一行http协议(GET 文件名 协议号)
3、写:根据内容信息,将对应的http头信息发回去,然后发数据
4、判断读到的文件名是不是普通文件还是目录:
普通文件:先发回http对应头信息,然后将文件内容发回
目录:先发回http对应头信息,将目录中的目录项提取出来,和html标签拼接在一起返回(需要进行编码)
5、错误页面:当请求的文件名或者目录不存在,返回错误页面
2、代码
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/stat.h>
#include <ctype.h>
#define MAXSIZE 2048
// 获取一行 \r\n 结尾的数据
int get_line(int cfd, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
while ((i < size-1) && (c != '\n')) {
n = recv(cfd, &c, 1, 0);
if (n > 0) {
if (c == '\r') {
n = recv(cfd, &c, 1, MSG_PEEK);
if ((n > 0) && (c == '\n')) {
recv(cfd, &c, 1, 0);
} else {
c = '\n';
}
}
buf[i] = c;
i++;
} else {
c = '\n';
}
}
buf[i] = '\0';
if (-1 == n)
i = n;
return i;
}
// 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;
}
/*
* 这里的内容是处理%20之类的东西!是"解码"过程。
* %20 URL编码中的‘ ’(space)
* %21 '!' %22 '"' %23 '#' %24 '$'
* %25 '%' %26 '&' %27 ''' %28 '('......
* 相关知识html中的‘ ’(space)是 
*/
void encode_str(char* to, int 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';
}
void decode_str(char *to, char *from)
{
for ( ; *from != '\0'; ++to, ++from ) {
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
*to = hexit(from[1])*16 + hexit(from[2]);
from += 2;
} else {
*to = *from;
}
}
*to = '\0';
}
void send_error(int cfd, int status, char *title, char *text)
{
char buf[4096] = {0};
sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", status, title);
sprintf(buf+strlen(buf), "Content-Type:%s\r\n", "text/html");
sprintf(buf+strlen(buf), "Content-Length:%d\r\n", -1);
sprintf(buf+strlen(buf), "Connection: close\r\n");
send(cfd, buf, strlen(buf), 0);
send(cfd, "\r\n", 2, 0);
memset(buf, 0, sizeof(buf));
sprintf(buf, "<html><head><title>%d %s</title></head>\n", status, title);
sprintf(buf+strlen(buf), "<body bgcolor=\"#cc99cc\"><h2 align=\"center\">%d %s</h4>\n", status, title);
sprintf(buf+strlen(buf), "%s\n", text);
sprintf(buf+strlen(buf), "<hr>\n</body>\n</html>\n");
send(cfd, buf, strlen(buf), 0);
return ;
}
// 通过文件名获取文件的类型
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";
}
int init_listen_fd(int port,int epfd)
{
// 创建监听套接字lfd
int lfd = socket(AF_INET,SOCK_STREAM,0);
if(lfd == -1)
{
perror("socket error");
exit(1);
}
// 创建服务器地址结构
struct sockaddr_in ser_addr;
bzero(&ser_addr,sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(port);
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 设置端口复用
int opt = 1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
// 绑定地址结构
int ret = bind(lfd,(struct sockaddr*)&ser_addr,sizeof(ser_addr));
if(ret == -1)
{
perror("bind error");
exit(1);
}
// 设置监听上限
ret = listen(lfd,128);
if(ret == -1)
{
perror("listen error");
exit(1);
}
// lfd 添加到epfd中
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 disconnect(int cfd,int epfd)
{
int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
if(ret != 0 )
{
perror("epoll_ctl del error");
exit(1);
}
close(cfd);
}
/*
发起头文件响应:
cfd:客户端
no:错误号
discript:错误描述
type:回发类型
len:文件长度
*/
void send_response(int cfd,int no,char* discript,const char* type,int len)
{
char buf[1024] = {0};
sprintf(buf,"HTTP/1.1 %d %s\r\n",no,discript);
sprintf(buf+strlen(buf),"Content-Type:%s\r\n",type);
sprintf(buf+strlen(buf),"Content-Length:%d\r\n",len);
send(cfd,buf,strlen(buf),0);
send(cfd,"\r\n",2,0);
}
void send_file(int cfd,const char* file)
{
int fd = open(file,O_RDONLY);
if(fd == -1)
{
// 错误页面
send_error(cfd,404,"Not Found","No such file or direntry");
return;
}
char buf[4096]={0};
int n = 0,ret;
while ((n=read(fd,buf,sizeof(buf)))>0)
{
ret = send(cfd,buf,n,0);
if(ret == -1)
{
if(errno == EAGAIN)
{continue;}
else if(errno == EINTR)
{
continue;
}
{
perror("send error");
exit(1);
}
}
}
// close(cfd);
}
// 发送目录内容
void send_dir(int cfd, const char* dirname)
{
int i, ret;
// 拼一个html页面<table></table>
char buf[4094] = {0};
sprintf(buf, "<html><head><title>目录名: %s</title></head>", dirname);
sprintf(buf+strlen(buf), "<body><h1>当前目录: %s</h1><table>", dirname);
char enstr[1024] = {0};
char path[1024] = {0};
// 目录项二级指针
struct dirent** ptr;
// scandir 函数
// dirname:readdir()函数
// ptr:传出参数
// alphasort(按照字符排序)
int num = scandir(dirname, &ptr, NULL, alphasort);
// 遍历
for(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),
"<tr><td><a href=\"%s\">%s</a></td><td>size:%ldk</td></tr>",
enstr, name, (long)st.st_size);
} else if(S_ISDIR(st.st_mode)) { // 如果是目录
sprintf(buf+strlen(buf),
"<tr><td><a href=\"%s/\">%s/</a></td><td>size:%ldk</td></tr>",
enstr, name, (long)st.st_size);
}
ret = send(cfd, buf, strlen(buf), 0);
if (ret == -1) {
if (errno == EAGAIN) {
perror("send error:");
continue;
} else if (errno == EINTR) {
perror("send error:");
continue;
} else {
perror("send error:");
exit(1);
}
}
memset(buf, 0, sizeof(buf));
// 字符串拼接
}
sprintf(buf+strlen(buf), "</table></body></html>");
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
}
void http_request(int cfd,const char* file)
{
// 判断文件是否存在
struct stat sbuf;
int ret = stat(file,&sbuf);
if(ret!=0)
{
// 回发浏览器404错误页面
send_error(cfd,404,"Not Found","No such file or direntry");
return;
}
// 判断是目录还是文件
if(S_ISDIR(sbuf.st_mode))
{ // 目录
// 发送头信息
send_response(cfd, 200, "OK", get_file_type(".html"), -1);
// 发送目录信息
send_dir(cfd, file);
}
else if(S_ISREG(sbuf.st_mode)) // 是一个普通文件
{
// 回发http协议应答
// 普通文件请求头
send_response(cfd,200,"OK",get_file_type(file), sbuf.st_size);
// send_response(cfd,200,"OK"," Content-Type: text/plain; charset=iso-8859-1", sbuf.st_size);
// 图片请求头
// send_respond(cfd, 200, "OK", "Content-Type:image/jpeg", -1);
// 音频文件
//send_respond(cfd, 200, "OK", "audio/mpeg", -1);
// 回发给客户端请求数据内容
// 回发数据内容
send_file(cfd,file);
}
}
void do_read(int cfd,int epfd)
{
// 读取第一行http协议,拆分,获取GET 文件名协议号
char line[1024] = {0};
char method[16]={0},path[256]={0},protocol[16]={0};
int len = get_line(cfd,line,sizeof(line));
if(len == 0)
{
printf("客户端关闭\n");
disconnect(cfd,epfd);
}
else
{
printf("line=%s\n",line);
// 从字符串中,找不为空格的
sscanf(line,"%[^ ] %[^ ] %[^ ]",method,path,protocol);
printf("method=%s path=%s protocol=%s\n",method,path,protocol);
// 将缓冲区其他内容全部读走
while(1)
{
char buf[1024] = {0};
len = get_line(cfd,buf,sizeof(buf));
if(buf[0] == '\n')
break;
else if (len == -1)
{
break;
}
}
}
if(strncasecmp(method,"GET",3)==0)
{
decode_str(path,path); // 解码
char *file = path+1; // 取出客户端要访问的文件名
if(strcmp(path,"/")==0)
{ // path中是/ 表示请求当前目录
file = "./";
}
// 回发给客户端请求数据内容
http_request(cfd,file);
disconnect(cfd,epfd);
}
}
void do_accept(int lfd,int epfd)
{
struct sockaddr_in ctl_addr;
socklen_t ctl_len = sizeof(ctl_addr);
int ret = accept(lfd,(struct sockaddr*)&ctl_addr,&ctl_len);
if(ret == -1)
{
perror("accept error");
exit(1);
}
printf("进来啦");
// 打印客户端ip+port
char client_ip[64] = {0};
printf("新的客户端ip:%s 端口:%d\n",
inet_ntop(AF_INET,&(ctl_addr.sin_addr.s_addr),client_ip,sizeof(client_ip)),
ntohs(ctl_addr.sin_port));
// 将cfd设置为非堵塞
int flag = fcntl(ret,F_GETFL);
flag |= O_NONBLOCK;
fcntl(ret,F_SETFL,flag);
// 将新客户端挂到树上
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = ret;
int r = epoll_ctl(epfd,EPOLL_CTL_ADD,ret,&ev);
if(r==-1)
{
perror("epoll_ctl add cfd error");
exit(1);
}
}
void epoll_run(int port)
{
int i = 0;
struct epoll_event all_events[MAXSIZE];
int epfd = epoll_create(MAXSIZE); // 创建一颗epoll监听树
if(epfd == -1)
{
perror("epoll_create error");
exit(1);
}
// lfd建立建立套接字
int lfd = init_listen_fd(port,epfd);
// 循环监听树上面的事件
while(1)
{
// 满足的事件会放到all_events 数组中,0表示不堵塞
int ret = epoll_wait(epfd,all_events,MAXSIZE,0);
if(ret == -1)
{
perror("epoll_wait error");
exit(1);
}
for(i=0;i<ret;i++)
{
// 只处理读事件,其他事件默认不处理
struct epoll_event* pev = &all_events[i];
if(!(pev->events & EPOLLIN)) // 不是读事件
continue;
if(pev->data.fd == lfd) // 客户端链接请求事件
{
do_accept(lfd,epfd);
}
else // 读数据
{
do_read(pev->data.fd,epfd);
}
}
}
}
int main(int argc,char* argv[])
{
if(argc<3)
{
printf("./server port path\n");
exit(1);
}
int port = atoi(argv[1]); // 获取用户输入端口
int ret = chdir(argv[2]); // 改变工作目录
if(ret != 0)
{
printf("chdir error\n");
exit(1);
}
// 启动 epoll监听
epoll_run(port);
return 0;
}
3、使用
gcc 编译代码
指定端口,和服务器目录
4、将程序改成守护进程在后台运行
只需要对main函数稍加修改就可以
链接: Linux c 守护进程创建
int main(int argc,char* argv[])
{
pid_t pid,sid;
int ret;
if(argc<3)
{
printf("./server port path\n");
exit(1);
}
pid = fork();
if(pid==0)
{
sid = setsid();
if(sid == -1)
{
perror("setsid error");
exit(1);
}
ret = chdir(argv[2]); // 改变工作目录
// if(ret == -1)
// sys_perror("chdir error");
umask(0022); // 改变文件访问权限掩码
close(STDIN_FILENO); // 关闭文件描述符0
int fd = open("/dev/null",O_RDWR);
if(fd == -1)
{
perror("open error");
exit(1);
}
dup2(fd,STDOUT_FILENO);
dup2(fd,STDERR_FILENO);
int port = atoi(argv[1]); // 获取用户输入端口
int ret = chdir(argv[2]); // 改变工作目录
if(ret != 0)
{
printf("chdir error\n");
exit(1);
}
// 启动 epoll监听
epoll_run(port);
}
return 0;
}
设置守护进程后,想要结束程序
// 查出进程
ps -aux | grep 9999
kill -9 进程id