[关闭]
@Dubyoo 2014-06-17T21:38:32.000000Z 字数 10373 阅读 2264

2014-06-17 FTP文件服务器(TCP,HTTP)

未分类


参考资料:
深入理解HTTP协议
http://www.blogjava.net/zjusuyong/articles/304788.html



遇到的问题:
客户端获取文件或文件夹时,若文件名中含有空格' ',则发送给服务器的文件名实际上对应的是'%20',如:

  1. 客户端点击页面上显示的文件名"Ubuntu One"
  2. 实际上服务器获取的URL文件名是"Ubuntu%20One"

原因:
在网络编程中,如果URL参数中含有特殊字符,如空格 ' ''#'
可能导致服务器端无法获得正确的参数值。这些字符被转换为 '%'+ ASCII码的两位16进制表示,如空格的ASCII码是32,即16进制的0x20,因此空格被替换成'%20'
服务器接收后需要将之转换。


  1. /*************************************************************************
  2. > File Name: FtpServer.c
  3. > Author: Dubyoo
  4. > Mail:wangwei1543@gmail.com
  5. > Created Time: Mon 16 Jun 2014 04:42:26 PM CST
  6. ************************************************************************/
  7. #include <stdarg.h>
  8. #include <errno.h>
  9. #include <fcntl.h>
  10. #include <unistd.h>
  11. #include <string.h>
  12. #include <time.h>
  13. #include <sys/types.h>
  14. #include <sys/stat.h>
  15. #include <dirent.h>
  16. #include <errno.h>
  17. #include <netinet/in.h>
  18. #include <sys/socket.h>
  19. #include <resolv.h>
  20. #include <arpa/inet.h>
  21. #include <stdlib.h>
  22. #include <signal.h>
  23. #include <getopt.h>
  24. #define DEFAULTIP "127.0.0.1"
  25. #define DEFAULTPORT "80"
  26. #define DEFAULTBACK "10"
  27. #define DEFAULTDIR "/home"
  28. #define DEFAULTLOG "/tmp/das-server.log"
  29. void prterrmsg(char *msg);
  30. #define prterrmsg(msg) { perror(msg);abort(); }
  31. void wrterrmsg(char *msg);
  32. #define wrterrmsg(msg) { fputs(msg, logfp); fputs(strerror(errno),logfp);fflush(logfp); abort(); }
  33. void prtinfomsg(char *msg);
  34. #define prtinfomsg(msg) { fputs(msg, stdout); }
  35. void wrtinfomsg(char *msg);
  36. #define wrtinfomsg(msg) { fputs(msg, logfp); fflush(logfp); }
  37. #define MAXBUF 1024
  38. char buffer[MAXBUF + 1];
  39. char *host = 0;
  40. char *port = 0;
  41. char *back = 0;
  42. char *dirroot = 0;
  43. char *logdir = 0;
  44. unsigned char daemon_y_n = 0;
  45. FILE *logfp;
  46. #define MAXPATH 150
  47. /*-----------------------------------------------
  48. *--- dir_up - 返回 dirpath 所指目录的上一级目录
  49. *-----------------------------------------------
  50. */
  51. char *dir_up(char *dirpath)
  52. {
  53. static char Path[MAXPATH];
  54. int len;
  55. strcpy(Path, dirpath);
  56. len = strlen(Path);
  57. if (len > 1 && Path[len - 1] == '/')
  58. len--;
  59. while (Path[len - 1] != '/' && len > 1)
  60. len--;
  61. if(len > 1)
  62. len--;
  63. Path[len] = 0;
  64. return Path;
  65. }
  66. /*-------------------------------------------------------
  67. *--- AllocateMemory - 分配空间并把 d 所指的内容复制到 s
  68. *-------------------------------------------------------
  69. */
  70. void AllocateMemory(char **s, int l, const char *d)
  71. {
  72. *s = (char*)malloc(l + 1);
  73. bzero(*s, l + 1);
  74. memcpy(*s, d, l);
  75. }
  76. /*------------------------------------------------------
  77. *--- GiveResponse - 把 Path 所指的内容发送到 client_sock
  78. *------------------- 如果 Path 是一个目录,则列出目录内容
  79. *------------------- 如果 Path 是一个文件,则下载文件
  80. *------------------------------------------------------
  81. */
  82. void GiveResponse(FILE * client_sock, char *Path)
  83. {
  84. struct dirent *dirent;
  85. struct stat info;
  86. char Filename[MAXPATH];
  87. DIR *dir;
  88. int fd, len;
  89. char *p, *realPath, *realFilename, *nport;
  90. /* 获得实际工作目录或文件 */
  91. len = strlen(dirroot) + strlen(Path) + 1;
  92. realPath = (char*)malloc(len + 1);
  93. bzero(realPath, len + 1);
  94. sprintf(realPath, "%s/%s", dirroot, Path);
  95. /* 获得实际工作端口 */
  96. len = strlen(port) + 1;
  97. nport = (char*)malloc(len + 1);
  98. bzero(nport, len + 1);
  99. sprintf(nport, ":%s", port);
  100. /* 获得实际工作目录或文件的信息以判断是文件还是目录 */
  101. if (stat(realPath, &info))
  102. {
  103. fprintf(client_sock,
  104. "HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: close\r\n\r\n"
  105. "<html><head>"
  106. "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
  107. "<title>%d - %s</title></head>"
  108. "<body><font size=+4>Linux 下目录访问服务器 </font><br><hr width=\"100%%\"><br><center>"
  109. "<table border cols=3 width=\"100%%\">",
  110. errno,strerror(errno));
  111. fprintf(client_sock,
  112. "</table><font color=\"CC0000\" size=+2> 请向管理员咨询为何出现如下错误提示: \n%s %s</font></body></html>",
  113. Path, strerror(errno));
  114. goto out;
  115. }
  116. /* 处理浏览文件请求,即下载文件 */
  117. if (S_ISREG(info.st_mode))
  118. {
  119. fd = open(realPath, O_RDONLY);
  120. len = lseek(fd, 0, SEEK_END);
  121. p = (char *) malloc(len + 1);
  122. bzero(p, len + 1);
  123. lseek(fd, 0, SEEK_SET);
  124. read(fd, p, len);
  125. close(fd);
  126. fprintf(client_sock,
  127. "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",
  128. len);
  129. fwrite(p, len, 1, client_sock);
  130. free(p);
  131. }
  132. else if (S_ISDIR(info.st_mode))
  133. {
  134. /* 处理浏览目录请求 */
  135. dir = opendir(realPath);
  136. fprintf(client_sock,
  137. "HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: close\r\n\r\n"
  138. "<html>"
  139. "<head>"
  140. "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
  141. "<title>%s</title>"
  142. "</head>"
  143. "<body><font size=+4>Linux 下目录访问服务器 </font><br><hr width=\"100%%\"><br><center>"
  144. "<table border cols=3 width=\"100%%\">",
  145. Path);
  146. fprintf(client_sock,
  147. "<caption><font size=+3> 目录 %s</font></caption>\n",
  148. Path);
  149. fprintf(client_sock,
  150. "<tr><td> 名称 </td><td> 大小 </td><td> 修改时间 </td></tr>\n"
  151. );
  152. if (dir == 0)
  153. {
  154. fprintf(client_sock,
  155. "</table><font color=\"CC0000\" size=+2>%s</font></body></html>",
  156. strerror(errno));
  157. return;
  158. }
  159. /* 读取目录里的所有内容 */
  160. while ((dirent = readdir(dir)) != 0)
  161. {
  162. if (strcmp(Path, "/") == 0)
  163. sprintf(Filename, "/%s", dirent->d_name);
  164. else
  165. sprintf(Filename, "%s/%s", Path, dirent->d_name);
  166. fprintf(client_sock, "<tr>");
  167. len = strlen(dirroot) + strlen(Filename) + 1;
  168. realFilename = (char*)malloc(len + 1);
  169. bzero(realFilename, len + 1);
  170. sprintf(realFilename, "%s/%s", dirroot, Filename);
  171. if (stat(realFilename, &info) == 0)
  172. {
  173. if (strcmp(dirent->d_name, ".") == 0) //跳过自身目录
  174. continue;
  175. if (strcmp(dirent->d_name, "..") == 0) //返回上一级菜单
  176. fprintf(client_sock,
  177. "<td><a href=\"http://%s%s%s\">(parent)</a></td>",
  178. host, atoi(port) == 80 ? "" : nport,
  179. dir_up(Path));
  180. else
  181. fprintf(client_sock,
  182. "<td><a href=\"http://%s%s%s\">%s</a></td>",
  183. host, atoi(port) == 80 ? "" : nport, Filename,
  184. dirent->d_name);
  185. if (S_ISDIR(info.st_mode))
  186. fprintf(client_sock, "<td> 目录 </td>");
  187. else if (S_ISREG(info.st_mode)) //普通文件显示大小
  188. {
  189. if(info.st_size < 1000)
  190. {
  191. fprintf(client_sock, "<td>%dB</td>", (int)info.st_size);
  192. }
  193. else if(info.st_size >= 1000 && info.st_size < 1000000)
  194. {
  195. fprintf(client_sock, "<td>%dKB</td>", ((int)info.st_size)/1000);
  196. }
  197. if(info.st_size >= 1000000 && info.st_size < 1000000000)
  198. {
  199. fprintf(client_sock, "<td>%dMB</td>", ((int)info.st_size)/1000000);
  200. }
  201. if(info.st_size >= 1000000000 )
  202. {
  203. fprintf(client_sock, "<td>%dGB</td>", ((int)info.st_size)/1000000000);
  204. }
  205. }
  206. else if (S_ISLNK(info.st_mode))
  207. fprintf(client_sock, "<td> 链接 </td>");
  208. else if (S_ISCHR(info.st_mode))
  209. fprintf(client_sock, "<td> 字符设备 </td>");
  210. else if (S_ISBLK(info.st_mode))
  211. fprintf(client_sock, "<td> 块设备 </td>");
  212. else if (S_ISFIFO(info.st_mode))
  213. fprintf(client_sock, "<td>FIFO</td>");
  214. else if (S_ISSOCK(info.st_mode))
  215. fprintf(client_sock, "<td>Socket</td>");
  216. else
  217. fprintf(client_sock, "<td>( 未知 )</td>");
  218. fprintf(client_sock, "<td>%s</td>", ctime(&info.st_ctime));
  219. }
  220. fprintf(client_sock, "</tr>\n");
  221. free(realFilename);
  222. }
  223. fprintf(client_sock, "</table></center></body></html>");
  224. }
  225. else
  226. {
  227. /* 既非常规文件又非目录,禁止访问 */
  228. fprintf(client_sock,
  229. "HTTP/1.1 200 OK\r\nServer: by Dubyoo\r\nConnection: close\r\n\r\n"
  230. "<html>"
  231. "<head>"
  232. "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />"
  233. "<title>permission denied</title>"
  234. "</head>"
  235. "<body><font size=+4>Linux 下目录访问服务器 </font><br><hr width=\"100%%\"><br><center>"
  236. "<table border cols=3 width=\"100%%\">"
  237. );
  238. fprintf(client_sock,
  239. "</table>"
  240. "<font color=\"CC0000\" size=+2> 你访问的资源 '%s' 被禁止访问,请联系管理员解决! </font>"
  241. "</body></html>",
  242. Path);
  243. }
  244. out:
  245. free(realPath);
  246. free(nport);
  247. }
  248. /*------------------------------------------------------
  249. *--- getoption - 分析取出程序的参数
  250. *------------------------------------------------------
  251. */
  252. void getoption(int argc, char **argv)
  253. {
  254. int c, len;
  255. char *p = 0;
  256. opterr = 0;
  257. while (1)
  258. {
  259. int option_index = 0;
  260. static struct option long_options[] =
  261. {
  262. { "host", 1, 0, 0 },
  263. { "port", 1, 0, 0 },
  264. { "back", 1, 0, 0 },
  265. { "dir", 1, 0, 0 },
  266. { "log", 1, 0, 0 },
  267. { "daemon", 0, 0, 0 },
  268. { 0, 0, 0, 0}
  269. };
  270. /*本程序支持如下参数:
  271. *--host IP 地址 或者 -H IP 地址
  272. *--port 端口 或者 -P 端口
  273. * --back 监听数量 或者 -B 监听数量
  274. *--dir 网站根目录 或者 -D 网站根目录
  275. *--log 日志存放路径 或者 -L 日志存放路径* --daemon 使程序进入后台运行模式
  276. */
  277. c = getopt_long(argc, argv, "H:P:B:D:L", long_options, &option_index);
  278. if (c == -1 || c == '?')
  279. break;
  280. if(optarg)
  281. len = strlen(optarg);
  282. else
  283. len = 0;
  284. if ((!c && !(strcasecmp(long_options[option_index].name, "host"))) || c == 'H')
  285. //strcasecmp(const char* s1, const char* s2) 比较两个字符串,忽略大小写
  286. p = host = (char*)malloc(len + 1);
  287. else if ((!c && !(strcasecmp(long_options[option_index].name, "port"))) || c == 'P')
  288. p = port = (char*)malloc(len + 1);
  289. else if ((!c && !(strcasecmp(long_options[option_index].name, "back"))) || c == 'B')
  290. p = back = (char*)malloc(len + 1);
  291. else if ((!c && !(strcasecmp(long_options[option_index].name, "dir"))) || c == 'D')
  292. p = dirroot = (char*)malloc(len + 1);
  293. else if ((!c && !(strcasecmp(long_options[option_index].name, "log"))) || c == 'L')
  294. p = logdir = (char*)malloc(len + 1);
  295. else if ((!c && !(strcasecmp(long_options[option_index].name, "daemon"))))
  296. {
  297. daemon_y_n = 1;
  298. continue;
  299. }
  300. else
  301. break;
  302. bzero(p, len + 1);
  303. memcpy(p, optarg, len);
  304. }
  305. }
  306. int main(int argc, char **argv)
  307. {
  308. struct sockaddr_in addr;
  309. int sock_fd;
  310. socklen_t addrlen;
  311. /* 获得程序工作的参数,如 IP 、端口、监听数、网页根目录、目录存放位置等 */
  312. getoption(argc, argv);
  313. if (!host)
  314. {
  315. addrlen = strlen(DEFAULTIP);
  316. AllocateMemory(&host, addrlen, DEFAULTIP);
  317. }
  318. if (!port)
  319. {
  320. addrlen = strlen(DEFAULTPORT);
  321. AllocateMemory(&port, addrlen, DEFAULTPORT);
  322. }
  323. if (!back)
  324. {
  325. addrlen = strlen(DEFAULTBACK);
  326. AllocateMemory(&back, addrlen, DEFAULTBACK);
  327. }
  328. if (!dirroot)
  329. {
  330. addrlen = strlen(DEFAULTDIR);
  331. AllocateMemory(&dirroot, addrlen, DEFAULTDIR);
  332. }
  333. if (!logdir)
  334. {
  335. addrlen = strlen(DEFAULTLOG);
  336. AllocateMemory(&logdir, addrlen, DEFAULTLOG);
  337. }
  338. printf("host=%s port=%s back=%s dirroot=%s logdir=%s %s是后台工作模式 ( 进程 ID :%d)\n", host, port, back, dirroot, logdir, daemon_y_n?"":"不", getpid());
  339. /* fork() 两次处于后台工作模式下 */
  340. if (daemon_y_n)
  341. {
  342. if (fork())
  343. exit(0);
  344. if (fork())
  345. exit(0);
  346. close(0), close(1), close(2);
  347. logfp = fopen(logdir, "a+");
  348. if (!logfp)
  349. exit(0);
  350. }
  351. /* 处理子进程退出以免产生僵尸进程 */
  352. signal(SIGCHLD, SIG_IGN);
  353. /* 创建 socket */
  354. if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0)
  355. {
  356. if (!daemon_y_n)
  357. {
  358. prterrmsg("socket()");
  359. }
  360. else
  361. {
  362. wrterrmsg("socket()");
  363. }
  364. }
  365. /* 设置端口快速重用 */
  366. addrlen = 1;
  367. setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen,
  368. sizeof(addrlen));
  369. addr.sin_family = AF_INET;
  370. addr.sin_port = htons(atoi(port));
  371. addr.sin_addr.s_addr = INADDR_ANY;
  372. //addr.sin_addr.s_addr = inet_addr(host);
  373. addrlen = sizeof(struct sockaddr_in);
  374. /* 绑定地址、端口等信息 */
  375. if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0)
  376. {
  377. if (!daemon_y_n)
  378. {
  379. prterrmsg("bind()");
  380. }
  381. else
  382. {
  383. wrterrmsg("bind()");
  384. }
  385. }
  386. /* 开启监听 */
  387. if (listen(sock_fd, atoi(back)) < 0)
  388. {
  389. if (!daemon_y_n)
  390. {
  391. prterrmsg("listen()");
  392. }
  393. else
  394. {
  395. wrterrmsg("listen()");
  396. }
  397. }
  398. while (1)
  399. {
  400. int len;
  401. int new_fd;
  402. addrlen = sizeof(struct sockaddr_in);
  403. /* 接受新连接请求 */
  404. new_fd = accept(sock_fd, (struct sockaddr*)(&addr), &addrlen);
  405. if (new_fd < 0)
  406. {
  407. if (!daemon_y_n)
  408. {
  409. prterrmsg("accept()");
  410. }
  411. else
  412. {
  413. wrterrmsg("accept()");
  414. }
  415. break;
  416. }
  417. bzero(buffer, MAXBUF + 1);
  418. sprintf(buffer, " 连接来自于 : %s:%d\n",
  419. inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  420. if (!daemon_y_n)
  421. {
  422. prtinfomsg(buffer);
  423. }
  424. else
  425. {
  426. wrtinfomsg(buffer);
  427. }
  428. /* 产生一个子进程去处理请求,当前进程继续等待新的连接到来 */
  429. if (!fork())
  430. {
  431. bzero(buffer, MAXBUF + 1);
  432. if ((len = recv(new_fd, buffer, MAXBUF, 0)) > 0)
  433. {
  434. FILE *ClientFP = fdopen(new_fd, "w");
  435. if (ClientFP == NULL)
  436. {
  437. if (!daemon_y_n)
  438. {
  439. prterrmsg("fdopen()");
  440. }
  441. else
  442. {
  443. prterrmsg("fdopen()");
  444. }
  445. }
  446. else
  447. {
  448. char Req[MAXPATH + 1] = "";
  449. sscanf(buffer, "GET %s HTTP", Req);
  450. bzero(buffer, MAXBUF + 1);
  451. sprintf(buffer, " 请求取文件 : \"%s\"\n", Req);
  452. if (!daemon_y_n)
  453. {
  454. prtinfomsg(buffer);
  455. }
  456. else
  457. {
  458. wrtinfomsg(buffer);
  459. }
  460. /* 处理用户请求 */
  461. GiveResponse(ClientFP, Req);
  462. fclose(ClientFP);
  463. }
  464. }
  465. exit(0);
  466. }
  467. close(new_fd);
  468. }
  469. close(sock_fd);
  470. return 0;
  471. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注