需求:使用B/S模型来访问主机中的文件(包括目录)
准备一个favicon.ico 文件放置到 服务器提供访问的资源目录中。
浏览器在请求图片的同时,会请求一个ico图标,用于浏览器标签文字部分前端的小图标显示。
这个ico的文件名固定——favicon.ico。因此,自行准备一个ico文件,放置于服务器提供给浏览器访问的目标目录即可。
返回值 必须 要检查!!!
尤其是 read、write、recv、send、recvfrom、sendto 函数,返回的errno为 EINTR和EAGAIN时,不代表是一个错误,但会严重影响程序运行结果!通常使用continue来处理这种情况即可。
本案例主要用到的是读数据,所以只考虑 GET 方法,那么浏览器发送来的数据也只需要读请求行即可。
读第一行后,其他数据清理掉。如果读到的是0,那么表示浏览器断开连接,使用 epoll_ctl() 函数将这个结点摘下。
读到第一行后要对第一行进行解析:使用 sprintf() 函数搭配正则表达式%[^ ]
使用,解析三块内容。
注意解析到的文件路径需要解码
**如果路径是 / 代表网址中没有指定访问的文件,即访问 ./ ,要将解析到的文件路径改为 ./ **
随后分析请求文件,封装了 http_request() 函数
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXSIZE 2048
// 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';
}
// 通过文件名获取文件的类型
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 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); //#include 模拟读一次/拷贝读一次. 读完 socke 中还有.
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;
}
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);
}
// 创建服务器地址结构 IP+port
struct sockaddr_in srv_addr;
bzero(&srv_addr, sizeof(srv_addr));
srv_addr.sin_family = AF_INET;
srv_addr.sin_port = htons(port);
srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 给 lfd 绑定地址结构
int ret = bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
if (ret == -1) {
perror("bind error");
exit(1);
}
// 设置监听上限
ret = listen(lfd, 128);
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 clt_addr;
socklen_t clt_addr_len = sizeof(clt_addr);
int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
if (cfd == -1) {
perror("accept error");
exit(1);
}
// 打印客户端IP+port
char client_ip[64] = {0};
printf("New Client IP: %s, Port: %d, cfd = %d\n",
inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)),
ntohs(clt_addr.sin_port), cfd);
// 设置 cfd 非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
// 将新节点cfd 挂到 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);
}
}
void disconnect(int cfd, int epfd)
{
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
if(-1 == ret)
{
perror("epoll_ctl error");
exit(0);
}
close(cfd);
}
void send_http_response(int cfd, int num, const char* desc, const char* type, int size)
{
char buf[BUFSIZ] = {0};
sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", num, desc);
sprintf(buf+strlen(buf), "%s:%s\r\n", "Content-Type", type);
sprintf(buf+strlen(buf), "%s:%d\r\n", "Content-Length", size);
sprintf(buf+strlen(buf), "\r\n");
//printf("写回给client的http应答:|%s|\n", buf);
//发送给浏览器
send(cfd, buf, strlen(buf), 0);
printf("%s",buf);
}
void send_file(int cfd, const char* file)
{
int n;
char buf[BUFSIZ] = {0};
int fd = open(file, O_RDONLY);
if(-1 == fd)
{
perror("open error");
exit(1);
}
while((n = read(fd, buf, sizeof(buf))) > 0)
{
send(cfd, buf, n, 0);
printf("%s", buf);
}
close(fd);
}
void send_dir(int cfd, const char* dirname)
{
int ret;
char path[256] = {0};
char enstr[1024] = {0};
char buf[BUFSIZ];
sprintf(buf, "当前目录:%s ", dirname);
sprintf(buf+strlen(buf), "%s
", dirname);
struct dirent** namelist = NULL;
int num = scandir(dirname, &namelist, NULL, alphasort);
for(int i=0 ;i<num ;++i)
{
char* name = namelist[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),
"%s %ld ",
enstr, name, (long)st.st_size);
} else if(S_ISDIR(st.st_mode)) { // 如果是目录
sprintf(buf+strlen(buf),
"%s/ %ld ",
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), "
");
send(cfd, buf, strlen(buf), 0);
}
void send_err(int cfd, int no, const char* desc, const char* sent)
{
char buf[BUFSIZ] = {0};
sprintf(buf, "%s %d %s\r\n", "HTTP/1.1", no, desc);
sprintf(buf+strlen(buf), "%s:%s\r\n", "Content-Type", get_file_type(".html"));
sprintf(buf+strlen(buf), "%s:%d\r\n", "Content-Length", -1);
sprintf(buf+strlen(buf), "\r\n");
sprintf(buf+strlen(buf), "%s ", desc);
sprintf(buf+strlen(buf), "%d %s
%s