免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 23018 | 回复: 31
打印 上一主题 下一主题

一个web服务器的实现 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-06-21 15:52 |只看该作者 |倒序浏览
题目:  一个web服务器的实现

客户端(浏览器)与web服务器之间的交互主要包含客户的请求和服务器的应答。
请求和应答的格式在超文本传输协议(HTTP)中有定义。HTTP协议使用纯文本。

例如:
$telnet  www.ouc.edu.cn  80
Trying 211.64.150.68...
Connected to www.ouc.edu.cn(211.64.150.6
Escape character is '^]'.
GET /index.html HTTP/1.0

HTTP/1.1  200  OK
Date: Wed, 21 Jun 2006  08:26:04 GMT
Server: Apache/2.0.54(Unix) DAV/2
..................................

原理是telnet创建了一个socket并调用了connect来连接到web服务器。服务器
接受连接请求,并创建了一个基于socket的从客户端的键盘到web服务进程的数据通道。接下来,客户端输入请求:
GET /index.html HTTP/1.0

一个HTTP请求包含有3个字符串。第一个字符串是命令,第二个是参数,第三个是所用协议的版本号。  *注意还有一空行

web服务器读取请求,检查请求,然后返回一个请求。应答有两部分:头部和内容。头部以状态行起始,如下所示:

HTTP/1.1  200  ok

状态行含有两个或更多的字符串。第一个串是协议的版本,第二个串是返回码,这里是200,其文本解释是OK。头部的其余部分是关于应答的附加信息。
应答的其余部分是返回的具体内容。


下面这个webserv.c就是一个web服务器的具体实现。

编译程序,在某个端口运行它:
$gcc webserv.c  -o  webserv
$./webserv  12345
就可以访问web服务器,网址为http://yourhostname:12345/  
将html文件放到该目录中并且用http://yourhostname:12345//filename.html就可以访问。



  1. /* webserv.c - a web server
  2. *      build: gcc webserv.c -o webserv
  3. */
  4. #include        <stdio.h>
  5. #include        <unistd.h>
  6. #include        <sys/types.h>
  7. #include        <sys/socket.h>
  8. #include        <netinet/in.h>
  9. #include        <netdb.h>
  10. #include        <sys/stat.h>
  11. #include        <time.h>
  12. #include        <string.h>

  13. #define   HOSTLEN  256
  14. #define          BACKLOG  1


  15. main(int ac, char *av[])
  16. {
  17.         int         sock, fd;
  18.         FILE        *fpin;
  19.         char        request[BUFSIZ];

  20.         if ( ac == 1 ){
  21.                 fprintf(stderr,"usage: ws portnum\n");
  22.                 exit(1);
  23.         }
  24.         sock = make_server_socket( atoi(av[1]) );
  25.         if ( sock == -1 ) exit(2);

  26.         /* main loop here */

  27.         while(1){
  28.                 /* take a call and buffer it */
  29.                 fd = accept( sock, NULL, NULL );
  30.                 fpin = fdopen(fd, "r" );

  31.                 /* read request */
  32.                 fgets(request,BUFSIZ,fpin);
  33.                 printf("got a call: request = %s", request);
  34.                 read_til_crnl(fpin);

  35.                 /* do what client asks */
  36.                 process_rq(request, fd);

  37.                 fclose(fpin);
  38.         }
  39. }

  40. /* ------------------------------------------------------ *
  41.    make_server_socket(int portnum)
  42.    创建一个服务器套接字,并调用listen监听
  43.    ------------------------------------------------------ */

  44. int make_server_socket(int portnum)
  45. {
  46.         return make_server_socket_q(portnum, BACKLOG);
  47. }
  48. int make_server_socket_q(int portnum, int backlog)
  49. {
  50.         struct  sockaddr_in   saddr;   /* build our address here */
  51.         struct        hostent                *hp;   /* this is part of our    */
  52.         char        hostname[HOSTLEN];     /* address                  */
  53.         int        sock_id;               /* the socket             */

  54.         sock_id = socket(PF_INET, SOCK_STREAM, 0);  /* get a socket */
  55.         if ( sock_id == -1 )
  56.                 return -1;

  57.         /** build address and bind it to socket **/

  58.         bzero((void *)&saddr, sizeof(saddr));   /* clear out struct     */
  59.         gethostname(hostname, HOSTLEN);         /* where am I ?         */
  60.         hp = gethostbyname(hostname);           /* get info about host  */
  61.                                                 /* fill in host part    */
  62.         bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
  63.         saddr.sin_port = htons(portnum);        /* fill in socket port  */
  64.         saddr.sin_family = AF_INET ;            /* fill in addr family  */
  65.         if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
  66.                return -1;

  67.         /** arrange for incoming calls **/

  68.         if ( listen(sock_id, backlog) != 0 )
  69.                 return -1;
  70.         return sock_id;
  71. }


  72. /* ------------------------------------------------------ *
  73.    read_til_crnl(FILE *)
  74.    skip over all request info until a CRNL is seen
  75.    ------------------------------------------------------ */

  76. read_til_crnl(FILE *fp)
  77. {
  78.         char        buf[BUFSIZ];
  79.         while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 )
  80.                 ;
  81. }

  82. /* ------------------------------------------------------ *
  83.    process_rq( char *rq, int fd )
  84.    分析客户端的请求,分析然后做出相应的应答
  85.    rq is HTTP command:  GET /foo/bar.html HTTP/1.0
  86.    ------------------------------------------------------ */

  87. process_rq( char *rq, int fd )
  88. {
  89.         char        cmd[BUFSIZ], arg[BUFSIZ];

  90.         /* create a new process and return if not the child */
  91.         if ( fork() != 0 )
  92.                 return;

  93.         strcpy(arg, "./");                /* precede args with ./ */
  94.         if ( sscanf(rq, "%s%s", cmd, arg+2) != 2 )
  95.                 return;

  96.         if ( strcmp(cmd,"GET") != 0 )
  97.                 cannot_do(fd);
  98.         else if ( not_exist( arg ) )
  99.                 do_404(arg, fd );
  100.         else if ( isadir( arg ) )
  101.                 do_ls( arg, fd );
  102.         else if ( ends_in_cgi( arg ) )
  103.                 do_exec( arg, fd );
  104.         else
  105.                 do_cat( arg, fd );
  106. }

  107. /* ------------------------------------------------------ *
  108.    the reply header thing: all functions need one
  109.    if content_type is NULL then don't send content type
  110.    ------------------------------------------------------ */

  111. header( FILE *fp, char *content_type )
  112. {
  113.         fprintf(fp, "HTTP/1.0 200 OK\r\n");
  114.         if ( content_type )
  115.                 fprintf(fp, "Content-type: %s\r\n", content_type );
  116. }

  117. /* ------------------------------------------------------ *
  118.    simple functions first:
  119.         cannot_do(fd)       unimplemented HTTP command
  120.     and do_404(item,fd)     no such object
  121.    ------------------------------------------------------ */

  122. cannot_do(int fd)
  123. {
  124.         FILE        *fp = fdopen(fd,"w");

  125.         fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n");
  126.         fprintf(fp, "Content-type: text/plain\r\n");
  127.         fprintf(fp, "\r\n");

  128.         fprintf(fp, "That command is not yet implemented\r\n");
  129.         fclose(fp);
  130. }

  131. do_404(char *item, int fd)
  132. {
  133.         FILE        *fp = fdopen(fd,"w");

  134.         fprintf(fp, "HTTP/1.0 404 Not Found\r\n");
  135.         fprintf(fp, "Content-type: text/plain\r\n");
  136.         fprintf(fp, "\r\n");

  137.         fprintf(fp, "The item you requested: %s\r\nis not found\r\n",
  138.                         item);
  139.         fclose(fp);
  140. }

  141. /* ------------------------------------------------------ *
  142.    the directory listing section
  143.    isadir() uses stat, not_exist() uses stat
  144.    do_ls runs ls. It should not
  145.    ------------------------------------------------------ */

  146. isadir(char *f)
  147. {
  148.         struct stat info;
  149.         return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) );
  150. }

  151. not_exist(char *f)
  152. {
  153.         struct stat info;
  154.         return( stat(f,&info) == -1 );
  155. }

  156. do_ls(char *dir, int fd)
  157. {
  158.         FILE        *fp ;

  159.         fp = fdopen(fd,"w");
  160.         header(fp, "text/plain");
  161.         fprintf(fp,"\r\n");
  162.         fflush(fp);

  163.         dup2(fd,1);
  164.         dup2(fd,2);
  165.         close(fd);
  166.         execlp("ls","ls","-l",dir,NULL);
  167.         perror(dir);
  168.         exit(1);
  169. }

  170. /* ------------------------------------------------------ *
  171.    the cgi stuff.  function to check extension and
  172.    one to run the program.
  173.    ------------------------------------------------------ */

  174. char * file_type(char *f)
  175. /* returns 'extension' of file */
  176. {
  177.         char        *cp;
  178.         if ( (cp = strrchr(f, '.' )) != NULL )
  179.                 return cp+1;
  180.         return "";
  181. }

  182. ends_in_cgi(char *f)
  183. {
  184.         return ( strcmp( file_type(f), "cgi" ) == 0 );
  185. }

  186. do_exec( char *prog, int fd )
  187. {
  188.         FILE        *fp ;

  189.         fp = fdopen(fd,"w");
  190.         header(fp, NULL);
  191.         fflush(fp);
  192.         dup2(fd, 1);
  193.         dup2(fd, 2);
  194.         close(fd);
  195.         execl(prog,prog,NULL);
  196.         perror(prog);
  197. }
  198. /* ------------------------------------------------------ *
  199.    do_cat(filename,fd)
  200.    sends back contents after a header
  201.    ------------------------------------------------------ */

  202. do_cat(char *f, int fd)
  203. {
  204.         char        *extension = file_type(f);
  205.         char        *content = "text/plain";
  206.         FILE        *fpsock, *fpfile;
  207.         int        c;

  208.         if ( strcmp(extension,"html") == 0 )
  209.                 content = "text/html";
  210.         else if ( strcmp(extension, "gif") == 0 )
  211.                 content = "image/gif";
  212.         else if ( strcmp(extension, "jpg") == 0 )
  213.                 content = "image/jpeg";
  214.         else if ( strcmp(extension, "jpeg") == 0 )
  215.                 content = "image/jpeg";

  216.         fpsock = fdopen(fd, "w");
  217.         fpfile = fopen( f , "r");
  218.         if ( fpsock != NULL && fpfile != NULL )
  219.         {
  220.                 header( fpsock, content );
  221.                 fprintf(fpsock, "\r\n");
  222.                 while( (c = getc(fpfile) ) != EOF )
  223.                         putc(c, fpsock);
  224.                 fclose(fpfile);
  225.                 fclose(fpsock);
  226.         }
  227.         exit(0);
  228. }
