参考
http://calmness.iteye.com/blog/378463
在传统的文件传输里面(read/write方式),在实现上其实是比较复杂的,需要经过多次上下文的切换,我们看一下如下两行代码:
Java代码
read(file, tmp_buf, len);
write(socket, tmp_buf, len);
以上两行代码是传统的read/write方式进行文件到socket的传输。
当需要对一个文件进行传输的时候,其具体流程细节如下:
1、调用read函数,文件数据被copy到内核缓冲区
2、read函数返回,文件数据从内核缓冲区copy到用户缓冲区
3、write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。
4、数据从socket缓冲区copy到相关协议引擎。
以上细节是传统read/write方式进行网络文件传输的方式,我们可以看到,在这个过程当中,文件数据实际上是经过了四次copy操作:
硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎
而sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。Sendfile系统调用是在2.1版本内核时引进的:
Java代码
sendfile(socket, file, len);
运行流程如下:
1、sendfile系统调用,文件数据被copy至内核缓冲区
2、再从内核缓冲区copy至内核中socket相关的缓冲区
3、最后再socket相关的缓冲区copy到协议引擎
相较传统read/write方式,2.1版本内核引进的sendfile已经减少了内核缓冲区到user缓冲区,再由user缓冲区到socket相关缓冲区的文件copy,而在内核版本2.4之后,文件描述符结果被改变,sendfile实现了更简单的方式,系统调用方式仍然一样,细节与2.1版本的不同之处在于,当文件数据被复制到内核缓冲区时,不再将所有数据copy到socket相关的缓冲区,而是仅仅将记录数据位置和长度相关的数据保存到socket相关的缓存,而实际数据将由DMA模块直接发送到协议引擎,再次减少了一次copy操作。
★★★★例子代码
[root@VM_253_237_tlinux sendfile]# cat sendclient.c
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
int main (int argc, char *argv[])
{
int a=clock();
printf("this is used %d\n",a);
struct sockaddr_un address;
int sockfd;
int len, result;
int i, bytes;
struct stat buf;
off_t off;
ssize_t res, total = 0;
int wfd;
char rwbuf[4096];
wfd = open ("src_sendfile_save", O_WRONLY);
if (wfd < 0) {
perror ("open");
exit (EXIT_FAILURE);
}
/*..socket,AF_UNIX....,SOCK_STREAM....*/
if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
perror ("socket");
exit (EXIT_FAILURE);
}
address.sun_family = AF_UNIX;
strcpy (address.sun_path, "server_socket");
len = sizeof (address);
/*..........*/
result = connect (sockfd, (struct sockaddr *)&address, len);
if (result == -1) {
printf ("ensure the server is up\n");
perror ("connect");
exit (EXIT_FAILURE);
}
while ((res = read (sockfd, rwbuf, 4096)) > 0) {
total += res;
write (wfd, rwbuf, 4096);
}
printf ("total %u bytes received from server snedfile\n", total);
close (sockfd);
close (wfd);
int b=clock();
printf("this is used %d\n",b);
int c=b-a;
printf("this is used %d\n",c);
return (0);
}
[root@VM_253_237_tlinux sendfile]#
[root@VM_253_237_tlinux sendfile]# cat sendserver.c
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/un.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main (int argc, char **argv)
{
struct sockaddr_un sin1;
int server_sockfd, client_sockfd;
int server_len, client_len;
ssize_t bytes, res=0;
ssize_t rtotal = 0;
FILE *stream;
int in_fd;
struct stat buf;
off_t off = 0;
unlink ("server_socket");
unlink ("src_sendfile_save");
stream = fopen ("src_sendfile_save", "w");
if (!stream) {
perror ("fopen");
exit (EXIT_FAILURE);
}
fclose (stream);
if ((in_fd = open ("src", O_RDONLY)) < 0) {
printf ("Can't open 'src' file");
exit (EXIT_FAILURE);
}
if (fstat (in_fd, &buf) == -1) {
printf ("Can't stat 'src' file\n");
exit (EXIT_FAILURE);
}
printf ("Get file size are %u bytes\n", buf.st_size);
server_sockfd = socket (AF_UNIX, SOCK_STREAM, 0);
if (server_sockfd < 0) {
perror ("socket");
exit (EXIT_FAILURE);
}
sin1.sun_family = AF_UNIX;
strcpy (sin1.sun_path, "server_socket");
server_len = sizeof (sin1);
if (bind (server_sockfd, (struct sockaddr *)&sin1, server_len) < 0) {
perror ("bind");
exit (EXIT_FAILURE);
}
if (listen (server_sockfd, 5) < 0) {
perror ("listen");
exit (EXIT_FAILURE);
}
printf ("The server is waiting for client connect...\n");
client_sockfd = accept (server_sockfd, (struct sockaddr *)&sin1, (socklen_t *)&client_len);
if (client_sockfd == -1 ) {
perror ("accept");
exit (EXIT_FAILURE);
}
while (off < buf.st_size) {
if ((res = sendfile (client_sockfd, in_fd, &off, buf.st_size)) < 0 ) {
printf ("sendfile failed\n");
exit (EXIT_FAILURE);
} else {
rtotal += res;
}
}
printf ("server sendfile total %u bytes\n", rtotal);
close (client_sockfd);
unlink ("server_socket");
return (0);
}
[root@VM_253_237_tlinux sendfile]#