Linux C Http 文件服务器实现
来源:互联网 发布:刺客信条起源 知乎 编辑:程序博客网 时间:2024/04/29 02:50
备注:该文件服务器是根据Tinyhttpd的http源码的基础上实现的
整体思路: 创建socket监听请求,收到请求后线程分离,在线程中进行请求数据的解析;根据解析到的消息我们分类处理,目前该文件服务器可提供的文件包括html、css、jpg、gif;
1.监听请求,; 首先设置了端口复用,这个从字面理解就好了,就是说该端口上可以绑定多个socket;分配套接字,Http文件服务器是基于TCP传输协议; 初始化监听的地址结构体; 套接字与地址结构体绑定;
/*端口复用*/ int opt = 1; setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); /*分配一个TCP套接字*/ server_fd = Socket(AF_INET, SOCK_STREAM, 0); //TCP /*初始化*/ bzero(&server_addr, server_addr_len); //清空 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); server_addr.sin_addr.s_addr = htonl(INADDR_ANY); /*绑定*/ bind(server_fd, (struct sockaddr*)&server_addr, server_addr_len); listen(server_fd, 128); //最多可支持128个请求连接同时在服务器 监听套接字 上等待处理 printf("httpServer running on port %d \n", SERVER_PORT);
2.循环监听,这里的accept函数是从步骤1中的listen函数的等待请求队列里接受一个连接; 自定义结构体,打包了文件描述符,地址,地址大小; 创建线程并分离,所有的处理工作在do_work函数中;
/* 自定义结构体 struct t_client_info{ int t_fd; struct sockaddr_in t_addr; socklen_t t_addr_len; };*/while(1) { client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_addr_len); t_info.t_fd = client_fd; //该部分是我自定义的结构体 t_info.t_addr = client_addr; //用于打包客户端套接字信息 t_info.t_addr_len = client_addr_len; //方便传递到下个线程 /*线程*/ pthread_create(&t_client, NULL, do_work, (void *)&t_info); //创建线程 pthread_detach(t_client); }
3.do_work()函数
3.1 初始化
/*初始化*/ Pt_this = (struct t_client_info *)arg; //从父线程上传递来的自定义结构体 int client_fd = Pt_this->t_fd; struct sockaddr_in client__addr = Pt_this->t_addr; socklen_t client_addr_len = Pt_this->t_addr_len;
3.2 进入函数后不要盲目的直接调用开始处理请求行,我们先判断这个客户端的下一步动作,
如果recv返回了但是num为0,说明客户端套接字主动断开连接,这里直接goto到线程
收尾工作;
//判断缓冲区是否有东西可读,用MSG_PEEK,不会移动滑动窗口 int num=0; char c; num = recv(client_fd, &c, 1, MSG_PEEK); if (num != 1) goto workEND;
3.3 处理请求头的第一行数据,一般格式是 (方法 文件路径 协议版本)
例如:GET /index.html HTTP/1.1
注意: #define ISsp(x) isspace((int)(x)) //用于判断空格;
get_line()函数 获取一行数据,自定义函数,以 \r\n判断;
/*接收请求行的数据*/ numchars = get_line(client_fd, buf, sizeof(buf)); i=0; j=0; //获取方法字段 while (!ISsp(buf[i]) && (i<sizeof(method)-1)) //留一格用在‘\0’ { method[i] = buf[i]; i++; } j = i; //记录当前空格的位置 i = 0; method[i] = '\0'; //判断方法 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) //忽略大小写比较字符串,相同返回0; //这里是除了get和post以外都返回 goto workEND; //如果请求方法是POST,我们用调用cgi程序,cgi==1 调用cgi cgi==0 提供静态文件 if (strcasecmp(method, "POST") == 0) //POST cgi = 1;
3.4 获取URL,即获取客户端想要请求的文件路径;
//j当前的位置是method后面的空格 while (ISsp(buf[j]) && (j<numchars)) j++; //获取URL while (!ISsp(buf[j]) && (i<sizeof(url)-1) && (j<numchars)) { url[i] = buf[j]; i++; j++; } url[i] = '\0';
3.5 客户端请求的URL一般是请求的Http文件服务器的站点目录
如果请求的是 /index.html ===> 那么在我的设置里对应的 是/root/Project/WebFileServer/File/index.html
一定要区分客户端请求的根目录其实是服务器上的站点根目录
/*保存有效的url并加上默认主页索引, 默认在 */ /* #define ROOT /root/Project/WebFileServer/File%s */ sprintf(path, ROOT, url); //最后结尾是‘/’代表请求的是一个目录文件服务器应该返回该目录中的index.html if (path[strlen(path)-1] == '/') strcat(path, "index.html");//获取了URL后,再获取该URL的文件信息,如果获取失败,那就结束这次响应/*通过文件名path获取文件信息,并保存在st所指向的结构体stat中*/ if (stat(path, &st) == -1) //获取失败,丢弃剩下的数据 { while ((numchars>0) && strcmp("\n", buf)) //不是\n返回1 numchars = get_line(client_fd, buf, sizeof(buf)); /* 这里可以返回404页面 */ goto workEND; } if ((st.st_mode & S_IFMT) == S_IFDIR) //目录类型 strcat(path, "/index.html"); /*检查文件路径是否存在*/ int exist = access(path, F_OK); if (exist == -1) //不存在
3.6 返回文件根据cgi的值,cgi等于1代表是POST,我们提交到cgi程序处理;
cgi等于0代表是普通的GET,文件服务器直接返回静态文件;
/*返回文件*/ if (!cgi) //返回静态文件 server_file(client_fd, path); //自定义函数,返回静态文件 else { /* 这里实现调用cgi程序 */ }workEND: close(client_fd);
4.get_line()函数 : 该函数为Tinyhttpd源码,可以放心的使用
int get_line(int sock, char *buf, int size){ printf("##get_line## \n"); // 1.字符 2.字符\r 3.字符\r\n int i = 0; char c = '\0'; int n; /*把终止条件统一为 \n 换行符,标准化 buf 数组*/ while ((i < size - 1) && (c != '\n')) { /*一次仅接收一个字节*/ n = recv(sock, &c, 1, 0); if (n > 0) { /*收到 \r 则继续接收下个字节,因为换行符可能是 \r\n */ if (c == '\r') { /*使用 MSG_PEEK 标志使下一次读取依然可以得到这次读取的内容,可认为接收窗口不滑动*/ n = recv(sock, &c, 1, MSG_PEEK); /*但如果是换行符则把它吸收掉*/ if ((n > 0) && (c == '\n')) recv(sock, &c, 1, 0); else c = '\n'; } /*存到缓冲区*/ buf[i] = c; i++; } else c = '\n'; } buf[i] = '\0'; /*返回 buf 数组大小*/ return(i);}
5.提供静态文件(其中包括html、css、jpg、gif、下载文件)
void server_file(int fd, const char *filename){ printf("##server_file## \n"); char *string = NULL; string = &filename[strlen(filename)-4]; else if (strcasecmp(string, ".jpg") == 0) pic(fd, filename); else if (strcasecmp(string, ".gif") == 0) gif(fd, filename); else if (strcasecmp(string, ".css") == 0) css(fd, filename); string = &filename[strlen(filename)-5]; if (strcasecmp(string, ".html") == 0) html(fd, filename); /*如果不是,则下载*/ Download(fd, filename);}
6.这里给出只给出提供html文件的函数和下载文件函数;其他函数实现类似
/* 返回html,注意按照Http协议格式 | | | | | | | | | | | | v v v v v v v v v v v v*/void html(int fd, const char *filename){ FILE *resource = NULL; int numchars = 1; char buf[BUFSIZ]; buf[0] = 'A'; buf[1] = '\0'; while ((numchars > 0) && strcmp("\n", buf)) /*这里要读完请求头的所有数据*/ numchars = get_line(fd, buf, sizeof(buf)); resource = fopen(filename, "r"); if (resource == NULL) { fclose(resource); return ; } else { //注意:可以使用filename的信息来返回文件类型 strcpy(buf, "HTTP/1.1 200 OK\r\n"); send(fd, buf, strlen(buf), 0); strcpy(buf, SERVER_STRING); send(fd, buf, strlen(buf), 0); sprintf(buf, "Content-Type: text/html\r\n"); send(fd, buf, strlen(buf), 0); strcpy(buf, "\r\n"); send(fd, buf, strlen(buf), 0); fgets(buf, sizeof(buf), resource); while (!feof(resource)) //其功能是检测流上的文件结束符,如果文件结束,则返回非0值,否则返>回 0,文件结束符只能被clearerr()清除 { send(fd, buf, strlen(buf), 0); fgets(buf, sizeof(buf), resource); } } fclose(resource);}
下载文件需要注意的是返回类型,在html返回中服务器的发送的响应头里,Content-Type设置的是text/html;在下载类型中需要采用application/octet-stream,此为下载类型,用二进制传输文件;
注意:该下载文件实现是用的大文件分块传输,切记发送数据时要保证文件要关闭,不要在打开文件的状态传输数据,当客户端关闭时服务器会退出;
void Download(int fd, const char *filename){ printf("##Download## fd:%d \n", fd); FILE *resource = NULL; char *p = NULL; int numchars = 1; char buf[BUFSIZ]; long file_len=0; buf[0] = 'A'; buf[1] = '\0'; //拿出所有的请求消息 while ((numchars > 0) && strcmp("\n", buf)) numchars = get_line(fd, buf, sizeof(buf)); resource = fopen(filename, "r"); if (resource == NULL) { printf("file not found \n"); fclose(resource); return ; } fseek(resource, 0, SEEK_END); //一直到文件尾 file_len = ftell(resource); //长度 //rewind(resource); //将文件指针指向开头 fclose(resource); //注意:可以使用filename的信息来返回文件类型 strcpy(buf, "HTTP/1.1 200 OK\r\n"); send(fd, buf, strlen(buf), 0); strcpy(buf, SERVER_STRING); //自定义宏,服务器信息 send(fd, buf, strlen(buf), 0); sprintf(buf, "Connection: keep-alive\r\nContent-type:application/octet-stream\r\n"); send(fd, buf, strlen(buf), 0); sprintf(buf, "Content-Length: %ld\r\n\r\n", file_len); send(fd, buf, strlen(buf), 0); //在\r\n\r\n后面就该放数据了,这里采用大文件分块 //写入文件内容 long seek=0; long wait_len = file_len - seek; long send_len=0; double mb=0; if (wait_len > FILELEN) //自定义宏,暂时设为1MB send_len = FILELEN; else send_len = wait_len; /*动态分配空间,在这里没有做错误处理*/ p = (char *)malloc(sizeof(char)*send_len); int sendnum=0; while(1) { resource = fopen(filename, "r"); if (resource == NULL) { printf("file open false\n"); fclose(resource); return ; } if (!fread(p, sizeof(unsigned char), send_len, resource))break; fclose(resource); //(自定义函数,代码在下面)判断套接字是否断开 //如果在对方断开时继续send,将会出现一些异常 if (isConnection(fd) == -1) //0连接 -1断开 break; send(fd, p, send_len, 0); printf("sendnum:-%d-\n", sendnum); seek = seek + send_len; wait_len = file_len - seek; if (wait_len > FILELEN) send_len = FILELEN; else send_len = wait_len; mb = (double)((double)seek/(double)1048576); printf("seek:-%ld-===>-%lfMB-\n", seek, mb); } } free(p); p = NULL; printf("Download end.\n");//自定义函数,判断连接是否断开int isConnection(int fd){ struct tcp_info info; int len=sizeof(info); getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, (socklen_t *)&len); if((info.tcpi_state == TCP_ESTABLISHED)) return 0; else return -1;}
7.文件上传
首先是客户端,一般采用POST方法提交到文件服务器上的一个cgi程序,在步骤3.6中我们实现了提供静态文件,这里来描述如果利用cgi上传文件到服务器;
步骤为:客户端按下按键发送一个带有文件的POST请求 ==> 服务器接收请求并获取文件信息 ==> 服务器接收文件数据并存储
客户端的提交信息:
POST /CGI/down.cgi?filename=xxx HTTP/1.1\r\n
… …\r\n\r\n
(file data)
这里请求了根目录下CGI目录中的down.cgi这个程序,提交的文件名放在xxx这个位置,这部分是在客户端实现的;服务器收到请求调用down.cgi ; 该cgi可以用C写也可以用Shell,Python… … 本次采用的C语言实现cgi程序;
void execute_postFile(int fd, char *path, char *method, char *requestline){ char file[] = "/root/Project/WebFileServer/File/CanUseFile/"; //这是我保存文件的路径 char buf[BUFSIZ]; int numchars = 1; int content_len = -1; buf[0]='A';buf[1]="\0"; //如果是GET我们不处理;拿出所有请求数据 if (strcasecmp(method, "GET") == 0) while ((numchars>0) && strcmp("\n", buf)) numchars = get_line(fd, buf, sizeof(buf)); else if (strcasecmp(method, "POST") == 0) { /*请求头中我们需要Content-Length*/ numchars = get_line(fd, buf, sizeof(buf)); while ((numchars>0) && strcmp("\n", buf)) { buf[15] = '\0'; if (strcasecmp(buf, "Content-Length:") == 0) content_len = atoi(&(buf[16])); numchars = get_line(fd, buf, sizeof(buf)); printf("--%s-- \n", buf); } if (content_len == -1) { printf("bad request \n"); return ; } } else { //other method } /*获取requestline中的文件名, ?filename=xxx*/ char *post_string = requestline; char filename[BUFSIZ]; int filenum = 0; while ((*post_string != '?') && (*post_string != '\0')) post_string++; if (*post_string == '?') { post_string = post_string + 10; while (!ISsp(*post_string)) { filename[filenum] = *post_string; filenum++; post_string++; } filename[filenum] = '\0'; } strcat(file, filename); printf("file:-%s-", file); FILE *fp = NULL; fp = fopen(file, "wb"); //以二进制写入,创建文件 if (fp == NULL) { printf("file not found \n"); fclose(fp); return ; } int i; char c; for(i=0; i<content_len; i++) { recv(fd, &c, 1, 0); fwrite(&c, sizeof(unsigned char), 1, fp); } fclose(fp); /* 这里可以向客户端返回保存信息 */ }
感谢你耐心的看完了这篇文章,希望能够帮助到你。
- Linux C Http 文件服务器实现
- Netty 实现HTTP文件服务器
- Netty 实现HTTP文件服务器
- Netty 实现HTTP文件服务器
- linux 共享同一个文件服务器实现 文件服务器集群
- Linux c实现一个tcp文件服务器和客户端
- 基于golang http包实现的文件服务器
- Linux c socket 实现http
- linux 安装FastDFS实现分布式文件服务器
- Linux文件服务器
- twisted06 静态http文件服务器
- 文件服务器ftp, http
- Nginx配置Http文件服务器
- http-server轻量级文件服务器
- Linux下http协议实现 C
- linux c 实现简易HTTP服务器
- 文件服务器实现基本条件!
- NIO Socket实现文件服务器
- 企业资产经营管理信息系统
- windows端口占用
- 技术分享连载(八十一)
- 《python数据分析与挖掘实战》笔记-3.1代码问题
- 键盘回车添加数据
- Linux C Http 文件服务器实现
- 去掉QQ空间的装扮
- 空格添加 并且 判断重复bong置顶
- 上百份文字的检测与识别资源,包含数据集、code和paper
- 【Linux】CentOS7 常用命令集合
- Google 将于明年彻底关闭 Chrome Apps,以后就是 PWA 的天下了
- osip编译
- 解决eclipse中tomcat乱码问题
- JBPM工作流引擎