复制代码

[ 本帖最后由 liuke432 于 2006-6-21 16:46 编辑 ]

论坛徽章:
0
2 [报告]
发表于 2006-06-21 15:58 |只看该作者
支持

论坛徽章:
0
3 [报告]
发表于 2006-06-21 16:12 |只看该作者
好东东,支持

论坛徽章:
0
4 [报告]
发表于 2006-06-21 17:43 |只看该作者
先顶下慢慢看~~~

论坛徽章:
0
5 [报告]
发表于 2006-06-21 17:52 |只看该作者
新到任的高手啊

论坛徽章:
0
6 [报告]
发表于 2006-06-21 18:38 |只看该作者
请问WEB服务器写的时候要注意点什么呢? 小弟正在学. ....好象很多书有关于这web server的code.

论坛徽章:
0
7 [报告]
发表于 2006-06-22 09:27 |只看该作者
原帖由 HAMMER 于 2006-6-21 17:52 发表
新到任的高手啊

多谢兄弟抬举,我还称不上是什么高手。 :)
学习unix网络编程才半年多,看到有本书上的这个例子比较不错,
对于学习网络编程的朋友很有实践价值。于是就整理了一下,让大
家一起学习。

论坛徽章:
0
8 [报告]
发表于 2006-06-22 09:41 |只看该作者
原帖由 linuxcici 于 2006-6-21 18:38 发表
请问WEB服务器写的时候要注意点什么呢? 小弟正在学. ....好象很多书有关于这web server的code.

