@Dubyoo
2014-06-17T13:38:32.000000Z
字数 10373
阅读 2512
未分类
参考资料:
深入理解HTTP协议
http://www.blogjava.net/zjusuyong/articles/304788.html
项目说明:该项目是一个基于TCP和HTTP的文件服务器,用户通过浏览器页面访问服务器的各目录或文件,并按照用户需要进行文件下载。
实现技术:用户使用浏览器访问文件服务器,服务器接收后,生成一个子进程来处理请求,通过分析客户端请求的URL读取文件或目录信息,并将处理结果通过HTTP发回客户端。
遇到的问题:
客户端获取文件或文件夹时,若文件名中含有空格' ',则发送给服务器的文件名实际上对应的是'%20',如:
客户端点击页面上显示的文件名"Ubuntu One"实际上服务器获取的URL文件名是"Ubuntu%20One"
原因:
在网络编程中,如果URL参数中含有特殊字符,如空格 ' ' 、 '#' 等
可能导致服务器端无法获得正确的参数值。这些字符被转换为 '%'+ ASCII码的两位16进制表示,如空格的ASCII码是32,即16进制的0x20,因此空格被替换成'%20'。
服务器接收后需要将之转换。
/*************************************************************************> File Name: FtpServer.c> Author: Dubyoo> Mail:wangwei1543@gmail.com> Created Time: Mon 16 Jun 2014 04:42:26 PM CST************************************************************************/#include <stdarg.h>#include <errno.h>#include <fcntl.h>#include <unistd.h>#include <string.h>#include <time.h>#include <sys/types.h>#include <sys/stat.h>#include <dirent.h>#include <errno.h>#include <netinet/in.h>#include <sys/socket.h>#include <resolv.h>#include <arpa/inet.h>#include <stdlib.h>#include <signal.h>#include <getopt.h>#define DEFAULTIP "127.0.0.1"#define DEFAULTPORT "80"#define DEFAULTBACK "10"#define DEFAULTDIR "/home"#define DEFAULTLOG "/tmp/das-server.log"void prterrmsg(char *msg);#define prterrmsg(msg) { perror(msg);abort(); }void wrterrmsg(char *msg);#define wrterrmsg(msg) { fputs(msg, logfp); fputs(strerror(errno),logfp);fflush(logfp); abort(); }void prtinfomsg(char *msg);#define prtinfomsg(msg) { fputs(msg, stdout); }void wrtinfomsg(char *msg);#define wrtinfomsg(msg) { fputs(msg, logfp); fflush(logfp); }#define MAXBUF 1024char buffer[MAXBUF + 1];char *host = 0;char *port = 0;char *back = 0;char *dirroot = 0;char *logdir = 0;unsigned char daemon_y_n = 0;FILE *logfp;#define MAXPATH 150/*-----------------------------------------------*--- dir_up - 返回 dirpath 所指目录的上一级目录*-----------------------------------------------*/char *dir_up(char *dirpath){static char Path[MAXPATH];int len;strcpy(Path, dirpath);len = strlen(Path);if (len > 1 && Path[len - 1] == '/')len--;while (Path[len - 1] != '/' && len > 1)len--;if(len > 1)len--;Path[len] = 0;return Path;}/*-------------------------------------------------------*--- AllocateMemory - 分配空间并把 d 所指的内容复制到 s*-------------------------------------------------------*/void AllocateMemory(char **s, int l, const char *d){*s = (char*)malloc(l + 1);bzero(*s, l + 1);memcpy(*s, d, l);}/*------------------------------------------------------*--- GiveResponse - 把 Path 所指的内容发送到 client_sock*------------------- 如果 Path 是一个目录,则列出目录内容*------------------- 如果 Path 是一个文件,则下载文件*------------------------------------------------------*/void GiveResponse(FILE * client_sock, char *Path){struct dirent *dirent;struct stat info;char Filename[MAXPATH];DIR *dir;int fd, len;char *p, *realPath, *realFilename, *nport;/* 获得实际工作目录或文件 */len = strlen(dirroot) + strlen(Path) + 1;realPath = (char*)malloc(len + 1);bzero(realPath, len + 1);sprintf(realPath, "%s/%s", dirroot, Path);/* 获得实际工作端口 */len = strlen(port) + 1;nport = (char*)malloc(len + 1);bzero(nport, len + 1);sprintf(nport, ":%s", port);/* 获得实际工作目录或文件的信息以判断是文件还是目录 */if (stat(realPath, &info)){fprintf(client_sock,"HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: close\r\n\r\n""<html><head>""<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />""<title>%d - %s</title></head>""<body><font size=+4>Linux 下目录访问服务器 </font><br><hr width=\"100%%\"><br><center>""<table border cols=3 width=\"100%%\">",errno,strerror(errno));fprintf(client_sock,"</table><font color=\"CC0000\" size=+2> 请向管理员咨询为何出现如下错误提示: \n%s %s</font></body></html>",Path, strerror(errno));goto out;}/* 处理浏览文件请求,即下载文件 */if (S_ISREG(info.st_mode)){fd = open(realPath, O_RDONLY);len = lseek(fd, 0, SEEK_END);p = (char *) malloc(len + 1);bzero(p, len + 1);lseek(fd, 0, SEEK_SET);read(fd, p, len);close(fd);fprintf(client_sock,"HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: keep-alive\r\nContent-type: application/*\r\nContent-Length:%d\r\n\r\n",len);fwrite(p, len, 1, client_sock);free(p);}else if (S_ISDIR(info.st_mode)){/* 处理浏览目录请求 */dir = opendir(realPath);fprintf(client_sock,"HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: close\r\n\r\n""<html>""<head>""<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />""<title>%s</title>""</head>""<body><font size=+4>Linux 下目录访问服务器 </font><br><hr width=\"100%%\"><br><center>""<table border cols=3 width=\"100%%\">",Path);fprintf(client_sock,"<caption><font size=+3> 目录 %s</font></caption>\n",Path);fprintf(client_sock,"<tr><td> 名称 </td><td> 大小 </td><td> 修改时间 </td></tr>\n");if (dir == 0){fprintf(client_sock,"</table><font color=\"CC0000\" size=+2>%s</font></body></html>",strerror(errno));return;}/* 读取目录里的所有内容 */while ((dirent = readdir(dir)) != 0){if (strcmp(Path, "/") == 0)sprintf(Filename, "/%s", dirent->d_name);elsesprintf(Filename, "%s/%s", Path, dirent->d_name);fprintf(client_sock, "<tr>");len = strlen(dirroot) + strlen(Filename) + 1;realFilename = (char*)malloc(len + 1);bzero(realFilename, len + 1);sprintf(realFilename, "%s/%s", dirroot, Filename);if (stat(realFilename, &info) == 0){if (strcmp(dirent->d_name, ".") == 0) //跳过自身目录continue;if (strcmp(dirent->d_name, "..") == 0) //返回上一级菜单fprintf(client_sock,"<td><a href=\"http://%s%s%s\">(parent)</a></td>",host, atoi(port) == 80 ? "" : nport,dir_up(Path));elsefprintf(client_sock,"<td><a href=\"http://%s%s%s\">%s</a></td>",host, atoi(port) == 80 ? "" : nport, Filename,dirent->d_name);if (S_ISDIR(info.st_mode))fprintf(client_sock, "<td> 目录 </td>");else if (S_ISREG(info.st_mode)) //普通文件显示大小{if(info.st_size < 1000){fprintf(client_sock, "<td>%dB</td>", (int)info.st_size);}else if(info.st_size >= 1000 && info.st_size < 1000000){fprintf(client_sock, "<td>%dKB</td>", ((int)info.st_size)/1000);}if(info.st_size >= 1000000 && info.st_size < 1000000000){fprintf(client_sock, "<td>%dMB</td>", ((int)info.st_size)/1000000);}if(info.st_size >= 1000000000 ){fprintf(client_sock, "<td>%dGB</td>", ((int)info.st_size)/1000000000);}}else if (S_ISLNK(info.st_mode))fprintf(client_sock, "<td> 链接 </td>");else if (S_ISCHR(info.st_mode))fprintf(client_sock, "<td> 字符设备 </td>");else if (S_ISBLK(info.st_mode))fprintf(client_sock, "<td> 块设备 </td>");else if (S_ISFIFO(info.st_mode))fprintf(client_sock, "<td>FIFO</td>");else if (S_ISSOCK(info.st_mode))fprintf(client_sock, "<td>Socket</td>");elsefprintf(client_sock, "<td>( 未知 )</td>");fprintf(client_sock, "<td>%s</td>", ctime(&info.st_ctime));}fprintf(client_sock, "</tr>\n");free(realFilename);}fprintf(client_sock, "</table></center></body></html>");}else{/* 既非常规文件又非目录,禁止访问 */fprintf(client_sock,"HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: close\r\n\r\n""<html>""<head>""<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />""<title>permission denied</title>""</head>""<body><font size=+4>Linux 下目录访问服务器 </font><br><hr width=\"100%%\"><br><center>""<table border cols=3 width=\"100%%\">");fprintf(client_sock,"</table>""<font color=\"CC0000\" size=+2> 你访问的资源 '%s' 被禁止访问,请联系管理员解决! </font>""</body></html>",Path);}out:free(realPath);free(nport);}/*------------------------------------------------------*--- getoption - 分析取出程序的参数*------------------------------------------------------*/void getoption(int argc, char **argv){int c, len;char *p = 0;opterr = 0;while (1){int option_index = 0;static struct option long_options[] ={{ "host", 1, 0, 0 },{ "port", 1, 0, 0 },{ "back", 1, 0, 0 },{ "dir", 1, 0, 0 },{ "log", 1, 0, 0 },{ "daemon", 0, 0, 0 },{ 0, 0, 0, 0}};/*本程序支持如下参数:*--host IP 地址 或者 -H IP 地址*--port 端口 或者 -P 端口* --back 监听数量 或者 -B 监听数量*--dir 网站根目录 或者 -D 网站根目录*--log 日志存放路径 或者 -L 日志存放路径* --daemon 使程序进入后台运行模式*/c = getopt_long(argc, argv, "H:P:B:D:L", long_options, &option_index);if (c == -1 || c == '?')break;if(optarg)len = strlen(optarg);elselen = 0;if ((!c && !(strcasecmp(long_options[option_index].name, "host"))) || c == 'H')//strcasecmp(const char* s1, const char* s2) 比较两个字符串,忽略大小写p = host = (char*)malloc(len + 1);else if ((!c && !(strcasecmp(long_options[option_index].name, "port"))) || c == 'P')p = port = (char*)malloc(len + 1);else if ((!c && !(strcasecmp(long_options[option_index].name, "back"))) || c == 'B')p = back = (char*)malloc(len + 1);else if ((!c && !(strcasecmp(long_options[option_index].name, "dir"))) || c == 'D')p = dirroot = (char*)malloc(len + 1);else if ((!c && !(strcasecmp(long_options[option_index].name, "log"))) || c == 'L')p = logdir = (char*)malloc(len + 1);else if ((!c && !(strcasecmp(long_options[option_index].name, "daemon")))){daemon_y_n = 1;continue;}elsebreak;bzero(p, len + 1);memcpy(p, optarg, len);}}int main(int argc, char **argv){struct sockaddr_in addr;int sock_fd;socklen_t addrlen;/* 获得程序工作的参数,如 IP 、端口、监听数、网页根目录、目录存放位置等 */getoption(argc, argv);if (!host){addrlen = strlen(DEFAULTIP);AllocateMemory(&host, addrlen, DEFAULTIP);}if (!port){addrlen = strlen(DEFAULTPORT);AllocateMemory(&port, addrlen, DEFAULTPORT);}if (!back){addrlen = strlen(DEFAULTBACK);AllocateMemory(&back, addrlen, DEFAULTBACK);}if (!dirroot){addrlen = strlen(DEFAULTDIR);AllocateMemory(&dirroot, addrlen, DEFAULTDIR);}if (!logdir){addrlen = strlen(DEFAULTLOG);AllocateMemory(&logdir, addrlen, DEFAULTLOG);}printf("host=%s port=%s back=%s dirroot=%s logdir=%s %s是后台工作模式 ( 进程 ID :%d)\n", host, port, back, dirroot, logdir, daemon_y_n?"":"不", getpid());/* fork() 两次处于后台工作模式下 */if (daemon_y_n){if (fork())exit(0);if (fork())exit(0);close(0), close(1), close(2);logfp = fopen(logdir, "a+");if (!logfp)exit(0);}/* 处理子进程退出以免产生僵尸进程 */signal(SIGCHLD, SIG_IGN);/* 创建 socket */if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0){if (!daemon_y_n){prterrmsg("socket()");}else{wrterrmsg("socket()");}}/* 设置端口快速重用 */addrlen = 1;setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen,sizeof(addrlen));addr.sin_family = AF_INET;addr.sin_port = htons(atoi(port));addr.sin_addr.s_addr = INADDR_ANY;//addr.sin_addr.s_addr = inet_addr(host);addrlen = sizeof(struct sockaddr_in);/* 绑定地址、端口等信息 */if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0){if (!daemon_y_n){prterrmsg("bind()");}else{wrterrmsg("bind()");}}/* 开启监听 */if (listen(sock_fd, atoi(back)) < 0){if (!daemon_y_n){prterrmsg("listen()");}else{wrterrmsg("listen()");}}while (1){int len;int new_fd;addrlen = sizeof(struct sockaddr_in);/* 接受新连接请求 */new_fd = accept(sock_fd, (struct sockaddr*)(&addr), &addrlen);if (new_fd < 0){if (!daemon_y_n){prterrmsg("accept()");}else{wrterrmsg("accept()");}break;}bzero(buffer, MAXBUF + 1);sprintf(buffer, " 连接来自于 : %s:%d\n",inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));if (!daemon_y_n){prtinfomsg(buffer);}else{wrtinfomsg(buffer);}/* 产生一个子进程去处理请求,当前进程继续等待新的连接到来 */if (!fork()){bzero(buffer, MAXBUF + 1);if ((len = recv(new_fd, buffer, MAXBUF, 0)) > 0){FILE *ClientFP = fdopen(new_fd, "w");if (ClientFP == NULL){if (!daemon_y_n){prterrmsg("fdopen()");}else{prterrmsg("fdopen()");}}else{char Req[MAXPATH + 1] = "";sscanf(buffer, "GET %s HTTP", Req);bzero(buffer, MAXBUF + 1);sprintf(buffer, " 请求取文件 : \"%s\"\n", Req);if (!daemon_y_n){prtinfomsg(buffer);}else{wrtinfomsg(buffer);}/* 处理用户请求 */GiveResponse(ClientFP, Req);fclose(ClientFP);}}exit(0);}close(new_fd);}close(sock_fd);return 0;}