Linux网络编程:一个HTTP协议的目录浏览和文件下载服务器

服务器源代码如下:

  1. #include <stdarg.h>
  2. #include <errno.h>
  3. #include <stdio.h>
  4. #include <fcntl.h>
  5. #include <unistd.h>
  6. #include <string.h>
  7. #include <time.h>
  8. #include <sys/types.h>
  9. #include <sys/stat.h>
  10. #include <dirent.h>
  11. #include <errno.h>
  12. #include <netinet/in.h>
  13. #include <sys/socket.h>
  14. #include <resolv.h>
  15. #include <arpa/inet.h>
  16. #include <stdlib.h>
  17. #include <signal.h>
  18. #include <getopt.h>
  19. #define DEFAULTIP "127.0.0.1"
  20. #define DEFAULTPORT "80"
  21. #define DEFAULTBACK "10"
  22. #define DEFAULTDIR "/home"
  23. #define DEFAULTLOG "/tmp/das-server.log"
  24. void prterrmsg(char *msg);
  25. #define prterrmsg(msg)        { perror(msg); abort(); }
  26. void wrterrmsg(char *msg);
  27. #define wrterrmsg(msg)        { fputs(msg, logfp); fputs(strerror(errno), logfp);fflush(logfp); abort(); }
  28. void prtinfomsg(char *msg);
  29. #define prtinfomsg(msg)        { fputs(msg, stdout);  }
  30. void wrtinfomsg(char *msg);
  31. #define wrtinfomsg(msg)        {  fputs(msg, logfp); fflush(logfp);}
  32. #define MAXBUF        1024
  33. char buffer[MAXBUF + 1];
  34. char *host = 0;
  35. char *port = 0;
  36. char *back = 0;
  37. char *dirroot = 0;
  38. char *logdir = 0;
  39. unsigned char daemon_y_n = 0;
  40. FILE *logfp;
  41. #define MAXPATH        150
  42. /*----------------------------------------
  43. *--- dir_up - 查找dirpath所指目录的上一级目录
  44. *----------------------------------------
  45. */
  46. char *dir_up(char *dirpath)
  47. {
  48.     static char Path[MAXPATH];
  49.     int len;
  50.     strcpy(Path, dirpath);
  51.     len = strlen(Path);
  52.     if (len > 1 && Path[len - 1] == '/')
  53.         len--;
  54.     while (Path[len - 1] != '/' && len > 1)
  55.         len--;
  56.     Path[len] = 0;
  57.     return Path;
  58. }
  59. /*------------------------------------------------------
  60. *--- AllocateMemory - 分配空间并把d所指的内容复制
  61. *------------------------------------------------------
  62. */
  63. void AllocateMemory(char **s, int l, char *d)
  64. {
  65.     *s = malloc(l + 1);
  66.     bzero(*s, l + 1);
  67.     memcpy(*s, d, l);
  68. }
  69. /************关于本文档********************************************
  70. *filename: das-server.c
  71. *purpose: 这是在Linux下用C语言写的目录访问服务器,支持目录浏览和文件下载
  72. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
  73. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
  74. *date time:2007-01-26 19:32
  75. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
  76. * 但请遵循GPL
  77. *Thanks to: Google.com
  78. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
  79. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
  80. *********************************************************************/
  81. /*------------------------------------------------------
  82. *--- GiveResponse - 把Path所指的内容发送到client_sock去
  83. *-------------------如果Path是一个目录,则列出目录内容
  84. *-------------------如果Path是一个文件,则下载文件
  85. *------------------------------------------------------
  86. */
  87. void GiveResponse(FILE * client_sock, char *Path)
  88. {
  89.     struct dirent *dirent;
  90.     struct stat info;
  91.     char Filename[MAXPATH];
  92.     DIR *dir;
  93.     int fd, len, ret;
  94.     char *p, *realPath, *realFilename, *nport;
  95.     /* 获得实际工作目录或文件 */
  96.     len = strlen(dirroot) + strlen(Path) + 1;
  97.     realPath = malloc(len + 1);
  98.     bzero(realPath, len + 1);
  99.     sprintf(realPath, "%s/%s", dirroot, Path);
  100.     /* 获得实际工作端口 */
  101.     len = strlen(port) + 1;
  102.     nport = malloc(len + 1);
  103.     bzero(nport, len + 1);
  104.     sprintf(nport, ":%s", port);
  105.     /* 获得实际工作目录或文件的信息以判断是文件还是目录 */
  106.     if (stat(realPath, &info)) {
  107.         fprintf(client_sock,
  108.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: close\r\n\r\n<html><head><title>%d - %s</title></head>"
  109.                 "<body><font size=+4>Linux 下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
  110.                 "<table border cols=3 width=\"100%%\">", errno,
  111.                 strerror(errno));
  112.         fprintf(client_sock,
  113.                 "</table><font color=\"CC0000\" size=+2>请向管理员咨询为何出现如下错误提示:\n%s %s</font></body></html>",
  114.                 Path, strerror(errno));
  115.         goto out;
  116.     }
  117.     /* 处理浏览文件请求,即下载文件 */
  118.     if (S_ISREG(info.st_mode)) {
  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.         ret = read(fd, p, len);
  125.         close(fd);
  126.         fprintf(client_sock,
  127.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\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.     } else if (S_ISDIR(info.st_mode)) {
  132.         /* 处理浏览目录请求 */
  133.         dir = opendir(realPath);
  134.         fprintf(client_sock,
  135.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: close\r\n\r\n<html><head><title>%s</title></head>"
  136.                 "<body><font size=+4>Linux 下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
  137.                 "<table border cols=3 width=\"100%%\">", Path);
  138.         fprintf(client_sock,
  139.                 "<caption><font size=+3>目录 %s</font></caption>\n",
  140.                 Path);
  141.         fprintf(client_sock,
  142.                 "<tr><td>名称</td><td>大小</td><td>修改时间</td></tr>\n");
  143.         if (dir == 0) {
  144.             fprintf(client_sock,
  145.                     "</table><font color=\"CC0000\" size=+2>%s</font></body></html>",
  146.                     strerror(errno));
  147.             return;
  148.         }
  149.         /* 读取目录里的所有内容 */
  150.         while ((dirent = readdir(dir)) != 0) {
  151.             if (strcmp(Path, "/") == 0)
  152.                 sprintf(Filename, "/%s", dirent->d_name);
  153.             else
  154.                 sprintf(Filename, "%s/%s", Path, dirent->d_name);
  155.             fprintf(client_sock, "<tr>");
  156.             len = strlen(dirroot) + strlen(Filename) + 1;
  157.             realFilename = malloc(len + 1);
  158.             bzero(realFilename, len + 1);
  159.             sprintf(realFilename, "%s/%s", dirroot, Filename);
  160.             if (stat(realFilename, &info) == 0) {
  161.                 if (strcmp(dirent->d_name, "..") == 0)
  162.                     fprintf(client_sock,
  163.                             "<td><a href=\"http://%s%s%s\">(parent)</a></td>",
  164.                             host, atoi(port) == 80 ? "" : nport,
  165.                             dir_up(Path));
  166.                 else
  167.                     fprintf(client_sock,
  168.                             "<td><a href=\"http://%s%s%s\">%s</a></td>",
  169.                             host, atoi(port) == 80 ? "" : nport, Filename,
  170.                             dirent->d_name);
  171.                 if (S_ISDIR(info.st_mode))
  172.                     fprintf(client_sock, "<td>目录</td>");
  173.                 else if (S_ISREG(info.st_mode))
  174.                     fprintf(client_sock, "<td>%d</td>", info.st_size);
  175.                 else if (S_ISLNK(info.st_mode))
  176.                     fprintf(client_sock, "<td>链接</td>");
  177.                 else if (S_ISCHR(info.st_mode))
  178.                     fprintf(client_sock, "<td>字符设备</td>");
  179.                 else if (S_ISBLK(info.st_mode))
  180.                     fprintf(client_sock, "<td>块设备</td>");
  181.                 else if (S_ISFIFO(info.st_mode))
  182.                     fprintf(client_sock, "<td>FIFO</td>");
  183.                 else if (S_ISSOCK(info.st_mode))
  184.                     fprintf(client_sock, "<td>Socket</td>");
  185.                 else
  186.                     fprintf(client_sock, "<td>(未知)</td>");
  187.                 fprintf(client_sock, "<td>%s</td>", ctime(&info.st_ctime));
  188.             }
  189.             fprintf(client_sock, "</tr>\n");
  190.             free(realFilename);
  191.         }
  192.         fprintf(client_sock, "</table></center></body></html>");
  193.     } else {
  194.         /* 既非常规文件又非目录,禁止访问 */
  195.         fprintf(client_sock,
  196.                 "HTTP/1.1 200 OK\r\nServer: DAS by ZhouLifa\r\nConnection: close\r\n\r\n<html><head><title>permission denied</title></head>"
  197.                 "<body><font size=+4>Linux 下目录访问服务器</font><br><hr width=\"100%%\"><br><center>"
  198.                 "<table border cols=3 width=\"100%%\">");
  199.         fprintf(client_sock,
  200.                 "</table><font color=\"CC0000\" size=+2>你访问的资源'%s'被禁止访问,请联系管理员解决!</font></body></html>",
  201.                 Path);
  202.     }
  203.   out:
  204.     free(realPath);
  205.     free(nport);
  206. }
  207. /*------------------------------------------------------
  208. *--- getoption - 分析取出程序的参数
  209. *------------------------------------------------------
  210. */
  211. void getoption(int argc, char **argv)
  212. {
  213.     int c, len;
  214.     char *p = 0;
  215.     opterr = 0;
  216.     while (1) {
  217.         int option_index = 0;
  218.         static struct option long_options[] = {
  219.             {"host", 1, 0, 0},
  220.             {"port", 1, 0, 0},
  221.             {"back", 1, 0, 0},
  222.             {"dir", 1, 0, 0},
  223.             {"log", 1, 0, 0},
  224.             {"daemon", 0, 0, 0},
  225.             {0, 0, 0, 0}
  226.         };
  227.         /* 本程序支持如一些参数:
  228.          * --host IP地址 或者 -H IP地址
  229.          * --port 端口 或者 -P 端口
  230.          * --back 监听数量 或者 -B 监听数量
  231.          * --dir 网站根目录 或者 -D 网站根目录
  232.          * --log 日志存放路径 或者 -L 日志存放路径
  233.          * --daemon 使程序进入后台运行模式
  234.          */
  235.         c = getopt_long(argc, argv, "H:P:B:D:L",
  236.                         long_options, &option_index);
  237.         if (c == -1 || c == '?')
  238.             break;
  239.         if(optarg)        len = strlen(optarg);
  240.         else        len = 0;
  241.         if ((!c && !(strcasecmp(long_options[option_index].name, "host")))
  242.             || c == 'H')
  243.             p = host = malloc(len + 1);
  244.         else if ((!c
  245.                   &&
  246.                   !(strcasecmp(long_options[option_index].name, "port")))
  247.                  || c == 'P')
  248.             p = port = malloc(len + 1);
  249.         else if ((!c
  250.                   &&
  251.                   !(strcasecmp(long_options[option_index].name, "back")))
  252.                  || c == 'B')
  253.             p = back = malloc(len + 1);
  254.         else if ((!c
  255.                   && !(strcasecmp(long_options[option_index].name, "dir")))
  256.                  || c == 'D')
  257.             p = dirroot = malloc(len + 1);
  258.         else if ((!c
  259.                   && !(strcasecmp(long_options[option_index].name, "log")))
  260.                  || c == 'L')
  261.             p = logdir = malloc(len + 1);
  262.         else if ((!c
  263.                   &&
  264.                   !(strcasecmp
  265.                     (long_options[option_index].name, "daemon")))) {
  266.             daemon_y_n = 1;
  267.             continue;
  268.         }
  269.         else
  270.             break;
  271.         bzero(p, len + 1);
  272.         memcpy(p, optarg, len);
  273.     }
  274. }
  275. int main(int argc, char **argv)
  276. {
  277.     struct sockaddr_in addr;
  278.     int sock_fd, addrlen;
  279.     /* 获得程序工作的参数,如 IP 、端口、监听数、网页根目录、目录存放位置等 */
  280.     getoption(argc, argv);
  281.     if (!host) {
  282.         addrlen = strlen(DEFAULTIP);
  283.         AllocateMemory(&host, addrlen, DEFAULTIP);
  284.     }
  285.     if (!port) {
  286.         addrlen = strlen(DEFAULTPORT);
  287.         AllocateMemory(&port, addrlen, DEFAULTPORT);
  288.     }
  289.     if (!back) {
  290.         addrlen = strlen(DEFAULTBACK);
  291.         AllocateMemory(&back, addrlen, DEFAULTBACK);
  292.     }
  293.     if (!dirroot) {
  294.         addrlen = strlen(DEFAULTDIR);
  295.         AllocateMemory(&dirroot, addrlen, DEFAULTDIR);
  296.     }
  297.     if (!logdir) {
  298.         addrlen = strlen(DEFAULTLOG);
  299.         AllocateMemory(&logdir, addrlen, DEFAULTLOG);
  300.     }
  301.     printf
  302.         ("host=%s port=%s back=%s dirroot=%s logdir=%s %s是后台工作模式(进程ID:%d)\n",
  303.          host, port, back, dirroot, logdir, daemon_y_n?"":"不", getpid());
  304.     /* fork() 两次处于后台工作模式下 */
  305.     if (daemon_y_n) {
  306.         if (fork())
  307.             exit(0);
  308.         if (fork())
  309.             exit(0);
  310.         close(0), close(1), close(2);
  311.         logfp = fopen(logdir, "a+");
  312.         if (!logfp)
  313.             exit(0);
  314.     }
  315.     /* 处理子进程退出以免产生僵尸进程 */
  316.     signal(SIGCHLD, SIG_IGN);
  317.     /* 创建 socket */
  318.     if ((sock_fd = socket(PF_INET, SOCK_STREAM, 0)) < 0) {
  319.         if (!daemon_y_n) {
  320.             prterrmsg("socket()");
  321.         } else {
  322.             wrterrmsg("socket()");
  323.         }
  324.     }
  325.     /* 设置端口快速重用 */
  326.     addrlen = 1;
  327.     setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &addrlen,
  328.                sizeof(addrlen));
  329.     addr.sin_family = AF_INET;
  330.     addr.sin_port = htons(atoi(port));
  331.     addr.sin_addr.s_addr = inet_addr(host);
  332.     addrlen = sizeof(struct sockaddr_in);
  333.     /* 绑定地址、端口等信息 */
  334.     if (bind(sock_fd, (struct sockaddr *) &addr, addrlen) < 0) {
  335.         if (!daemon_y_n) {
  336.             prterrmsg("bind()");
  337.         } else {
  338.             wrterrmsg("bind()");
  339.         }
  340.     }
  341.     /* 开启临听 */
  342.     if (listen(sock_fd, atoi(back)) < 0) {
  343.         if (!daemon_y_n) {
  344.             prterrmsg("listen()");
  345.         } else {
  346.             wrterrmsg("listen()");
  347.         }
  348.     }
  349.     while (1) {
  350.         int len;
  351.         int new_fd;
  352.         addrlen = sizeof(struct sockaddr_in);
  353.         /* 接受新连接请求 */
  354.         new_fd = accept(sock_fd, (struct sockaddr *) &addr, &addrlen);
  355.         if (new_fd < 0) {
  356.             if (!daemon_y_n) {
  357.                 prterrmsg("accept()");
  358.             } else {
  359.                 wrterrmsg("accept()");
  360.             }
  361.             break;
  362.         }
  363.         bzero(buffer, MAXBUF + 1);
  364.         sprintf(buffer, "连接来自于: %s:%d\n",
  365.                 inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
  366.         if (!daemon_y_n) {
  367.             prtinfomsg(buffer);
  368.         } else {
  369.             wrtinfomsg(buffer);
  370.         }
  371.         /* 产生一个子进程去处理请求,当前进程继续等待新的连接到来 */
  372.         if (!fork()) {
  373.             bzero(buffer, MAXBUF + 1);
  374.             if ((len = recv(new_fd, buffer, MAXBUF, 0)) > 0) {
  375.                 FILE *ClientFP = fdopen(new_fd, "w");
  376.                 if (ClientFP == NULL) {
  377.                     if (!daemon_y_n) {
  378.                         prterrmsg("fdopen()");
  379.                     } else {
  380.                         prterrmsg("fdopen()");
  381.                     }
  382.                 } else {
  383.                     char Req[MAXPATH + 1] = "";
  384.                     sscanf(buffer, "GET %s HTTP", Req);
  385.                     bzero(buffer, MAXBUF + 1);
  386.                     sprintf(buffer, "请求取文件: \"%s\"\n", Req);
  387.                     if (!daemon_y_n) {
  388.                         prtinfomsg(buffer);
  389.                     } else {
  390.                         wrtinfomsg(buffer);
  391.                     }
  392.                     /* 处理用户请求 */
  393.                     GiveResponse(ClientFP, Req);
  394.                     fclose(ClientFP);
  395.                 }
  396.             }
  397.             exit(0);
  398.         }
  399.         close(new_fd);
  400.     }
  401.     close(sock_fd);
  402.     return 0;
  403. }

复制代码

编译程序用下列命令:
gcc -Wall das-server.c -o das-server
注:das即 Dictory Access Server
以root用户启动服务程序用下列命令:
./das-server
或以普通用户启动服务程序用下列命令:
./das-server --port 7838

./das-server -P 7838
注:只有root用户才有权限启动1024以下的端口,所以如果想用默认的80端口就得用root来运行。
如果要想让程序在后台自动运行,即处理精灵模式下工作,在命令后面加上--daemon参数即可。
打开一个网络浏览器输入服务地址开始浏览,如下图:

下载文件如下图:

注:请不要下载较大的文件,比如文件大小超过10M的,因为程序是一次分配内存,会占用系统内存较大导致系统死掉!

posted @ 2013-07-16 17:36  mickole  阅读(759)  评论(0编辑  收藏  举报