tinyhttpd源码详解

tinyhttpd是一轻量级的web 服务器,最近几天终于抽出时间来研究研究了。其源码百度即可下载,500多行,确实是学习linux编程的好材料。很多网友都写了关于tinyhttpd的博文,但是我还是觉得不够深入,严格说是写得不够深入,往往就是把500多行代码一扔,分析下主要过程,画个流程图就完事了。我怎么觉得还有很多东西可以挖一挖呢,也许还可再调整一下代码,虽然目前也不清楚可调整多少,待我细细道来。

我分析的过程就按主要路线走,也就是这样一个主干道流程:服务器创建socket并监听某一端口->浏览器输入url发出请求->服务器收到请求,创建线程处理请求,主线程继续等待->新线程读取http请求,并解析相关字段,读取文件内容或者执行CGI程序并返回给浏览器->关闭客户端套接字,新线程退出

咱们先来看看main函数

int main(void)
{
 int server_sock = -1;
 u_short port = 0;
 int client_sock = -1;
 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 (1)
 {
  client_sock = accept(server_sock,
                       (struct sockaddr *)&client_name,
                       &client_name_len);
  if (client_sock == -1)
   error_die("accept");
 if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
   perror("pthread_create");
 }

 close(server_sock);

 return(0);
}

这段代码,只要是稍微了解linux的网络编程就很好懂,创建服务端socket,绑定、监听、等待客户端连接。只不过作者把这些步骤都放在了一个叫startup的函数里。那来看startup

int startup(u_short *port)
{
 int httpd = 0;
 struct sockaddr_in name;

 httpd = socket(PF_INET, SOCK_STREAM, 0);
 if (httpd == -1)
  error_die("socket");
 memset(&name, 0, sizeof(name));//也可以用bzero
 name.sin_family = AF_INET;
 name.sin_port = htons(*port);
 name.sin_addr.s_addr = htonl(INADDR_ANY);//任何网络接口
 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
  error_die("bind");
 if (*port == 0)  /* if dynamically allocating a port */
 {
  int namelen = sizeof(name);
  if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
   error_die("getsockname");
  *port = ntohs(name.sin_port);//系统动态分配一个端口号
 }
 if (listen(httpd, 5) < 0)
  error_die("listen");
 return(httpd);//返回服务套接字描述符
}

很常见的步骤,就不多说了。

此后,服务端就accept等待连接,作者其实没有关心客户端来自哪里,那accept的第二、第三参数完全可以为NULL。接着就创建线程把客户端套接字作为参数传过去了,由新线程处理请求,这是服务器编程的常用手段,提高并发性。注意这里的线程函数并不完全合法,至少在linux上就不符合线程函数的原型定义,编译时编译器也只是警告而未报错。

接下来重点就在线程函数accept_request上了

