tinyhttpd ------ C 语言实现最简单的 HTTP 服务器
工作流程:
1>服务器启动,在指定端口或随机选取端口绑定httpd服务。
2>收到一个http请求时(其实就是listen端口accept的时候),派生一个线程运行accept_request函数。
3>取出http请求中method(get或post)和url,对于get方法,如果有携带参数,则query_string指针指向url中?后面的get参数。
4>格式化url到path数组,表示浏览器请求的文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下。当url以/结尾,或者url是个目录,则默认在path中加上index.thml,表示访问主页。
5>如果文件路径合法,对于无参数的get请求,直接输出服务器文件到浏览器,即用http格式写到套接字上,跳到(10)。其他情况(带参数get,post方法,url为科执行文件),则调用execute_cgi函数执行cgi脚本。
6>读取整个http请求并丢弃,如果是post则找出content-length,把http状态码200写到套接字里面。
7>建立两个管道,cgi_input和cgi_output,并fork一个子进程。
8>在子进程中,把stdout重定向到cgi_output的写入端,把stdin重定向到cgi_input的读取端,关闭cgi_input的写入端和cgi_output的读取端,是指request_method的环境变量,get的话设置query_string的环境变量,post的话设置content-length的环境变量,这些环境变量都是为了给cgi脚本调用,接着用execl运行cgi程序。
9>在父进程中,关闭cgi_input的读取端和cgi_output的写入端,如果post的话,把post数据写入到cgo_input,已被重定向到stdin读取cgi_output的管道输出到客户端,等待子进程结束。
10>关闭与浏览器的链接,完成一次http请求与回应,因为http是无连接的。
- /* J. David's webserver */
- /* This is a simple webserver.
- * Created November 1999 by J. David Blackstone.
- * CSE 4344 (Network concepts), Prof. Zeigler
- * University of Texas at Arlington
- */
- /* This program compiles for Sparc Solaris 2.6.
- * To compile for Linux:
- * 1) Comment out the #include <pthread.h> line.
- * 2) Comment out the line that defines the variable newthread.
- * 3) Comment out the two lines that run pthread_create().
- * 4) Uncomment the line that runs accept_request().
- * 5) Remove -lsocket from the Makefile.
- */
- #include <stdio.h>
- #include <sys/socket.h>
- #include <sys/types.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <unistd.h>
- #include <ctype.h>
- #include <strings.h>
- #include <string.h>
- #include <sys/stat.h>
- #include <pthread.h>
- #include <sys/wait.h>
- #include <stdlib.h>
- #define ISspace(x) isspace((int)(x))
- #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
- void accept_request(int);
- //处理从套接字上监听到的一个HTTP请求,在这里可以很大一部分的体现服务器处理请求的流程
- void bad_request(int);
- //返回给客户端这是个错误请求,HTTP状态码是400 BAD REQUEST
- void cat(int, FILE *);
- //读取服务器上某个文件写到socket套接字
- void cannot_execute(int);
- //主要执行在处理cgi程序的处理,也是个主要函数
- void error_die(const char *);
- //把错误信息写到perror并退出
- void execute_cgi(int, const char *, const char *, const char *);
- //运行cgi程序的处理,也是哥主函数
- int get_line(int, char *, int);
- //读取套接字的一行,把回车换行等情况都统一为换行符结束
- void headers(int, const char *);
- //把HTTP相应头写到套接字
- void not_found(int);
- //主要处理找不到请求的文件时的情况
- void serve_file(int, const char *);
- //调用cat把服务器文件返回给浏览器
- int startup(u_short *);
- //初始化httpd服务,包括建立套接字,绑定端口,进行监听等
- void unimplemented(int);
- //返回给浏览器表示接收到的http请求所用的method不被支持
- /**********************************************************************/
- /* A request has caused a call to accept() on the server port to
- * return. Process the request appropriately.
- * Parameters: the socket connected to the client */
- /**********************************************************************/
- void accept_request(int client)
- {
- char buf[];
- int numchars;
- char method[];
- char url[];
- char path[];
- size_t i, j;
- struct stat st;
- int cgi = ; /* becomes true if server decides this is a CGI
- * program */
- char *query_string = NULL;
- numchars = get_line(client, buf, sizeof(buf));
- //读取 client端发送的数据并且 返回的参数是 numchars
- i = ;
- j = ;
- while (!ISspace(buf[j]) && (i < sizeof(method) - ))
- {
- method[i] = buf[j];
- i++;
- j++;
- }
- //得到传递的参数是post 还是get方法 还是其他
- method[i] = '\0';
- if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
- {
- //回给浏览器表明收到的 HTTP 请求所用的 method 不被支持
- unimplemented(client);
- return;
- }
- if (strcasecmp(method, "POST") == )
- cgi = ;
- i = ;
- //http请求行格式是 method urI http-version
- while (ISspace(buf[j]) && (j < sizeof(buf)))
- j++;
- while (!ISspace(buf[j]) && (i < sizeof(url) - ) && (j < sizeof(buf)))
- {
- url[i] = buf[j]; //包含请求行中的URI
- i++;
- j++;
- }
- url[i] = '\0';
- ///获取发送数据中的URI
- if (strcasecmp(method, "GET") == )
- {
- //get方法 是将参数传递在URI中的?后面如果元素多用&进行链接
- query_string = url;
- while ((*query_string != '?') && (*query_string != '\0'))
- query_string++;
- // get 请求? 后面为参数
- if (*query_string == '?')
- {
- cgi = ;
- *query_string = '\0';
- query_string++;
- }
- }
- //格式url存储在path数组 并且html存储在 htdocs文件中
- sprintf(path, "htdocs%s", url);
- if (path[strlen(path) - ] == '/')
- strcat(path, "index.html"); //字符串进行衔接 + index.html
- //stat函数 根据path路径 获取文件内容 存储在 st 结构体中 成功返回0 错误返回-1
- if (stat(path, &st) == -)
- {
- while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
- numchars = get_line(client, buf, sizeof(buf));
- not_found(client);
- }
- else
- {
- if ((st.st_mode & S_IFMT) == S_IFDIR)
- strcat(path, "/index.html");
- //文件的权限 属主 属组 其它 三种任一个拥有执行权
- if ((st.st_mode & S_IXUSR) ||
- (st.st_mode & S_IXGRP) ||
- (st.st_mode & S_IXOTH) )
- cgi = ;
- //调用cat 把服务器文件返回给浏览器 post方法或者拥有执行权限
- if (!cgi)
- serve_file(client, path);
- else
- execute_cgi(client, path, method, query_string)
- //运行cgi程序的处理,也是个主要函数
- }
- close(client);
- }
- /**********************************************************************/
- /* Inform the client that a request it has made has a problem.
- * Parameters: client socket */
- /**********************************************************************/
- void bad_request(int client)
- {
- char buf[];
- sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
- send(client, buf, sizeof(buf), );
- sprintf(buf, "Content-type: text/html\r\n");
- send(client, buf, sizeof(buf), );
- sprintf(buf, "\r\n");
- send(client, buf, sizeof(buf), );
- sprintf(buf, "<P>Your browser sent a bad request, ");
- send(client, buf, sizeof(buf), );
- sprintf(buf, "such as a POST without a Content-Length.\r\n");
- send(client, buf, sizeof(buf), );
- }
- /**********************************************************************/
- /* Put the entire contents of a file out on a socket. This function
- * is named after the UNIX "cat" command, because it might have been
- * easier just to do something like pipe, fork, and exec("cat").
- * Parameters: the client socket descriptor
- * FILE pointer for the file to cat */
- /**********************************************************************/
- //读取服务器上的某个文件 写到socket套接字上
- void cat(int client, FILE *resource)
- {
- char buf[];
- fgets(buf, sizeof(buf), resource);
- //检测流上的文件结束符 如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。
- while (!feof(resource))
- {
- send(client, buf, strlen(buf), );
- fgets(buf, sizeof(buf), resource);
- }
- }
- /**********************************************************************/
- /* Inform the client that a CGI script could not be executed.
- * Parameter: the client socket descriptor. */
- /**********************************************************************/
- void cannot_execute(int client)
- {
- char buf[];
- sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "Content-type: text/html\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
- send(client, buf, strlen(buf), );
- }
- /**********************************************************************/
- /* Print out an error message with perror() (for system errors; based
- * on value of errno, which indicates system call errors) and exit the
- * program indicating an error. */
- /**********************************************************************/
- void error_die(const char *sc)
- {
- perror(sc);
- exit();
- }
- /**********************************************************************/
- /* Execute a CGI script. Will need to set environment variables as
- * appropriate.
- * Parameters: client socket descriptor
- * path to the CGI script */
- /**********************************************************************/
- //运行cgi程序 也是个主函数
- void execute_cgi(int client, const char *path,
- const char *method, const char *query_string)
- {
- //在父进程中,关闭cgi_input的读入端和cgi_output的写入端,如果post的话
- //把post数据写入到cgi_input,已被重定向到stdin,读取cgi_output的管道
- //输出到客户端,该管道输入是stdout,接着关闭所有管道,等待子进程结束。
- char buf[];
- int cgi_output[];
- int cgi_input[];
- //cgi_output[1] cgi_input[1] 为输入端
- //cgi_input[0] cgi_output[0] 为输出端
- pid_t pid;
- int status;
- int i;
- char c;
- int numchars = ;
- int content_length = -;
- buf[] = 'A';
- buf[] = '\0';
- //读入请求头
- if (strcasecmp(method, "GET") == )
- while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
- numchars = get_line(client, buf, sizeof(buf));
- else /* POST */
- {
- numchars = get_line(client, buf, sizeof(buf));
- while ((numchars > ) && strcmp("\n", buf))
- {
- buf[] = '\0';
- if (strcasecmp(buf, "Content-Length:") == )
- content_length = atoi(&(buf[]));
- numchars = get_line(client, buf, sizeof(buf));
- }
- if (content_length == -)
- {
- bad_request(client);
- return;
- }
- }
- sprintf(buf, "HTTP/1.0 200 OK\r\n");
- send(client, buf, strlen(buf), );
- if (pipe(cgi_output) < )
- {
- cannot_execute(client);
- return;
- }
- if (pipe(cgi_input) < )
- {
- cannot_execute(client);
- return;
- }
- if ( (pid = fork()) < )
- {
- cannot_execute(client);
- return;
- }
- if (pid == ) /* child: CGI script */
- {
- char meth_env[];
- char query_env[];
- char length_env[];
- //把stdout重定向到cgi_output的写入端
- dup2(cgi_output[], );
- //把stdin重定向到cgi_input的读入端
- dup2(cgi_input[], );
- //关闭cgi_output的读入端 和 cgi_input的 写入端
- close(cgi_output[]);
- close(cgi_input[]);
- //设置request_method的环境变量
- sprintf(meth_env, "REQUEST_METHOD=%s", method);
- putenv(meth_env);
- if (strcasecmp(method, "GET") == )
- {
- //设置query_string的环境变量
- sprintf(query_env, "QUERY_STRING=%s", query_string);
- putenv(query_env);
- }
- else /* POST */
- {
- //设置content_length的环境变量
- sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
- putenv(length_env);
- }
- //用execl运行cgi程序
- execl(path, path, NULL);
- exit();
- }
- else /* parent */
- {
- close(cgi_output[]);
- close(cgi_input[]);
- //关闭cgi_output的 写入端 和 cgi_input的读取端
- if (strcasecmp(method, "POST") == )
- //接受post的数据
- for (i = ; i < content_length; i++)
- {
- recv(client, &c, , );
- write(cgi_input[], &c, );
- //讲post数据写入cgi_input,并且重定向到stdin中
- }
- //读取cgi_output的管道输出到客户端,该管道输入时stdout
- while (read(cgi_output[], &c, ) > )
- send(client, &c, , );
- //关闭管道
- close(cgi_output[]);
- close(cgi_input[]);
- //等待子进程
- waitpid(pid, &status, );
- }
- }
- /**********************************************************************/
- /* Get a line from a socket, whether the line ends in a newline,
- * carriage return, or a CRLF combination. Terminates the string read
- * with a null character. If no newline indicator is found before the
- * end of the buffer, the string is terminated with a null. If any of
- * the above three line terminators is read, the last character of the
- * string will be a linefeed and the string will be terminated with a
- * null character.
- * Parameters: the socket descriptor
- * the buffer to save the data in
- * the size of the buffer
- * Returns: the number of bytes stored (excluding null) */
- /**********************************************************************/
- int get_line(int sock, char *buf, int size)
- {
- int i = ;
- char c = '\0';
- int n;
- while ((i < size - ) && (c != '\n'))
- {
- //每次接受一个字符
- n = recv(sock, &c, , );
- /* DEBUG printf("%02X\n", c); */
- if (n > )
- {
- if (c == '\r')
- {
- n = recv(sock, &c, , MSG_PEEK);
- /* DEBUG printf("%02X\n", c); */
- if ((n > ) && (c == '\n'))
- recv(sock, &c, , );
- else
- c = '\n';
- }
- buf[i] = c;
- i++;
- }
- else
- c = '\n';
- }
- buf[i] = '\0';
- return(i);
- }
- /**********************************************************************/
- /* Return the informational HTTP headers about a file. */
- /* Parameters: the socket to print the headers on
- * the name of the file */
- /**********************************************************************/
- //http 响应体
- void headers(int client, const char *filename)
- {
- char buf[];
- (void)filename; /* could use filename to determine file type */
- strcpy(buf, "HTTP/1.0 200 OK\r\n");//响应的状态行
- send(client, buf, strlen(buf), );
- strcpy(buf, SERVER_STRING);
- send(client, buf, strlen(buf), );
- sprintf(buf, "Content-Type: text/html\r\n"); //响应头
- send(client, buf, strlen(buf), );
- strcpy(buf, "\r\n"); //响应正文段
- send(client, buf, strlen(buf), );
- }
- /**********************************************************************/
- /* Give a client a 404 not found status message. */
- /**********************************************************************/
- void not_found(int client)
- {
- //客户端发送的请求无法实现
- char buf[];
- //响应行
- sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, SERVER_STRING);
- send(client, buf, strlen(buf), );
- //响应头
- sprintf(buf, "Content-Type: text/html\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "\r\n");
- //响应头和响应正文段之间有一个空行 表示响应行结束
- send(client, buf, strlen(buf), );
- //响应正文段 text/html
- sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "your request because the resource specified\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "is unavailable or nonexistent.\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "</BODY></HTML>\r\n");
- send(client, buf, strlen(buf), );
- }
- /**********************************************************************/
- /* Send a regular file to the client. Use headers, and report
- * errors to client if they occur.
- * Parameters: a pointer to a file structure produced from the socket
- * file descriptor
- * the name of the file to serve */
- /**********************************************************************/
- void serve_file(int client, const char *filename)
- {
- FILE *resource = NULL;
- int numchars = ;
- char buf[];
- buf[] = 'A';
- buf[] = '\0';
- while ((numchars > ) && strcmp("\n", buf)) /* read & discard headers */
- numchars = get_line(client, buf, sizeof(buf));
- resource = fopen(filename, "r");
- if (resource == NULL)
- not_found(client);
- else
- {
- headers(client, filename);
- cat(client, resource);
- }
- fclose(resource);
- }
- /**********************************************************************/
- /* This function starts the process of listening for web connections
- * on a specified port. If the port is 0, then dynamically allo //响应正文段cate a
- * port and modify the original port variable to reflect the actual
- * port.
- * Parameters: pointer to variable containing the port to connect on
- * Returns: the socket */
- /**********************************************************************/
- int startup(u_short *port)
- {
- int httpd = ;
- struct sockaddr_in name;
- //建立套接字
- httpd = socket(PF_INET, SOCK_STREAM, );
- if (httpd == -)
- error_die("socket");
- memset(&name, , sizeof(name));
- name.sin_family = AF_INET;
- name.sin_port = htons(*port);
- name.sin_addr.s_addr = htonl(INADDR_ANY);
- //绑定端口和ip
- if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < )
- error_die("bind");
- if (*port == ) /* if dynamically allocating a port */
- {
- int namelen = sizeof(name);
- if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -)
- error_die("getsockname");
- *port = ntohs(name.sin_port);
- }
- //监听
- if (listen(httpd, ) < )
- error_die("listen");
- return(httpd);
- }
- /**********************************************************************/
- /* Inform the client that the requested web method has not been
- * implemented.
- * Parameter: the client socket */
- /**********************************************************************/
- void unimplemented(int client)
- {
- //发回响应信息 http的请求方法不被接受
- char buf[];
- //返回501 错误 未实现 (Not implemented)是指Web 服务器不理解或不支持发送给它的 HTTP 数据流中找到的 HTTP 方法
- sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, SERVER_STRING);
- send(client, buf, strlen(buf), );
- sprintf(buf, "Content-Type: text/html\r\n");//响应头
- send(client, buf, strlen(buf), );
- sprintf(buf, "\r\n");
- send(client, buf, strlen(buf), ); //响应正文段
- sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "</TITLE></HEAD>\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
- send(client, buf, strlen(buf), );
- sprintf(buf, "</BODY></HTML>\r\n");
- send(client, buf, strlen(buf), );
- }
- /**********************************************************************/
- int main(void)
- {
- int server_sock = -;
- u_short port = ;
- int client_sock = -;
- struct sockaddr_in client_name;
- int client_name_len = sizeof(client_name);
- pthread_t newthread;
- server_sock = startup(&port);
- printf("httpd running on port %d\n", port);
- while ()
- {
- client_sock = accept(server_sock,
- (struct sockaddr *)&client_name,
- &client_name_len);
- //建立链接
- if (client_sock == -)
- error_die("accept");
- /* accept_request(client_sock); */
- //多线程进行控制
- if (pthread_create(&newthread , NULL, accept_request, client_sock) != )
- perror("pthread_create");
- }
- close(server_sock);
- return();
- }
tinyhttpd ------ C 语言实现最简单的 HTTP 服务器的更多相关文章
- go 语言实现一个简单的 web 服务器
学习Go语言的一些感受,不一定准确. 假如发生战争,JAVA一般都是充当航母战斗群的角色.一旦出动,就是护卫舰.巡洋舰.航母舰载机.预警机.电子战飞机.潜艇等等浩浩荡荡,杀将过去.(JVM,数十个JA ...
- 一个简单的 Web 服务器 [未完成]
最近学习C++,linux和网络编程,想做个小(mini)项目. 就去搜索引擎, 开源中国, Sourceforge上找http server的项目. 好吧,也去了知乎. 知乎上程序员氛围好, ...
- R语言:用简单的文本处理方法优化我们的读书体验
博客总目录:http://www.cnblogs.com/weibaar/p/4507801.html 前言 延续之前的用R语言读琅琊榜小说,继续讲一下利用R语言做一些简单的文本处理.分词的事情.其实 ...
- 踢爆IT劣书出版黑幕——由清华大学出版社之《C语言入门很简单》想到的(1)
1.前言与作者 首先声明,我是由于非常偶然的机会获得<C语言入门很简单>这本书的,绝对不是买的.买这种书实在丢不起那人. 去年这书刚出版时,在CU论坛举行试读推广,我当时随口说了几句(没说 ...
- 留念 C语言第一课简单的计算器制作
留念 C语言第一课简单的计算器制作 学C语言这么久了. /* 留念 C语言第一课简单的计算器制作 */ #include<stdio.h> #include<stdlib.h ...
- 用C语言编写一个简单的词法分析程序
问题描述: 用C或C++语言编写一个简单的词法分析程序,扫描C语言小子集的源程序,根据给定的词法规则,识别单词,填写相应的表.如果产生词法错误,则显示错误信息.位置,并试图从错误中恢复.简单的恢复方法 ...
- 用Go语言实现一个简单的聊天机器人
一.介绍 目的:使用Go语言写一个简单的聊天机器人,复习整合Go语言的语法和基础知识. 软件环境:Go1.9,Goland 2018.1.5. 二.回顾 Go语言基本构成要素:标识符.关键字.字面量. ...
- C语言之非常简单的几道题
C语言之非常简单的几道题(还是写写),比较简单吧,主要有几道题的数据类型(如,第三题)和语句顺序(如,第二题)需要注意一小下下. 1. 求表达式S=1*2*3……*N的值大于150时,最小的N的值 / ...
- C 语言实例 - 实现简单的计算器
C 语言实例 - 实现简单的计算器 实现加减乘除计算. 实例 # include <stdio.h> int main() { char operator; double firstNum ...
随机推荐
- Redux的应该注意的问题
1. Store中的State修改不能直接修改原有的State,若直接修改State,则redux中的所有操作都将指向 内存中的同一个state,将无法获取每一次操作前后的state,就无法追溯sta ...
- idea导入web项目tomcat
概述 主要分为项目配置和tomcat配置两大步骤. 一.项目配置 打开idea,选择导入项 选择将要打开的项目路径后,继续选择项目的原本类型(后续引导设置会根据原本的项目类型更新成idea的项目),此 ...
- CentOS7搭建 Hadoop + HBase + Zookeeper集群
摘要: 本文主要介绍搭建Hadoop.HBase.Zookeeper集群环境的搭建 一.基础环境准备 1.下载安装包(均使用当前最新的稳定版本,截止至2017年05月24日) 1)jdk-8u131 ...
- 论C++11 中vector的N种遍历方法
随着C++11标准的出现,C++标准添加了许多有用的特性,C++代码的写法也有比较多的变化. vector是经常要使用到的std组件,对于vector的遍历,本文罗列了若干种写法. (注:本文中代码为 ...
- HDU1298 字典树+dfs
T9 Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Total Submissi ...
- 使用tcpdump监控网络消息发送
tcpdump是一个用于截取网络分组,并输出分组内容的工具,简单说就是数据包抓包工具.tcpdump凭借强大的功能和灵活的截取策略,使其成为Linux系统下用于网络分析和问题排查的首选工具. tcpd ...
- 编译skia静态库时,图片解码库无法注册的问题
转载:http://www.cnblogs.com/imlucky/archive/2012/08/01/2617851.html 今天编译skia库,增加图片解码库时总是无效.按照此博客的方法修改后 ...
- java中String字符串的替换函数:replace与replaceAll的区别
例如有如下x的字符串 String x = "[kllkklk\\kk\\kllkk]";要将里面的“kk”替换为++,可以使用两种方法得到相同的结果 replace(CharSe ...
- 使用jquery.qrcode生成二维码及常见问题解决方案
转载文章 使用jquery.qrcode生成二维码及常见问题解决方案 一.jquery.qrcode.js介 jquery.qrcode.js 是一个纯浏览器 生成 QRcode 的 jQuery ...
- 【C++ STL】Deques
1.结构 容器deque和vector非常相似,也是采用动态数组来管理元素,提供随机存取,有着和vector几乎一样的接口,不同的是deque的动态数组头尾都开放,因此可以在头尾都可以进行快速的安插和 ...