nginx中upstream的设计和实现(四)

这此主要是分析发送数据到客户端的部分以及buffering状态下,nginx接收upstream数据的部分,这也是upstream的最复杂的部分,这里我还是忽略了cache部分,以后我会专门写blog来分析nginx的cache部分。

这部分的函数入口是ngx_http_upstream_send_response,这里有一个很重要的标记,那就是u->buffering,这个标记的含义就是nginx是否会尽可能多的读取upstream的数据。如果关闭,则就是一个同步的发送,也就是接收多少,发送给客户端多少。默认这个是打开的。也就是nginx会buf住upstream发送的数据。

不管buffering是否打开,后端发送的头都不会被buffer,首先会发送header,然后才是body的发送,而body的发送就需要区分buffering选项了。如下图所示:

upstream_ac

阅读全文

c reference manual读书笔记(三)

这次主要是针对c语言中的声明(declaration)定义.

1 在c语言中声明的作用域(scope)可以分为4种,分别是 block/local scope, prototype scope, function scope以及file scope.其中file scope是最顶层的域. 如果一个声明不是external的,则默认这个声明就是被限制在它所出现的文件中.

2 一个标示符在它被完全声明之前是不能使用的。更精确的说就是从我们定义一个声明到这个声明的结束之间不能出现这个标示符.可是下面的例子是完全正确的,这是因为使用intsize(sizeof)之前,它已经被声明了:

1
2
  
static int intsize = sizeof(intsize)

而下面的例子则会编译出错,因为Test在使用之前还没有被声明.

1
2
  
typedef struct {Test *s} Test;

3 c语言中也支持forward reference,不过只允许下面三种类型,分别是 在goto语句中的label,label都是在goto语句之后声明的;还有就是允许在struct,union,array类型的完全声明之前使用当前的声明,比如下面的例子

1
2
  
typedef struct s {struct s *n} T;

最后一种例外是函数的声明,这个原因是在c99之前,c语言允许隐式(implicitly)的声明,也就是如果一个函数在调用之前没有被声明,则在函数被调用的地方会隐式的声明这个函数(后面会详细介绍这个)。不过c99是不允许隐式的声明的.

阅读全文

c reference manual读书笔记(二)

这次主要是预处理器和宏处理的部分。

1 预处理器主要是处理源代码中以#开头的行,预处理器执行完毕后的代码必定是一个合法的c程序.

2 预处理器的命令是完全不依赖于c语言的语法的.

3 预处理器不会parse源代码,预处理的词法处理和编译器的是不同的,预处理器能够理解合法的c标记,可是它也会忽略在c编译器中认为是不合法标记。比如下面的代码,对于预处理器来说,没有任何问题的.

阅读全文

c reference manual读书笔记(一)

我读的是第五版的影印版,简单的做了一些笔记.下面是第二章的笔记.

1 c 源文件的字符集(character set)包含在ISO/IEC 10646的Latin block中. 包括5种类型,分别是 52个lating大小写字母,10个数字(0-9),空格,horizontal tab(ht) vertical tab(VT) form feed(FF) 以及29个graphic 字符(这个可以去看c99的手册).而不在集合内的字符可能会出现在注释/字符常量/字符串常量/文件名中.在c中还有执行字符集(execution character)的概念,一般来说编译和运行都在相同的电脑,则source字符集和执行字符集都是相同的.

2 在c的源程序中,blank, end-of-line, vertical tab, form feed, horizontal-tab都会被认为是空格(whitespace characters).

3 c89中要求逻辑行的最大长度是509个字符,而c99是4095个字符.

4 c语言中可以使用 9个trigraphs(比如??/表示),而trigraphs的翻译会在词法分析之前(gcc默认编译没有打开trigraph,必须添加-trigraphs命令,或者使用-std命令指定c标准).

5 多字节字符(multibytes characters),主要针对非英语的环境.编码实现分为state-independent和state-depend,顾名思义,一个是编码依赖于前一个多字节字符,一个是不依赖于前一个多字节字符.

阅读全文

rfc 2988(Computing TCP’s Retransmission Timer)简介

rfc 2988是描述tcp如何计算定时器的一个rfc,是2000年发布的,而最近2988 已经被更新:

http://tools.ietf.org/html/draft-paxson-tcpm-rfc2988bis-02

并且google的jerry chu(2988bis的作者之一)最近在内核提交一个patch,主要是用来修改3次握手时的初始化rto的值(以及rfc 2988bis的一些改变),当前的内核默认是3,而patch修改这个值为1,主要的修改依据是rfc2988.

阅读全文

nginx fastcgi模块的一个bug