void accept_request(int client)
{
 char buf[1024];
 int numchars;
 char method[255];
 char url[255];
 char path[512];
 size_t i, j;
 struct stat st;
 int cgi = 0;      /* becomes true if server decides this is a CGI
                    * program */
 char *query_string = NULL;

 numchars = get_line(client, buf, sizeof(buf));
 i = 0; j = 0;
 while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
 {
  method[i] = buf[j];
  i++; j++;
 }
 method[i] = '\0';

 if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
 {
  unimplemented(client);
  return;
 }

 if (strcasecmp(method, "POST") == 0)
  cgi = 1;

 i = 0;
 while (ISspace(buf[j]) && (j < sizeof(buf)))
  j++;
 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
 {
  url[i] = buf[j];
  i++; j++;
 }
 url[i] = '\0';

 if (strcasecmp(method, "GET") == 0)
 {
  query_string = url;
  while ((*query_string != '?') && (*query_string != '\0'))
   query_string++;
  if (*query_string == '?')
  {
   cgi = 1;
   *query_string = '\0';
   query_string++;
  }
 }

 sprintf(path, "htdocs%s", url);
 if (path[strlen(path) - 1] == '/')
  strcat(path, "index.html");
 if (stat(path, &st) == -1) {
  while ((numchars > 0) && 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 = 1;
  if (!cgi)
   serve_file(client, path);
  else
   execute_cgi(client, path, method, query_string);
 }

 close(client);
}

首先很关键一点要理解get_line的意思。我们要知道当在浏览器中输入url后enter之后,它发给服务器是文本型的字符串,遵循http请求格式,类似下面的:

GET / HTTP/1.1

HOST:www.abc.com

Content-type:text/html

...

get_line干的事就是读取一行,并且不管原来是以\n还是\r\n结束,均转化为以\n再加\0字符结束。其实现如下:

int get_line(int sock, char *buf, int size)
{
 int i = 0;
 char c = '\0';
 int n;

 while ((i < size - 1) && (c != '\n'))
 {
  n = recv(sock, &c, 1, 0);//从sock中一次读一个字符,循环读
  if (n > 0)
  {
   if (c == '\r') //如果读到回车,一般紧接着字符就是\n
   {
    n = recv(sock, &c, 1, MSG_PEEK);
    if ((n > 0) && (c == '\n'))
     recv(sock, &c, 1, 0);//这时再读,c还是\n,循环跳出
    else
     c = '\n';
   }
   buf[i] = c;
   i++;
  }
  else
   c = '\n';
 }
 buf[i] = '\0';

 return(i);//返回读取的字符数
}

get_line完后,就是开始解析第一行,判断是GET方法还是POST方法,目前只支持这两种。如果是POST,还是把cgi置1,表明要运行CGI程序;如果是GET方法且附带以?开头的参数时,也认为是执行CGI程序

还是获取要访问的url,可以是很常见的/,/index.html等等。该程序默认为根目录是在htdocs下的,且默认文件是index.html。另外还判断了给定文件是否有可执权限,如果有,则认为是CGI程序。最后根据变量cgi的值来进行相应选择:读取静态文件或者执行CGI程序返回结果。

我们首先看看最简单的静态文件情况,调用函数serve_file

void serve_file(int client, const char *filename)
{
 FILE *resource = NULL;
 int numchars = 1;
 char buf[1024];

 buf[0] = 'A'; buf[1] = '\0';
 while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  numchars = get_line(client, buf, sizeof(buf));//必须要读完客户端发来的头部,否则后来的send不能正常显示在浏览器中。

 resource = fopen(filename, "r");
 if (resource == NULL)
  not_found(client);
 else
 {
  headers(client, filename);
  cat(client, resource);
 }
 fclose(resource);
}

将文件名作为参数,首先读完客户端的头部,然后打开创建文件流。为了模拟http响应,首先向客户端发送头部,头部信息至少包含以下几点:

http/1.0 200 ok

server:

content-type:

\r\n(一个空白行,标识头部结束)

最后发送数据体部分,即文件内容,在cat方法中,fgets每读入一行,就send,直到末尾。headers和cat函数就不在这里列出了。下面,我们来看看一个具体测试例子,紧接着在gdb中调试

我在根目录下的htdocs下建立一个新文件index2.html,内容如下:

<a href="http://10.108.222.96:54205/test.sh">Display Date</a>

我在这里放了一个链接,href部分是关于cgi的,先不管,就只看文本部分能否显示在浏览器中。

首先编译之后直接运行./httpd,程序打印"httpd running on port 53079"

我们在浏览器中访问index2.html文件,如下图所示:

文本能正确显示了。那如何在gdb中调试观察呢?

[email protected]:~/chenshi/tinyhttpd-0.1.0$ gdb attach 7029  【通过ps查看httpd进程的PID,然后gdb attach之】
Attaching to process 7029
Reading symbols from /home/xiaoqiang/chenshi/tinyhttpd-0.1.0/httpd...done.
Reading symbols from /lib/i386-linux-gnu/libpthread.so.0...(no debugging symbols found)...done.
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".
Loaded symbols for /lib/i386-linux-gnu/libpthread.so.0
Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/i386-linux-gnu/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
0xb7750424 in __kernel_vsyscall ()
(gdb) bt
#0  0xb7750424 in __kernel_vsyscall ()
#1  0xb772dc08 in accept () from /lib/i386-linux-gnu/libpthread.so.0
#2  0x0804a8d6 in main () at httpd.c:516
(gdb) l accept_request
warning: Source file is more recent than executable.
47	/* A request has caused a call to accept() on the server port to
48	 * return.  Process the request appropriately.
49	 * Parameters: the socket connected to the client */
50	/**********************************************************************/
51	void accept_request(int client)
52	{
53	 char buf[1024];
54	 int numchars;
55	 char method[255];
56	 char url[255];
(gdb) l
57	 char path[512];
58	 size_t i, j;
59	 struct stat st;
60	 int cgi = 0;      /* becomes true if server decides this is a CGI
61	                    * program */
62	 char *query_string = NULL;
63
64	 numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行
65	 i = 0; j = 0;
66	 while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
(gdb) b 64 【在64行设置断点,观察读到的是什么】
Breakpoint 1 at 0x8048b3f: file httpd.c, line 64.
(gdb) c
Continuing. 【直到在浏览器中发起了请求,后面的才会打印出来】
[New Thread 0xb63feb40 (LWP 7655)]
[Switching to Thread 0xb63feb40 (LWP 7655)]

Breakpoint 1, accept_request (client=4) at httpd.c:64
64	 numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行
(gdb) n
65	 i = 0; j = 0;
(gdb) p buf 【打印读到的一行】
$1 = "GET /index2.html HTTP/1.1\n", '\000' <repeats 997 times> 【果真是HTTP GET请求的第一行】
(gdb) l
60	 int cgi = 0;      /* becomes true if server decides this is a CGI
61	                    * program */
62	 char *query_string = NULL;
63
64	 numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行
65	 i = 0; j = 0;
66	 while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
67	 {
68	  method[i] = buf[j];
69	  i++; j++;
(gdb) l
70	 }
71	 method[i] = '\0';//获取到了HTTP方法
72
73	 if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
74	 {
75		 //忽略大小写比较
76	  unimplemented(client);
77	  return;//尚未支持的请求方法,线程返回
78	 }
79
(gdb) l serve_file 【其它的细节调试就不在这里演示了,直接跳到serve_file里】
412	 * Parameters: a pointer to a file structure produced from the socket
413	 *              file descriptor
414	 *             the name of the file to serve */
415	/**********************************************************************/
416	void serve_file(int client, const char *filename)
417	{
418	 FILE *resource = NULL;
419	 int numchars = 1;
420	 char buf[1024];
421
(gdb) l
422	 buf[0] = 'A'; buf[1] = '\0';
423	 while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
424	  numchars = get_line(client, buf, sizeof(buf));
425
426	 resource = fopen(filename, "r");
427	 if (resource == NULL)
428	  not_found(client);
429	 else
430	 {
431	  headers(client, filename);
(gdb) b 426 【在426行设置断点】
Breakpoint 2 at 0x804a247: file httpd.c, line 426.
(gdb) c
Continuing.

Breakpoint 2, serve_file (client=4, filename=0xb63fdf4e "htdocs/index2.html") at httpd.c:426
426	 resource = fopen(filename, "r");
(gdb) p filename
$2 = 0xb63fdf4e "htdocs/index2.html"
(gdb) n
427	 if (resource == NULL)
(gdb) n
431	  headers(client, filename);
(gdb) n
432	  cat(client, resource);
(gdb) s 【进入cat里面看看】
cat (client=4, resource=0xb6c00468) at httpd.c:170
170	{
(gdb) l
165	 * easier just to do something like pipe, fork, and exec("cat").
166	 * Parameters: the client socket descriptor
167	 *             FILE pointer for the file to cat */
168	/**********************************************************************/
169	void cat(int client, FILE *resource)
170	{
171	 char buf[1024];
172
173	 fgets(buf, sizeof(buf), resource);
174	 while (!feof(resource))
(gdb) n
173	 fgets(buf, sizeof(buf), resource);
(gdb) n
174	 while (!feof(resource))
(gdb) p buf 【讲到了index2.html的一行,然后send】
$3 = "<a href=\"http://10.108.222.96:54205/test.sh\">Display Date</a>\n", '\000' <repeats 306 times>, "\"\225^\267\000\000\000\000 \312q\267\000\320t\267 \000\000\000 \312q\267\304Re\267 \000\000\000El^\267\001\000\000\000\000\320t\267 \000\000\000\364\277q\267\360\331?\266V\003_\267\364\277q\267 \000\000\000 \312q\267\000\320t\267\000\000\000\000$k^\267 \312q\267\000\320t\267 ", '\000' <repeats 15 times>, "A\252\004\b\364\277q\267 \000\000\000\377\377\377\377\000\000\000\000\236\201^\267 ", '\000' <repeats 23 times>, " \312q\267U\205^\267 \312q\267\000\320t\267 ", '\000' <repeats 19 times>"\364, \277q\267\001\000\000\000R\252\004\b\000\000\000\000\343v^\267"...
(gdb) n
176	  send(client, buf, strlen(buf), 0);
(gdb) n
177	  fgets(buf, sizeof(buf), resource);
(gdb) n
174	 while (!feof(resource))
(gdb) n
179	}
(gdb) n
serve_file (client=4, filename=0xb63fdf4e "htdocs/index2.html") at httpd.c:434
434	 fclose(resource);
(gdb) bt
#0  serve_file (client=4, filename=0xb63fdf4e "htdocs/index2.html") at httpd.c:434
#1  0x08048f83 in accept_request (client=4) at httpd.c:130
#2  0xb7726d4c in start_thread () from /lib/i386-linux-gnu/libpthread.so.0
#3  0xb7665b8e in clone () from /lib/i386-linux-gnu/libc.so.6
(gdb) n
435	}
(gdb) s
accept_request (client=4) at httpd.c:139
139	 close(client); <span style="background-color: rgb(255, 255, 255);">【直到运行在这里,浏览器的请求才会真正停止,意味着标签栏那个不断旋转的标志就停了】</span>
(gdb) s
140	}
(gdb) s
0xb7726d4c in start_thread () from /lib/i386-linux-gnu/libpthread.so.0
(gdb) s
Single stepping until exit from function start_thread,
which has no line number information.
[New Thread 0xb5bfdb40 (LWP 7656)]
[Switching to Thread 0xb5bfdb40 (LWP 7656)]

Breakpoint 1, accept_request (client=4) at httpd.c:64
64	 numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行
(gdb) n
[Thread 0xb63feb40 (LWP 7655) exited]
65	 i = 0; j = 0;
(gdb) p buf
$4 = "GET /favicon.ico HTTP/1.1\n", '\000' <repeats 997 times> 【再读一行时,竟读到favicon.ico,目前没弄明白这怎么回事】
(gdb) 

前面已说过,tinyhttpd目前就支持两种请求形式,纯get请求或者带?的get和直接POST请求。了解到源码htdocs目录下的cgi都是perl写的,不知读者你懂不懂,反正博主我不懂,所以就改一改,改成自己的需求,用shell写。正如index2.html所示:

<a href="http://10.108.222.96:54205/test.sh">Display Date</a>

test.sh脚本如下:

#!/bin/sh

#echo "Content-type:text/html"

echo

echo "<html><head><meta charset="utf-8"><title>MyTitle</title></head><body>"

time=`date`

echo "<p>Server Time:$time"

echo "</body></html>"

即包括服务器响应给客户的字符数据,顺便把服务器时间传过去。注意要加test.sh添加执行权限,才会被视为执行cgi程序,且href中的端口号要改为你具体的端口号,这里只是个示例。来看当在浏览器中点击“Display Date”时,服务器作出的响应:

(gdb) l execute_cgi 【为了节省空间,以下内容我删除了无关内容】
warning: Source file is more recent than executable.
214	 * Parameters: client socket descriptor
215	 *             path to the CGI script */
216	/**********************************************************************/
217	void execute_cgi(int client, const char *path,
218	                 const char *method, const char *query_string)
219	{
220	 char buf[1024];
229
230	 buf[0] = 'A'; buf[1] = '\0';
231	 if (strcasecmp(method, "GET") == 0)
(gdb) b 231 【在execute_cgi处设置断点】
Breakpoint 1 at 0x8049555: file httpd.c, line 231.
(gdb) c
Continuing. 【当在浏览器发起请求时,serve_file被调用,但此时断点在execute_cgi处,所以此处没有反应直到鼠标点击链接】
[New Thread 0xb7567b40 (LWP 7708)]
[Thread 0xb7567b40 (LWP 7708) exited]
[New Thread 0xb6bffb40 (LWP 7709)]
[Thread 0xb6bffb40 (LWP 7709) exited]
[New Thread 0xb63feb40 (LWP 7710)]
[Switching to Thread 0xb63feb40 (LWP 7710)]

Breakpoint 1, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",
    query_string=0xb63fe255 "") at httpd.c:231
231	 if (strcasecmp(method, "GET") == 0)
(gdb) info args 【查看此函数调用参数值】
client = 4
path = 0xb63fdf4e "htdocs/test.sh" 【文件为test.sh脚本】
method = 0xb63fe14e "GET"
query_string = 0xb63fe255 ""
257
258	 if (pipe(cgi_output) < 0) {
259	  cannot_execute(client);
260	  return;
261	 }
262	 if (pipe(cgi_input) < 0) {
263	  cannot_execute(client);
264	  return;
265	 }
266
(gdb) b 258 【在创建管道处设置断点】
Breakpoint 2 at 0x804973e: file httpd.c, line 258.
(gdb) c
Continuing.

Breakpoint 2, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",
    query_string=0xb63fe255 "") at httpd.c:258
258	 if (pipe(cgi_output) < 0) {
(gdb) n
262	 if (pipe(cgi_input) < 0) {
(gdb) n
267	 if ( (pid = fork()) < 0 ) {
(gdb) l
262	 if (pipe(cgi_input) < 0) {
263	  cannot_execute(client);
264	  return;
265	 }
266
267	 if ( (pid = fork()) < 0 ) {
268	  cannot_execute(client);
269	  return;
270	 }
271	 if (pid == 0)  /* child: CGI script */
(gdb) l
272	 {
273	  char meth_env[255];
274	  char query_env[255];
275	  char length_env[255];
276
277	  dup2(cgi_output[1], 1);
278	  dup2(cgi_input[0], 0);
279	  close(cgi_output[0]);
280	  close(cgi_input[1]);
281	  sprintf(meth_env, "REQUEST_METHOD=%s", method);
(gdb) l
282	  putenv(meth_env);
283	  if (strcasecmp(method, "GET") == 0) { 【我的测试例子虽说是get请求,但不需要设置什么环境变量】
284	   sprintf(query_env, "QUERY_STRING=%s", query_string);
285	   putenv(query_env);
286	  }
287	  else {   /* POST */
288	   sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
289	   putenv(length_env);
290	  }
291	  execl(path, path, NULL); 【子进程执行test.sh】
(gdb) l
292	  exit(0);
293	 }
294
295	 else {    /* parent */
296	  close(cgi_output[1]);
297	  close(cgi_input[0]);
298	  if (strcasecmp(method, "POST") == 0)
299	   for (i = 0; i < content_length; i++) {
300	    recv(client, &c, 1, 0);
301	    write(cgi_input[1], &c, 1);
(gdb) b 298 【由于子进程执行test.sh,父进程发送响应给浏览器,所以先进入父进程,看发的是什么】
Breakpoint 3 at 0x80498ec: file httpd.c, line 298.
(gdb) c
Continuing.

Breakpoint 3, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",
    query_string=0xb63fe255 "") at httpd.c:298
298	  if (strcasecmp(method, "POST") == 0)
(gdb) n
304	  while (read(cgi_output[0], &c, 1) > 0)
(gdb) l
299	   for (i = 0; i < content_length; i++) { 【如果是POST,则还要继续从cgi_input中读取数据体,它被导入到标准输入,从而经由管道进入cgi_output[1]】
300	    recv(client, &c, 1, 0);
301	    write(cgi_input[1], &c, 1);
302	   }
303
304	  while (read(cgi_output[0], &c, 1) > 0)
305	   send(client, &c, 1, 0);
306
307	  close(cgi_output[0]);
308	  close(cgi_input[1]);
(gdb) s 【单步从cgi_output[0]中读】
305	   send(client, &c, 1, 0);
(gdb) p c
$1 = 10 '\n'
(gdb) s
305	   send(client, &c, 1, 0);
(gdb) p c 【以下部分刚好读到的是test脚本的"<html"】
$2 = 60 '<'
(gdb) s
305	   send(client, &c, 1, 0);
(gdb) p c
$3 = 104 'h'
(gdb) s
305	   send(client, &c, 1, 0);
(gdb) p c
$4 = 116 't'
(gdb) s
305	   send(client, &c, 1, 0);
(gdb) p c
$5 = 109 'm'
(gdb) s
305	   send(client, &c, 1, 0);
(gdb) p c
$6 = 108 'l'
(gdb) l
300	    recv(client, &c, 1, 0);
301	    write(cgi_input[1], &c, 1);
302	   }
303
304	  while (read(cgi_output[0], &c, 1) > 0)
305	   send(client, &c, 1, 0);
306
307	  close(cgi_output[0]);
308	  close(cgi_input[1]);
309	  waitpid(pid, &status, 0);
(gdb) b 307
Breakpoint 4 at 0x80499be: file httpd.c, line 307.
(gdb) c
Continuing.

Breakpoint 4, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",
    query_string=0xb63fe255 "") at httpd.c:307
307	  close(cgi_output[0]);
(gdb) n
308	  close(cgi_input[1]);
(gdb) n
309	  waitpid(pid, &status, 0);
(gdb) n
311	}
(gdb) p status
$7 = 0
(gdb) n
accept_request (client=4) at httpd.c:139
139	 close(client); <span style="background-color: rgb(255, 255, 255);">【直到这里,浏览器才显示了返回结果】</span>
(gdb) n
140	}
(gdb) 

结果显示:

当然我在这里只是演示了其中的一种情况,至于情况如get请求带?查询的,POST请求带数据体的,只有靠读者自己去尝试了,博主暂时抛砖引玉于此。

呃,感觉讲解至此结束了呢。貌似还有一点点细节博主还得继续研究下,总之通过这个例子确实对Linux编程了解了更多了,感谢开源,哈哈!

参考链接

1 http://blog.csdn.net/jcjc918/article/details/42129311

2 http://blog.sina.com.cn/s/blog_a5191b5c0102v9yr.html

3 CGI介绍:http://www.jdon.com/idea/cgi.htm

4 http://www.scholat.com/vpost.html?pid=7337

时间: 2024-11-05 11:52:26

tinyhttpd源码详解的相关文章

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

Java concurrent AQS 源码详解

一.引言 AQS(同步阻塞队列)是concurrent包下锁机制实现的基础,相信大家在读完本篇博客后会对AQS框架有一个较为清晰的认识 这篇博客主要针对AbstractQueuedSynchronizer的源码进行分析,大致分为三个部分: 静态内部类Node的解析 重要常量以及字段的解析 重要方法的源码详解. 所有的分析仅基于个人的理解,若有不正之处,请谅解和批评指正,不胜感激!!! 二.Node解析 AQS在内部维护了一个同步阻塞队列,下面简称sync queue,该队列的元素即静态内部类No

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Spring IOC源码详解之容器依赖注入

Spring IOC源码详解之容器依赖注入 上一篇博客中介绍了IOC容器的初始化,通过源码分析大致了解了IOC容器初始化的一些知识,先简单回顾下上篇的内容 载入bean定义文件的过程,这个过程是通过BeanDefinitionReader来完成的,其中通过 loadBeanDefinition()来对定义文件进行解析和根据Spring定义的bean规则进行处理 - 事实上和Spring定义的bean规则相关的处理是在BeanDefinitionParserDelegate中完成的,完成这个处理需

Spring IOC源码详解之容器初始化

Spring IOC源码详解之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比较典型的代码 ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDe

IntentService源码详解

IntentService可以做什么: 如果你有一个任务,分成n个子任务,需要它们按照顺序完成.如果需要放到一个服务中完成,那么IntentService就会使最好的选择. IntentService是什么: IntentService是一个Service(看起来像废话,但是我第一眼看到这个名字,首先注意的是Intent啊.),所以如果自定义一个IntentService的话,一定要在AndroidManifest.xml里面声明. 从上面的"可以做什么"我们大概可以猜测一下Inten

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

butterknife源码详解

butterknife源码详解 作为Android开发者,大家肯定都知道大名鼎鼎的butterknife.它大大的提高了开发效率,虽然在很早之前就开始使用它了,但是只知道是通过注解的方式实现的,却一直没有仔细的学习下大牛的代码.最近在学习运行时注解,决定今天来系统的分析下butterknife的实现原理. 如果你之前不了解Annotation,那强烈建议你先看注解使用. 废多看图: 从图中可以很直观的看出它的module结构,以及使用示例代码. 它的目录和我们在注解使用这篇文章中介绍的一样,大体

Android ArrayMap源码详解

尊重原创,转载请标明出处    http://blog.csdn.net/abcdef314159 分析源码之前先来介绍一下ArrayMap的存储结构,ArrayMap数据的存储不同于HashMap和SparseArray,在上一篇<Android SparseArray源码详解>中我们讲到SparseArray是以纯数组的形式存储的,一个数组存储的是key值一个数组存储的是value值,今天我们分析的ArrayMap和SparseArray有点类似,他也是以纯数组的形式存储,不过不同的是他的