服务器是基于epoll实现 ET模式 非阻塞模式,浏览器用Get请求方式访问服务器本地资源
写这个demo过程中遇到问题:
1.浏览器 访问的 例如 192.168.1.130:8989/ 这个 '/'这个根文件不是服务器跟目录而是服务器的本地资源相对路径,所有要切换服务器的目录到 浏览器资源访问的目录下 通过chdir设置,浏览器访的 路径 假设一个图片 192.168.1.130:8989/wsy.png; 通过http数据的解析 我们得到的是
/wsy.png 此时需要去掉 '/'
2.epoll ET模型 ,收到新的客户端连接,需要将通信描述符标记 改为非阻塞模式,事件加上ETPOLLET 此时 read 和recv 不阻塞,通过while循环将数据读完,
3.服务器给客户端发送比较大的图片时候的由于服务器发送的数据过快,导致浏览器显示数据不全,(由于缓冲机制覆盖问题 丢失数据导师浏览器的数据缺陷注:tcp协议数据是不会跌势,是浏览器的本身刷新的原因),所以需要加发送数据给浏览器的时候 稍微延时处理即可
4.浏览器访问多个文件夹里面的数据会出现 超链接访问的路径不对,
if ( S_ISDIR(st.st_mode)) //the file is dir , so add '/'
{
sprintf(buf+strlen(buf), "<tr><td><a charset=\"UTF-8\" href=\"%s/\">%s</a></td><td>%s</td></tr>",name, name, subBuf); 需要判断是不是一个文件 然后在超链接后面加一个'/'
},这样就可以解决路径访问不对的问题
5.由于http数据的传输过程中不会有中文的,所以浏览器带有中文的数据 比如:192.168.1.130:8989/风景.png, 此时浏览器会自动将 ‘风景’转化为 Unicode 码形式 %E8%86%84.png,此时浏览器访问服务器本地资源就找不到,我们就要将浏览器中文编码unicode还原为utf-8格式编码,将16进制转化为10进制字符,函数 decodeMsg(),就可以找到服务器本地资源目录的风景.png图片资源
// server.c
#ifndef _SERVER_H_
#define _SERVER_H_
#include<stdlib.h>
#include<stdio.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<errno.h>
#include<fcntl.h>
#include<string.h>
#include<sys/stat.h>
#include<strings.h>
#include<dirent.h>
#include<ctype.h>
int initListenFd(unsigned short prot);
int epollRun(int lfd);
int acceptNewConnect(int lfd, int epfd);
int recvHttpRequest(int fd, int epfd);
int parseHttpLine(const char* buf, int fd);
int sendHttpHeadMsg(int fd, int staus, const char* dscr, const char* type, int leght);
//
int sendFileInfo(int fd, const char* name);
int sendDirInfo(int fd, const char*dirname);
//
const char* getFileTypeByName(const char* name);
void decodeMsg(char* to, char* from);
int hextoDex(char c);
#endif
//server.c
#include "server.h"
int initListenFd(unsigned short prot)
{
int lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1)
{
perror("socket");
return -1;
}
int opt = 1; //set lfd no_block
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(prot);
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if (-1 == ret)
{
perror("bind");
return -1;
}
ret = listen(lfd, 128);
if (-1 == ret)
{
perror("listen");
return -1;
}
return lfd;
}
int epollRun(int lfd)
{
int epfd = epoll_create(1);
if (epfd < 0)
{
perror("epfd");
exit(0);
/* code */
}
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = lfd;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
if (ret == -1)
{
perror("epoll_ctl");
exit(0);
/* code */
}
struct epoll_event evs[1024];
int size = sizeof(evs) / sizeof(evs[0]);
int falg = 0;
while (1)
{
if (falg)
{
break;
}
printf("epoll_wait..........\n");
int num = epoll_wait(epfd, evs, size, -1);
if (-1 == num)
{
perror("epoll_wait");
exit(0);
/* code */
}
for (size_t i = 0; i < num; i++)
{
int curfd = evs[i].data.fd;
if (evs[i].events & EPOLLOUT)
{
continue;
}
if (curfd == lfd)
{
// create new client connetct
int ret = acceptNewConnect(lfd, epfd);
if (ret == -1)
{
break;
}
}
else
{
recvHttpRequest(curfd, epfd);
}
}
}
}
int acceptNewConnect(int lfd, int epfd)
{
//new client coming
struct sockaddr_in clientaddr;
int clen = sizeof(clientaddr);
// memset(&clientaddr, )
int cfd = accept(lfd, (struct sockaddr*)&clientaddr, &clen);
if (cfd == -1)
{
perror("accept");
return -1;
/* code */
}
char myip[28];
memset(myip, 0 ,sizeof(myip));
printf("new client accept ip=%s, prot=%d\n", inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr,
myip, sizeof(myip)), ntohs(clientaddr.sin_port));
//set fd non_block
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
//deaulf ET
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET; //set ET Model
ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
return 0;
}
int recvHttpRequest(int fd, int epfd)
{
// set fd o_nonblock ET mode
char buf[4096];
char temp[1024];
int rlen, tolenLen = 0;
while ( (rlen = recv(fd, temp, sizeof(temp), 0)) > 0 )
{
if ( tolenLen + rlen <= sizeof(buf) )
{
memcpy(buf + tolenLen, temp, rlen);
tolenLen += rlen;
}
else
{
break;
}
}
/*
GET /cat.gif HTTP/1.1 \r\n
Host: 192.168.32.130:8989 \r\n
Connection: keep-alive \r\n
Cache-Control: max-age=0 \r\n
Upgrade-Insecure-Requests: 1 \r\n
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.72 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
*/
printf("recv http data:\n %s\n", buf);
if (rlen == -1 && errno == EAGAIN)
{
printf("read data done\n");
//find "\r\n" pointer
//get lien
// GET /cat.gif HTTP/1.1 \r\n
char* p = strstr(buf, "\r\n");
if (p)
{
int len = p - buf;
buf[len] = '\0';
}
parseHttpLine(buf, fd);
}
else if(rlen == 0)
{
printf("old client close\n");
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
else
{
perror("read");
}
//deal headData
return -1;
}
int parseHttpLine(const char* buf, int fd)
{
char methon[10] = {0};
char path[1024] = {0};
// GET /wangcheng/res http1.1
sscanf(buf, "%[^ ] %[^ ]", methon, path);
if(strcasecmp(methon, "get") != 0 )
{
printf("this is post request\n");
return -1;
}
char* file = NULL;
//if path with chineese, so 16hex to 10. exp:path= ./wsy%E8%85%12.png unicode
printf("decodeMsg1 = %s", path);
decodeMsg(path, path);
printf("decodeMsg2 = %s", path);
if (strcmp(path, "/") == 0 ) // is res ./
{
file = "./"; // res/
}
else // is filepath
{
file = path + 1;
}
printf("current working directory: %s\n", getcwd(NULL, 0));
//throught path get file attrs
struct stat st;
int ret = stat(file, &st);
if (ret == -1)
{
printf("file is not exit");
sendHttpHeadMsg(fd, 404, "Not Found", getFileTypeByName("404.html"), -1);
sendFileInfo(fd, "404.html");
return -1;
}
if( S_ISDIR(st.st_mode)) //this is dir
{
sendHttpHeadMsg(fd, 200, "OK", getFileTypeByName(".html"), -1);
sendDirInfo(fd, file);
}
else //this is file
{
sendHttpHeadMsg(fd, 200, "OK", getFileTypeByName(file), st.st_size);
sendFileInfo(fd, file);
}
return 0;
}
int sendHttpHeadMsg(int fd, int status, const char* dscr, const char* type, int leght)
{
/*
http/1.1 200 ok
Content-Type:image/jpg
Content-Length: -1
send filedata
*/
char buf[1024] = {0};
sprintf(buf, "http/1.1 %d %s\r\n", status, dscr);
sprintf(buf+strlen(buf), "Content-Type: %s\r\n", type);
sprintf(buf+strlen(buf), "Content-Length: %d\r\n\r\n", leght);
printf("headMsg = %s\n", buf);
int ret = send(fd, buf, strlen(buf), 0);
if (-1 == ret)
{
perror("send");
return -1;
}
return 0;
}
//
int sendFileInfo(int cfd, const char* name)
{
//open file
int fd = open(name, O_RDONLY);
while (1)
{
char buf[1024] = {0};
int len = read(fd, buf, sizeof(buf));
if (len > 0)
{
// printf("read res data =%s\n", buf);
send(cfd, buf, len, 0);
//because of send so fast , client accept data problem so, add sleep
usleep(100);
}
else if( len == 0)
{
printf("read file data done\n");
close(fd);
break;
}
else
{
perror("file read");
return -1;
}
}
return 0;
}
const char* getFileTypeByName(const char* name)
{
char* dot = strrchr(name, '.');
if (dot == NULL)
{
return "text/plain";
}
if ( strcasecmp(dot, ".html") == 0 )
{
return "text/html";
}
else if( strcasecmp(dot, ".img") == 0 )
{
return "application/x-img";
}
else if( strcasecmp(dot, ".jpg") == 0 )
{
return "image/jpeg";
}
else if( strcasecmp(dot, ".sor") == 0 )
{
return "text/plain";
}
else if( strcasecmp(dot, ".gif") == 0 )
{
return "image/gif";
}
else if( strcasecmp(dot, ".txt") == 0 )
{
return "text/plain";
}
else{
return "text/plain";
}
}
int sendDirInfo(int fd, const char*dirname)
{
/*
<html>
<head>
<title>this is dir</title>
</head>
<body>
<table>
<tr>
<td>文件名字</td>
<td>文件大小</td>
<td>文件时间</td>
</tr>
<tr>
<td>文件名字</td>
<td>文件大小</td>
<td>文件时间</td>
</tr>
</table>
</body>
</html>
send html info to client
*/
struct dirent** namelist; // struct dirent* p[size]; malloc memory
//for all file
int num = scandir(dirname, &namelist, NULL, alphasort);
char buf[4096] ={0};
// <meta http-equiv="Content-Type"content="text/html;charset=utf-8"> del luan ma
sprintf(buf, "<html> <meta http-equiv=\"Content-Type\"content=\"text/html;charset=utf-8\"><head><title>你好! %s</title></head><body><table><tr><td>FileName</td><td>FileSize</td></tr>", dirname);
char subBuf[1024]={0};
struct stat st;
for (size_t i = 0; i < num; i++)
{
char* name = namelist[i]->d_name;
sprintf(subBuf, "%s%s", dirname, name);
int ret = stat(subBuf, &st);
memset(subBuf, 0, sizeof(subBuf));
short size = st.st_size * 1.0;
if (size > 1024)
{
sprintf(subBuf, "%ldKB", size / 1024);
}
else if(size > 1024 * 1024)
{
sprintf(subBuf, "%ldMB", size / (1024*1024) );
}
else
{
sprintf(subBuf, "%ld", size);
}
if ( S_ISDIR(st.st_mode)) //the file is dir , so add '/'
{
sprintf(buf+strlen(buf), "<tr><td><a charset=\"UTF-8\" href=\"%s/\">%s</a></td><td>%s</td></tr>",name, name, subBuf);
}
else{
sprintf(buf+strlen(buf), "<tr><td><a charset=\"UTF-8\" href=\"%s\">%s</a></td><td>%s</td></tr>",name, name, subBuf);
}
send(fd, buf, strlen(buf), 0);
memset(buf, 0, sizeof(buf));
free(namelist[i]);
}
sprintf(buf, "</table></body></html>");
send(fd, buf, strlen(buf), 0);
free(namelist);
return 0;
}
void decodeMsg(char* to, char* from)
{
for ( ; *from != '\0'; ++to, ++from)
{
//isxdigit check char is hex ?
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2]))
{
*to = hextoDex(from[1]) * 16 + hextoDex(from[2]);
from += 2;
}
else
{
*to = *from;
}
}
*to = '\0';
}
int hextoDex(char c)
{
if (c >= '0' && c <= '9')
{
return c - '0';
}
else if ( c >= 'a' && c <= 'f')
{
return c - 'a' + 10;
}
else if ( c >= 'A' && c <= 'F')
{
return c - 'A' + 10;
}
}
//main.c
#include"server.h"
int main(int agrc, char* agrv[])
{
if (agrc < 3)
{
printf("please input ./a.out /wangcheng/res 8989\n");
return -1;
/* code */
}
//get port
unsigned short port = atoi(agrv[2]);
//change dir path cmd curdir invironment
char* path = agrv[1];
printf("main path = %s\n", path);
chdir(path);
int lfd = initListenFd(port);
epollRun(lfd);
return 0;
}