上周服务器更新到nginx的0.8.X之后,nginx出现了core dump的情况,而在0.7.X并不会出现,通过察看core dump文件以及nginx 0.8.x和0.7.x的比较,发现core dump是nginx 0.8.40引入下面这个feature才导致的:

*) Feature: a “fastcgi_param” directive with value starting with

“HTTP_” overrides a client request header line.

在nginx 0.8.40之后,如果你的fastcgi_param定义的变量以HTTP_开头,则传递给后端的头会忽略request header中的这个头,比如定义了一个 fastcgi_param $HTTP_HOST test, 那么传递给后端时,host这个头的值就是test.

这里的逻辑是这样子的,当nginx创建一个fastcgi request的时候,会先计算所需要的长度,首先是计算header的长度,在计算之前会先分配一个ignored数组(用来保存将要被忽略的头),它的大小是配置文件中fastcgi_param定义的以HTTP_开头的变量的个数. 然后遍历所有的request header,如果发现header的名字和fastcgi_param中定义的变量的(HTTP_开头)名字相同(使用hash),则将这个header指针放到ignored数组中,最后在拷贝request header的时候直接在这个数组里面查找,如果有则跳过,否则拷贝头以及它的值。

看起来没什么问题,可是这里忽略了request header有可能会有重复的这个情况,此时ignored数组可能就会越界,从而导致core dump.

阅读全文

TFO(tcp fast open)简介

这个是google的几个人提交的一个rfc,是对tcp的一个增强,简而言之就是在3次握手的时候也用来交换数据。这个东西google内部已经在使用了,不过内核的相关patch还没有开源出来,chrome也支持这个了(client的内核必须支持). 要注意,TFO默认是关闭的,因为它有一些特定的适用场景,下面我会介绍到。

阅读全文

nginx中upstream的设计和实现(三)

这次主要来分析当upstream发送过来数据之后,nginx是如何来处理。不过这里我忽略了cache部分,以后我会专门来分析nginx的cache部分。

在前面blog我们能得知upstream端的读回调函数是ngx_http_upstream_process_header,因此这次我们就从ngx_http_upstream_process_header的分析开始。

下面是ngx_http_upstream_process_header执行的流程图.

upstream_process_header

阅读全文

linux kernel中epoll的设计和实现

这里就不贴源码了,源码分析的话,网上一大堆,我这里只是简要的描述下epoll的实现和一些关键的代码片段。

相关的文件在 fs/eventpoll.c中,我看的是2.6.38的内核代码.

1 epoll在创建的时候会调用anon_inode_getfd新建一个file instance,也就是epoll可以看成一个文件。因此我们可以看到epoll_create会返回一个fd.

1
2
3
4
	  
error = anon_inode_getfd("[eventpoll]", &eventpoll_fops, ep,

O_RDWR | (flags & O_CLOEXEC));

2 epoll所管理的所有的句柄都是放在一个大的结构eventpoll(红黑树)中,而这个结构是保存在file 的private_data域中的(因为epoll本身就是一个文件).这样每次通过epoll fd就可以直接得到eventpoll.

1
2
3
4
5
6
7
8
9
10
	  
file = fget(epfd);

/\* Get the "struct file \*" for the target file */

tfile = fget(fd);

……………………………..

ep = file->private_data;

3 每一个加入到epoll监听的句柄(也就是红黑树的一个节点)都是一个epitem.它包含了一个 struct eventpoll *ep,也就是它所属于的eventpoll(epoll实例).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	  
if (!(epi = kmem_cache_alloc(epi_cache, GFP_KERNEL)))

return -ENOMEM;

/\* Item initialization follow here … \*/

INIT_LIST_HEAD(&epi->rdllink);

INIT_LIST_HEAD(&epi->fllink);

INIT_LIST_HEAD(&epi->pwqlist);

epi->ep = ep;

ep_set_ffd(&epi->ffd, tfile, fd);

epi->event = *event;

epi->nwait = 0;

epi->next = EP_UNACTIVE_PTR;

阅读全文

nginx中upstream的设计和实现(二)

这次主要来看upstream的几个相关的hook函数。

首先要知道,对于upstream,同时有两个连接,一个时client和nginx,一个是nginx和upstream,这个时候就会有两个回调,然后上篇blog中,我们能看到在upstream中,会改变read_event_handler和write_event_handler,不过这里有三个条件,分别是

1 没有使用cache,

2 不忽略client的提前终止

3 不是post_action

1
2
3
4
5
6
7
8
9
10
11
12
  
//条件赋值

if (!u->store && !r->post_action && !u->conf->ignore_client_abort) {

//然后给读写handler赋值

r->read_event_handler = ngx_http_upstream_rd_check_broken_connection;

r->write_event_handler = ngx_http_upstream_wr_check_broken_connection;

}

阅读全文