本节我们主要分析一下ngx_linux_sendfile_chain.c源文件。
1. 相关函数声明
当前我们并不支持NGX_THREADS
,系统支持NGX_HAVE_SENDFILE64
。
2. 函数ngx_linux_sendfile_chain()
1.1 sendfile()版本问题
一直到Linux 2.4.21版本为止,sendfile()函数都只支持32bit的offset,在此种情况下如果off_t为64bit时,包含<sys/sendfile.h>
头文件会导致编译失败。因此在没有sendfile64()函数的情况下,我们采用extern的方式声明sendfile()。
在ngx_auto_config.h头文件中我们有如下定义(在auto/os/conf脚本中进行对应的检测):
#ifndef NGX_HAVE_SENDFILE
#define NGX_HAVE_SENDFILE 1
#endif
#ifndef NGX_HAVE_SENDFILE64
#define NGX_HAVE_SENDFILE64 1
#endif
2.2 TCP_NODELAY/TCP_CORK
1) TCP_NODELAY
默认情况下,发送数据采用Nagle算法。这样虽然提高了网络吞吐量,但是实时性却降低了,对一些交互性很强的应用程序来说是不允许的,使用TCP_NODELAY选项可以禁止Nagle算法。
此时,应用程序向内核递交的每个数据包都会立即发送出去。需要注意的是,虽然禁止了Nagle算法,但网络的传输仍然受到TCP确认延迟机制的影响。
2) TCP_CORK
所谓的CORK就是塞子的意思,形象的理解就是用CORK将连接塞住,使数据先不发送出去,等到拔去塞子后再发送出去。设置该选项后,内核会尽量把小数据包拼成一个大的数据包(一个MTU)再发送出去,当然若一定时间后(一般为200ms,该值尚待确认),内核仍然没有组合成一个MTU时也必须发送现有的数据。
然而,TCP_CORK的实现可能并不像你想象的那么完美,CORK并不会将连接完全塞住。内核其实并不知道应用层到底什么时候会发送第二批数据用于和第一批数据拼接以达到MTU的大小,因此内核会给出一个时间限制,在该时间内没有拼接成一个大包(努力接近MTU)的话,内核就会无条件发送。也就是说若应用层程序发送小数据包的间隔不够短时,TCP_CORK就没有一点作用,反而会失去数据的实时性(每个小包数据都会延时一定时间再发送)。
这里的TCP_NOPUSH与TCP_CORK含义是相同的。TCP_CORK与TCP_NODELAY为互斥关系。
2.3 数据发送
ngx_linux_sendfile_chain()函数支持发送两种形式的数据:
内存发送和文件发送的区别(writev和sendfile):
1) 文件发送的效率相对内存发送效率要高很多,效率主要高在少了内核层到用户态的拷贝,用户态到内核态的拷贝。直接在磁盘将数据从网卡发送出去。
2) 通常的情况下,程序可能会在多个地方产生不同的buffer。writev是读取多个不连续的buffer然后集中写入。大并发服务器的时候这个效率还是很高的。writev和write函数的区别就在于多个非连续buffer的读取后写入,当负载大的时候就可以很好的提现出性能效果了。当然,如果数据足够小(小于1024)且只有唯一的一个buffer,我们直接用send/write就可以了。
3) 对于静态文件的传输,用sendfile可以减少系统调用。注意:文件发送的场景主要是对文件数据不进行改变,如果数据需要作改变还是得用内存发送。
下面我们仔细分析一下ngx_linux_sendfile_chain()函数:
3. 函数ngx_linux_sendfile()
此函数调用sendfile()发送文件中的数据。注意如下几个方面:
1) offset变量的定义
这里主要是由于sendfile()函数在32bit环境下被我们定义成了:
extern ssize_t sendfile(int s, int fd, int32_t *offset, size_t size);
2) 对中断的处理
若发送过程中被INT信号中断,返回继续处理。
3) 对sendfile()返回值0的处理
这里返回0表示文件产生了截断。
4. 多线程数据发送
这里两个函数都较为简单。ngx_linux_sendfile_thread
主要是为connection分配一个sendfile_task,然后调用thread_handler将该任务添加到队列中。
[参看]:
-
TCP_NODELAY与TCP_CORK
-
关于TCP_NODELAY和TCP_CORK选项
-
ngx_linux_sendfile_chain