我对这个还不是很精通。   
web服务器的侧重点不一样,有的提供了更多的安全特征,有的则快速处理请求或使用较少的内存。
其中一个重要的特征是服务器的效率问题。服务器可以同时处理多少个请求?对于每个请求,服务器需要多少系统资源?
上面那个程序是用多进程来处理的。   
下面再贴一个多线程的。

论坛徽章:
0
9 [报告]
发表于 2006-06-22 09:55 |只看该作者
多线程的web服务器

多线程版本增加了内部统计功能:
     服务器的运行时间
     接收的客户端请求的数目
     发送回客户端的数据量

这里使用独立线程(Detached Threads)防止僵尸线程(Zombie Threads)


  1. /* webserv_pthread.c - a threaded  web server
  2. * building: gcc webserv_pthread.c -lpthread -o webserv_pthread
  3. */

  4. #include <stdio.h>
  5. #include <sys/types.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. #include <netdb.h>
  9. #include <sys/stat.h>
  10. #include <string.h>

  11. #include <pthread.h>
  12. #include <stdlib.h>
  13. #include <unistd.h>

  14. #include <dirent.h>
  15. #include <time.h>

  16. #define   HOSTLEN  256
  17. #define          BACKLOG  1

  18. /* server facts here */

  19. time_t         server_started ;
  20. int         server_bytes_sent;
  21. int         server_requests;

  22. main(int ac, char *av[])
  23. {
  24.         int                 sock, fd;
  25.         int                *fdptr;
  26.         pthread_t        worker;
  27.         pthread_attr_t        attr;

  28.         void *handle_call(void *);

  29.         if ( ac == 1 ){
  30.                 fprintf(stderr,"usage: tws portnum\n");
  31.                 exit(1);
  32.         }
  33.         sock = make_server_socket( atoi(av[1]) );
  34.         if ( sock == -1 ) { perror("making socket"); exit(2); }

  35.         setup(&attr);

  36.         /* main loop here: take call, handle call in new thread  */

  37.         while(1){
  38.                 fd = accept( sock, NULL, NULL );
  39.                 server_requests++;

  40.                 fdptr = malloc(sizeof(int));
  41.                 *fdptr = fd;
  42.                 pthread_create(&worker,&attr,handle_call,fdptr);
  43.         }
  44. }


  45. /* ------------------------------------------------------ *
  46.    make_server_socket(int portnum)
  47.    创建一个服务器套接字,并调用listen监听
  48.    ------------------------------------------------------ */

  49. int make_server_socket_q(int , int );

  50. int make_server_socket(int portnum)
  51. {
  52.         return make_server_socket_q(portnum, BACKLOG);
  53. }
  54. int make_server_socket_q(int portnum, int backlog)
  55. {
  56.         struct  sockaddr_in   saddr;   /* build our address here */
  57.         struct        hostent                *hp;   /* this is part of our    */
  58.         char        hostname[HOSTLEN];     /* address                  */
  59.         int        sock_id;               /* the socket             */

  60.         sock_id = socket(PF_INET, SOCK_STREAM, 0);  /* get a socket */
  61.         if ( sock_id == -1 )
  62.                 return -1;

  63.         /** build address and bind it to socket **/

  64.         bzero((void *)&saddr, sizeof(saddr));   /* clear out struct     */
  65.         gethostname(hostname, HOSTLEN);         /* where am I ?         */
  66.         hp = gethostbyname(hostname);           /* get info about host  */
  67.                                                 /* fill in host part    */
  68.         bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length);
  69.         saddr.sin_port = htons(portnum);        /* fill in socket port  */
  70.         saddr.sin_family = AF_INET ;            /* fill in addr family  */
  71.         if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 )
  72.                return -1;

  73.         /** arrange for incoming calls **/

  74.         if ( listen(sock_id, backlog) != 0 )
  75.                 return -1;
  76.         return sock_id;
  77. }

  78. /*
  79. * initialize the status variables and
  80. * set the thread attribute to detached
  81. */
  82. setup(pthread_attr_t *attrp)
  83. {
  84.         pthread_attr_init(attrp);
  85.         pthread_attr_setdetachstate(attrp,PTHREAD_CREATE_DETACHED);

  86.         time(&server_started);
  87.         server_requests = 0;
  88.         server_bytes_sent = 0;
  89. }

  90. void *handle_call(void *fdptr)
  91. {
  92.         FILE        *fpin;
  93.         char        request[BUFSIZ];
  94.         int        fd ;

  95.         fd = *(int *)fdptr;
  96.         free(fdptr);                                /* get fd from arg  */

  97.         fpin = fdopen(fd, "r");                        /* buffer input        */
  98.         fgets(request,BUFSIZ,fpin);                /* read client request */
  99.         printf("got a call on %d: request = %s", fd, request);
  100.         skip_rest_of_header(fpin);

  101.         process_rq(request, fd);                /* process client rq */

  102.         fclose(fpin);
  103. }

  104. /* ------------------------------------------------------ *
  105.    skip_rest_of_header(FILE *)
  106.    skip over all request info until a CRNL is seen
  107.    ------------------------------------------------------ */
  108. skip_rest_of_header(FILE *fp)
  109. {
  110.         char        buf[BUFSIZ];
  111.         while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 )
  112.                 ;
  113. }

  114. /* ------------------------------------------------------ *
  115.    process_rq( char *rq, int fd )
  116.    do what the request asks for and write reply to fd
  117.    handles request in a new process
  118.    rq is HTTP command:  GET /foo/bar.html HTTP/1.0
  119.    ------------------------------------------------------ */
  120. process_rq( char *rq, int fd)
  121. {
  122.         char        cmd[BUFSIZ], arg[BUFSIZ];

  123.         if ( sscanf(rq, "%s%s", cmd, arg) != 2 )
  124.                 return;
  125.         sanitize(arg);
  126.         printf("sanitized version is %s\n", arg);

  127.         if ( strcmp(cmd,"GET") != 0 )
  128.                 not_implemented();
  129.         else if ( built_in(arg, fd) )
  130.                 ;
  131.         else if ( not_exist( arg ) )
  132.                 do_404(arg, fd);
  133.         else if ( isadir( arg ) )
  134.                 do_ls( arg, fd );
  135.         else
  136.                 do_cat( arg, fd );
  137. }
  138. /*
  139. * make sure all paths are below the current directory
  140. */
  141. sanitize(char *str)
  142. {
  143.         char        *src, *dest;

  144.         src = dest = str;

  145.         while( *src ){
  146.                 if( strncmp(src,"/../",4) == 0 )
  147.                         src += 3;
  148.                 else if ( strncmp(src,"//",2) == 0 )
  149.                         src++;
  150.                 else
  151.                         *dest++ = *src++;
  152.         }
  153.         *dest = '\0';
  154.         if ( *str == '/' )
  155.                 strcpy(str,str+1);

  156.         if ( str[0]=='\0' || strcmp(str,"./")==0 || strcmp(str,"./..")==0 )
  157.                 strcpy(str,".");
  158. }

  159. /* handle built-in URLs here.  Only one so far is "status" */
  160. built_in(char *arg, int fd)
  161. {
  162.         FILE        *fp;

  163.         if ( strcmp(arg,"status") != 0 )
  164.                 return 0;
  165.         http_reply(fd, &fp, 200, "OK", "text/plain",NULL);

  166.         fprintf(fp,"Server started: %s", ctime(&server_started));
  167.         fprintf(fp,"Total requests: %d\n", server_requests);
  168.         fprintf(fp,"Bytes sent out: %d\n", server_bytes_sent);
  169.         fclose(fp);
  170.         return 1;
  171. }

  172. http_reply(int fd, FILE **fpp, int code, char *msg, char *type, char *content)
  173. {
  174.         FILE        *fp = fdopen(fd, "w");
  175.         int        bytes = 0;

  176.         if ( fp != NULL ){
  177.                 bytes = fprintf(fp,"HTTP/1.0 %d %s\r\n", code, msg);
  178.                 bytes += fprintf(fp,"Content-type: %s\r\n\r\n", type);
  179.                 if ( content )
  180.                         bytes += fprintf(fp,"%s\r\n", content);
  181.         }
  182.         fflush(fp);
  183.         if ( fpp )
  184.                 *fpp = fp;
  185.         else
  186.                 fclose(fp);
  187.         return bytes;
  188. }

  189. /* ------------------------------------------------------ *
  190.    simple functions first:
  191.         not_implemented(fd)      unimplemented HTTP command
  192.         and do_404(item,fd)     no such object
  193.    ------------------------------------------------------ */
  194. not_implemented(int fd)
  195. {
  196.         http_reply(fd,NULL,501,"Not Implemented","text/plain",
  197.                         "That command is not implemented");
  198. }

  199. do_404(char *item, int fd)
  200. {
  201.         http_reply(fd,NULL,404,"Not Found","text/plain",
  202.                         "The item you seek is not here");
  203. }

  204. /* ------------------------------------------------------ *
  205.    the directory listing section
  206.    isadir() uses stat, not_exist() uses stat
  207.    ------------------------------------------------------ */
  208. isadir(char *f)
  209. {
  210.         struct stat info;
  211.         return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) );
  212. }

  213. not_exist(char *f)
  214. {
  215.         struct stat info;
  216.         return( stat(f,&info) == -1 );
  217. }

  218. do_ls(char *dir, int fd)
  219. {
  220.         DIR              *dirptr;
  221.         struct dirent *direntp;
  222.         FILE              *fp;
  223.         int              bytes = 0;

  224.         bytes = http_reply(fd,&fp,200,"OK","text/plain",NULL);
  225.         bytes += fprintf(fp,"Listing of Directory %s\n", dir);

  226.         if ( (dirptr = opendir(dir)) != NULL ){
  227.                 while( direntp = readdir(dirptr) ){
  228.                         bytes += fprintf(fp, "%s\n", direntp->d_name);
  229.                 }
  230.                 closedir(dirptr);
  231.         }
  232.         fclose(fp);
  233.         server_bytes_sent += bytes;
  234. }

  235. /* ------------------------------------------------------ *
  236.    functions to cat files here.
  237.    file_type(filename) returns the 'extension': cat uses it
  238.    ------------------------------------------------------ */
  239. char * file_type(char *f)
  240. {
  241.         char        *cp;
  242.         if ( (cp = strrchr(f, '.' )) != NULL )
  243.                 return cp+1;
  244.         return "";
  245. }

  246. /* do_cat(filename,fd): sends header then the contents */

  247. do_cat(char *f, int fd)
  248. {
  249.         char        *extension = file_type(f);
  250.         char        *type = "text/plain";
  251.         FILE        *fpsock, *fpfile;
  252.         int        c;
  253.         int        bytes = 0;

  254.         if ( strcmp(extension,"html") == 0 )
  255.                 type = "text/html";
  256.         else if ( strcmp(extension, "gif") == 0 )
  257.                 type = "image/gif";
  258.         else if ( strcmp(extension, "jpg") == 0 )
  259.                 type = "image/jpeg";
  260.         else if ( strcmp(extension, "jpeg") == 0 )
  261.                 type = "image/jpeg";

  262.         fpsock = fdopen(fd, "w");
  263.         fpfile = fopen( f , "r");
  264.         if ( fpsock != NULL && fpfile != NULL )
  265.         {
  266.                 bytes = http_reply(fd,&fpsock,200,"OK",type,NULL);
  267.                 while( (c = getc(fpfile) ) != EOF ){
  268.                         putc(c, fpsock);
  269.                         bytes++;
  270.                 }
  271.                 fclose(fpfile);
  272.                 fclose(fpsock);
  273.         }
  274.         server_bytes_sent += bytes;
  275. }

复制代码

论坛徽章:
0
10 [报告]
发表于 2006-06-22 12:33 |只看该作者
顶一个
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP