Tinyhttpd精读解析
首先,本人刚刚开始开源代码精读,写的不对的地方,大家轻拍,一起进步。本文是对Tinyhttpd的一次精读,大家每天都在用着http服务,很多人也一直活跃在上层,使用IIS、Apache等,大家是否想看看http服务器大概是怎么运作的,通过一个500多行的源码加上完整的注释,和大家逛一逛http服务器。Tinyhttpd真的非常适合阅读尤其是刚入门的,清晰的代码,简单的makefile...其实有很多分析tinyghttpd的,这边抱着人家写的是人家,自己写的才是自己的态度,写的尽量详细,尽量简单,很多都写在代码注释里面,也把学习中的一些坑翻出来和大家一起读读,吸取将近20年前的大神通过500行代码带给我们的财富。不管你是写c的,或者C#,js亦或java,只要用到http的都欢迎读读。
大家看后如果觉得我有写的不对的地方,欢迎指出~~
附上注解代码的github地址:https://github.com/nengm/Tinyhttpd
1.首先是一张图 全解了Tinyhttp是如何运作的,我觉得图说明比我用文字描述的要清晰,语言功底不太给力。
2. 主要函数简略说明,代码中进行了详细注释。
main 主函数
startup 绑定监听套接字
accept_request 每次收到请求,创建一个线程来处理接受到的请求
serve_file 接读取文件返回给请求的http客户端
execute_cgi 执行cgi文件
3.注意点
注意点1:
index.html必须没有执行权限,否则看不到内容,并且会产生Program received signal SIGPIPE, Broken pipe,因为程序中如果有可执行权限会当cgi脚本处理。所以假如html有执行权限先把它去除了,chmod 600 index.html
color.cgi、date.cgi必须要有执行权限。
注意点2:
color.cgi是用perl写的,相信大家很少接触了。所以可以引用网上一个简单的例子,换成一个shell写的cgi测试
#!/bin/bash
echo "Content-Type: text/html"
echo
echo "<HTML><BODY>"
echo "<CENTER>Today is:</CENTER>"
echo "<CENTER><B>"
date
echo "</B></CENTER>"
echo "</BODY></HTML>"
4.操作
1.执行make,生成成功,./httpd启动成功。
2.如果在当前linux下的firefox下执行。直接在浏览器中输入
3.在windows中测试,
4.测试执行自带的perl写的cgi脚本
5.进入index2.html页面,测试shell写的cgi脚本。
5.代码
- /* 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"
- //每次收到请求,创建一个线程来处理接受到的请求
- //把client_sock转成地址作为参数传入pthread_create
- void accept_request(void *arg);
- //错误请求
- void bad_request(int);
- //读取文件
- void cat(int, FILE *);
- //无法执行
- void cannot_execute(int);
- //错误输出
- void error_die(const char *);
- //执行cig脚本
- void execute_cgi(int, const char *, const char *, const char *);
- //得到一行数据,只要发现c为\n,就认为是一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket用读出
- //如果是下个字符则不处理,将c置为\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\n
- int get_line(int, char *, int);
- //返回http头
- void headers(int, const char *);
- //没有发现文件
- void not_found(int);
- //如果不是CGI文件,直接读取文件返回给请求的http客户端
- void serve_file(int, const char *);
- //开启tcp连接,绑定端口等操作
- int startup(u_short *);
- //如果不是Get或者Post,就报方法没有实现
- void unimplemented(int);
- // Http请求,后续主要是处理这个头
- //
- // GET / HTTP/1.1
- // Host: 192.168.0.23:47310
- // Connection: keep-alive
- // Upgrade-Insecure-Requests: 1
- // User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
- // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*; q = 0.8
- // Accept - Encoding: gzip, deflate, sdch
- // Accept - Language : zh - CN, zh; q = 0.8
- // Cookie: __guid = 179317988.1576506943281708800.1510107225903.8862; monitor_count = 5
- //
- // POST / color1.cgi HTTP / 1.1
- // Host: 192.168.0.23 : 47310
- // Connection : keep - alive
- // Content - Length : 10
- // Cache - Control : max - age = 0
- // Origin : http ://192.168.0.23:40786
- // Upgrade - Insecure - Requests : 1
- // User - Agent : Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 55.0.2883.87 Safari / 537.36
- // Content - Type : application / x - www - form - urlencoded
- // Accept : text / html, application / xhtml + xml, application / xml; q = 0.9, image / webp, */*;q=0.8
- // Referer: http://192.168.0.23:47310/
- // Accept-Encoding: gzip, deflate
- // Accept-Language: zh-CN,zh;q=0.8
- // Cookie: __guid=179317988.1576506943281708800.1510107225903.8862; monitor_count=281
- // Form Data
- // color=gray
- /**********************************************************************/
- /* 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(void *arg)
- {
- //socket
- int client = (intptr_t)arg;
- 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;
- //根据上面的Get请求,可以看到这边就是取第一行
- //这边都是在处理第一条http信息
- //"GET / HTTP/1.1\n"
- numchars = get_line(client, buf, sizeof(buf));
- i = ; j = ;
- //第一行字符串提取Get
- while (!ISspace(buf[j]) && (i < sizeof(method) - ))
- {
- method[i] = buf[j];
- i++; j++;
- }
- //结束
- method[i] = '\0';
- //判断是Get还是Post
- if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
- {
- unimplemented(client);
- return;
- }
- //如果是POST,cgi置为1
- if (strcasecmp(method, "POST") == )
- cgi = ;
- i = ;
- //跳过空格
- while (ISspace(buf[j]) && (j < sizeof(buf)))
- j++;
- //得到 "/" 注意:如果你的http的网址为http://192.168.0.23:47310/index.html
- // 那么你得到的第一条http信息为GET /index.html HTTP/1.1,那么
- // 解析得到的就是/index.html
- while (!ISspace(buf[j]) && (i < sizeof(url) - ) && (j < sizeof(buf)))
- {
- url[i] = buf[j];
- i++; j++;
- }
- url[i] = '\0';
- //判断Get请求
- if (strcasecmp(method, "GET") == )
- {
- query_string = url;
- while ((*query_string != '?') && (*query_string != '\0'))
- query_string++;
- if (*query_string == '?')
- {
- cgi = ;
- *query_string = '\0';
- query_string++;
- }
- }
- //路径
- sprintf(path, "htdocs%s", url);
- //默认地址,解析到的路径如果为/,则自动加上index.html
- if (path[strlen(path) - ] == '/')
- strcat(path, "index.html");
- //获得文件信息
- if (stat(path, &st) == -) {
- //把所有http信息读出然后丢弃
- 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");
- //如果你的文件默认是有执行权限的,自动解析成cgi程序,如果有执行权限但是不能执行,会接受到报错信号
- if ((st.st_mode & S_IXUSR) ||
- (st.st_mode & S_IXGRP) ||
- (st.st_mode & S_IXOTH) )
- cgi = ;
- if (!cgi)
- //接读取文件返回给请求的http客户端
- serve_file(client, path);
- else
- //执行cgi文件
- execute_cgi(client, path, method, query_string);
- }
- //执行完毕关闭socket
- 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 */
- /**********************************************************************/
- //得到文件内容,发送
- void cat(int client, FILE *resource)
- {
- char buf[];
- fgets(buf, sizeof(buf), resource);
- //循环读
- 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 */
- /**********************************************************************/
- void execute_cgi(int client, const char *path,
- const char *method, const char *query_string)
- {
- //缓冲区
- char buf[];
- //2根管道
- int cgi_output[];
- int cgi_input[];
- //进程pid和状态
- pid_t pid;
- int status;
- int i;
- char c;
- //读取的字符数
- int numchars = ;
- //http的content_length
- int content_length = -;
- //默认字符
- buf[] = 'A'; buf[] = '\0';
- //忽略大小写比较字符串
- if (strcasecmp(method, "GET") == )
- //读取数据,把整个header都读掉,以为Get写死了直接读取index.html,没有必要分析余下的http信息了
- 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))
- {
- //如果是POST请求,就需要得到Content-Length,Content-Length:这个字符串一共长为15位,所以
- //取出头部一句后,将第16位设置结束符,进行比较
- //第16位置为结束
- buf[] = '\0';
- if (strcasecmp(buf, "Content-Length:") == )
- //内存从第17位开始就是长度,将17位开始的所有字符串转成整数就是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), );
- //建立output管道
- if (pipe(cgi_output) < ) {
- cannot_execute(client);
- return;
- }
- //建立input管道
- if (pipe(cgi_input) < ) {
- cannot_execute(client);
- return;
- }
- // fork后管道都复制了一份,都是一样的
- // 子进程关闭2个无用的端口,避免浪费
- // ×<------------------------->1 output
- // 0<-------------------------->× input
- // 父进程关闭2个无用的端口,避免浪费
- // 0<-------------------------->× output
- // ×<------------------------->1 input
- // 此时父子进程已经可以通信
- //fork进程,子进程用于执行CGI
- //父进程用于收数据以及发送子进程处理的回复数据
- if ( (pid = fork()) < ) {
- cannot_execute(client);
- return;
- }
- if (pid == ) /* child: CGI script */
- {
- char meth_env[];
- char query_env[];
- char length_env[];
- //子进程输出重定向到output管道的1端
- dup2(cgi_output[], );
- //子进程输入重定向到input管道的0端
- dup2(cgi_input[], );
- //关闭无用管道口
- close(cgi_output[]);
- close(cgi_input[]);
- //CGI环境变量
- sprintf(meth_env, "REQUEST_METHOD=%s", method);
- putenv(meth_env);
- if (strcasecmp(method, "GET") == ) {
- sprintf(query_env, "QUERY_STRING=%s", query_string);
- putenv(query_env);
- }
- else { /* POST */
- sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
- putenv(length_env);
- }
- //替换执行path
- execl(path, path, NULL);
- //int m = execl(path, path, NULL);
- //如果path有问题,例如将html网页改成可执行的,但是执行后m为-1
- //退出子进程,管道被破坏,但是父进程还在往里面写东西,触发Program received signal SIGPIPE, Broken pipe.
- exit();
- } else { /* parent */
- //关闭无用管道口
- close(cgi_output[]);
- close(cgi_input[]);
- if (strcasecmp(method, "POST") == )
- for (i = ; i < content_length; i++) {
- //得到post请求数据,写到input管道中,供子进程使用
- recv(client, &c, , );
- write(cgi_input[], &c, );
- }
- //从output管道读到子进程处理后的信息,然后send出去
- 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) */
- /**********************************************************************/
- //得到一行数据,只要发现c为\n,就认为是一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket用读出
- //如果是下个字符则不处理,将c置为\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\n
- 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就读走
- n = recv(sock, &c, , MSG_PEEK);
- /* DEBUG printf("%02X\n", c); */
- if ((n > ) && (c == '\n'))
- recv(sock, &c, , );
- else
- //不是\n(读到下一行的字符)或者没读到,置c为\n 跳出循环,完成一行读取
- 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的headers
- 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), );
- 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 */
- /**********************************************************************/
- //如果不是CGI文件,直接读取文件返回给请求的http客户端
- 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 allocate 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);
- //绑定socket
- if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < )
- error_die("bind");
- //如果端口没有设置,提供个随机端口
- if (*port == ) /* if dynamically allocating a port */
- {
- socklen_t 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)
- {
- char buf[];
- 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;
- //这边要为socklen_t类型
- socklen_t client_name_len = sizeof(client_name);
- pthread_t newthread;
- server_sock = startup(&port);
- printf("httpd running on port %d\n", port);
- while ()
- {
- //接受请求,函数原型
- //#include <sys/types.h>
- //#include <sys/socket.h>
- //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- client_sock = accept(server_sock,
- (struct sockaddr *)&client_name,
- &client_name_len);
- if (client_sock == -)
- error_die("accept");
- /* accept_request(client_sock); */
- //每次收到请求,创建一个线程来处理接受到的请求
- //把client_sock转成地址作为参数传入pthread_create
- if (pthread_create(&newthread, NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != )
- perror("pthread_create");
- }
- close(server_sock);
- return();
- }
Tinyhttpd精读解析的更多相关文章
- TinyHttpd代码解析
十一假期,闲来无事.看了几个C语言开源代码.http://www.cnblogs.com/TinyHttpd 这里本来想解析一下TinyHttpd的代码,但是在网上一搜,发现前辈们已经做的很好了.这里 ...
- 精读《V8 引擎 Lazy Parsing》
1. 引言 本周精读的文章是 V8 引擎 Lazy Parsing,看看 V8 引擎为了优化性能,做了怎样的尝试吧! 这篇文章介绍的优化技术叫 preparser,是通过跳过不必要函数编译的方式优化性 ...
- Python:Sqlmap源码精读之解析xml
XML <?xml version="1.0" encoding="UTF-8"?> <root> <!-- MySQL --&g ...
- .net core 源码解析-web app是如何启动并接收处理请求
最近.net core 1.1也发布了,蹒跚学步的小孩又长高了一些,园子里大家也都非常积极的在学习,闲来无事,扒拔源码,涨涨见识. 先来见识一下web站点是如何启动的,如何接受请求,.net core ...
- tinyhttpd源码分析
我们经常使用网页,作为开发人员我们也部署过httpd服务器,比如开源的apache,也开发过httpd后台服务,比如fastcgi程序,不过对于httpd服务器内部的运行机制,却不是非常了解,前几天看 ...
- tinyhttpd服务器源码学习
下载地址:http://sourceforge.net/projects/tinyhttpd/ /* J. David's webserver */ /* This is a simple webse ...
- HTTP服务器的本质:tinyhttpd源码分析及拓展
已经有一个月没有更新博客了,一方面是因为平时太忙了,另一方面是想积攒一些干货进行分享.最近主要是做了一些开源项目的源码分析工作,有c项目也有python项目,想提升一下内功,今天分享一下tinyhtt ...
- 开源代码学习之Tinyhttpd
想开始陆续研究一些感兴趣的开源代码于是先挑一个代码量短的来过渡一下,写这篇博客的目的是记录下自己学习的过程.Tinyhttpd算是一个微型的web服务器,浏览器与Web服务器之间的通信采用的是Http ...
- 精读《syntax-parser 源码》
1. 引言 syntax-parser 是一个 JS 版语法解析器生成器,具有分词.语法树解析的能力. 通过两个例子介绍它的功能. 第一个例子是创建一个词法解析器 myLexer: import { ...
随机推荐
- Dice (II) (DP)唉,当时没做出来
Dice (II) Time Limit: 3000MS Memory Limit: 32768KB 64bit IO Format: %lld & %llu [Submit] [ ...
- Day2 python基础学习
http://www.pythondoc.com/ Python中文学习大本营 本节内容: 一.字符串操作 二.列表操作 三.元组操作 四.字典操作 五.集合操作 六.字符编码操作 一.字符串操作 1 ...
- javascript中的DOM介绍(一)
一.基础知识点 1.DOM是文档对象模型,是针对HTML和XML文档的一个API(应用程序接口) 2.DOM描绘了一个层次化的节点数,允许开发人员进行添加,移除个修改等操作 3.IE浏览器中所有的DO ...
- JAVA提高二:枚举
JDK5.0中有一个非常有用的特性:枚举,这个特性以前在C语言中出现过,后来JDK出现后,开始觉得没有必要,但随着使用JAVA语言的人数增多,发现大家对枚举的需求非常大,于是又加入了此特性,下面我们来 ...
- 浅析php curl_multi_*系列函数进行批量http请求
何起: 一系列 数量很大 数据不热 还希望被蜘蛛大量抓取的页面,在蜘蛛抓取高峰时,响应时间会被拉得很高. 前人做了这样一个事儿:页面分3块,用3个内部接口提供,入口文件用curl_multi_*系列函 ...
- sqlDependency监控数据库数据变化,自动通知
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...
- 将 C# 枚举反序列化为 JSON 字符串 基础理论
该转换过程需要引用 Newtonsoft.JSON,这其中的转换过程还是蛮有意思的. 一.定义枚举 /// <summary> /// 托寄物品枚举 /// </summary> ...
- win10 uwp 后台获取资源
本文告诉大家,从后台代码获取界面定义的资源. 如果一个资源是写在 App 的资源,那么如何使用代码去获得他? 简单的方法是使用下面的代码 Application.Current.Resources[& ...
- Linux-Nand Flash驱动(分析MTD层并制作NAND驱动)
1.本节使用的nand flash型号为K9F2G08U0M,它的命令如下: 1.1我们以上图的read id(读ID)为例,它的时序图如下: 首先需要使能CE片选 1)使能CLE 2)发送0X90命 ...
- 使用Xmanager通过XDMCP连接远程Centos 7 (摘自xmanager官方博客)
Using Xmanager to connect to remote CentOS 7 via XDMCP Gnome in CentOS 7 tries to use local hardware ...