@Dubyoo
2014-06-17T21:38:32.000000Z
字数 10373
阅读 2264
未分类
参考资料:
深入理解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 1024
char 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);
else
sprintf(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));
else
fprintf(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>");
else
fprintf(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);
else
len = 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;
}
else
break;
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;
}