ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
linux下支持sendfile,这样发送文件,可以直接通过内核调用,减少了到应用程序读写两道操作程序,并降低了内存的使用。看man文档,sendfile在2.6.9以后,infd只能是普通文件,outfd必须是socket。
经过试验,在linux9,as4,as5下nfs挂载的文件,都可以使用sendfile发送。
sendfile,对阻塞的sockfd发送,也会阻塞程序。
当设置outfd为非阻塞模式的时候,测试sendfile 只发送了106496字节(可以more /proc/sys/net/core/wmem_default ),而不能完成整个文件的发送。
因此,在epoll模式边缘触发非阻塞模式下,必须监听outfd可写,记录sendfile当前发送的字节,下一次调用时,从上次发送完成的地址开始。
offset为空,则sendfile将根据所读取的字节数来自动设定infd的文件偏移,下一次从偏移读取。如果设置有offset,则不会改变文件偏移指针。
使用epoll测试的时候,设置边缘触发,每次epoll_wait返回,sendfile只发送小量字节,在没有达到EAGAIN返回前(写阻塞前),继续epoll_wait,发现可以获得下一次的epoll写事件。
但是反过来,如果监听的是读取事件,只读取小量字节,剩余少量字节没有完全读取的话,epoll将不会再次触发读事件。因此对待读操作,一般epoll_wait之后,都要while(没有EAGIN或中断){读},只要是EAGAIN或中断,就接着读!
sendfile,在对方关闭socket之后,如果还处在发送的过程中,则产生SIGPIPE信号。和write一样。
最新测试发现:
针对tcp socket,获取epoll的write通知后,如果不写到EAGAIN,epoll_wait将不会再获得可写事件触发!
因此sendfile必须不断写,直到EAGAIN为止!
测试代码如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/epoll.h>
int main(int argc,char*argv[])
{
if (argc!=3)
{
perror("Usage:./a.out pathname unixsckname\n");
return -1;
}
pid_t pid;
pid=fork();
if (pid>0){//parent process
int outfd,infd;
infd=open(argv[1],O_RDONLY);
if (infd ==-1){
printf("open %s fail\n",argv[1]);
return -1;
}
sleep(1);
outfd=socket(AF_UNIX,SOCK_STREAM,0);
int on=1;
if (setsockopt(outfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0){
perror("set sockopt outfd fail");
exit(EXIT_FAILURE);
}
struct sockaddr_un sckaddr;
bzero(&sckaddr,sizeof(struct sockaddr));
sckaddr.sun_family=AF_UNIX;
strncpy(sckaddr.sun_path,argv[2],sizeof(sckaddr.sun_path)-1);
if(connect(outfd,(struct sockaddr *)&sckaddr,sizeof(struct sockaddr_un))==-1){
printf("connect socket failer,%s\n",strerror(errno));
return -1;
}
if (outfd ==-1){
printf("open %s fail\n",argv[1]);
return -1;
}
struct stat stabuf;
if( fstat(infd,&stabuf)!=0 ){
perror("fstat failure");
exit(-1);
}
printf("file size is%d\n",stabuf.st_size);
ssize_t ssize;
fcntl(outfd, F_SETFL, fcntl(outfd, F_GETFL, 0)|O_NONBLOCK);
//fcntl(infd, F_SETFL, fcntl(infd, F_GETFL, 0)|O_NONBLOCK);
struct epoll_event ev;
struct epoll_event events[1];
int epfd;
epfd=epoll_create(10);
ev.events = EPOLLOUT | EPOLLET;
ev.data.fd=outfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,outfd,&ev);
int totalsend=0;
while(1){
int nfds;
nfds=epoll_wait(epfd,events,10,-1);
if (nfds==-1){
perror("epoll_wait");
fprintf(stderr,"epoll_wait\n");
break;
}
fprintf(stderr,"epoll_wait ok\n");
if (events[0].data.fd == outfd){
//ssize=sendfile(outfd,infd,0,stabuf.st_size);
ssize=sendfile(outfd,infd,0,1024);
fprintf(stderr,"send file %d bytes\n",ssize);
if (ssize<0){
printf("sendfile error is %s",strerror(errno));
close(infd);
close(outfd);
break;
}else if(errno==EAGAIN){
fprintf(stderr,"egain write %d bytes\n",ssize);
//break;
continue;
}else{
fprintf(stderr,"error is %s\n",strerror(errno));
totalsend+=ssize;
}
}
}
/*
ssize=sendfile(outfd,infd,0,stabuf.st_size);
if (ssize<0){
printf("sendfile error is %s",strerror(errno));
close(infd);
close(outfd);
}
*/
close(outfd);
close(infd);
printf("file size is%d,sended %d\n",stabuf.st_size,ssize);
}else{
int sockfd;
sockfd=socket(PF_UNIX,SOCK_STREAM,0);
if (sockfd<0){
perror("create socket fail\n");
return;
}
int on=1;
if (setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0){
perror("set sockopt fail");
exit(EXIT_FAILURE);
}
struct sockaddr_un sckaddr;
memset(&sckaddr,0,sizeof(struct sockaddr_un));
sckaddr.sun_family=AF_UNIX;
strncpy(sckaddr.sun_path,argv[2],sizeof(sckaddr.sun_path)-1);
if (bind(sockfd,(struct sockaddr *)&sckaddr,sizeof(struct sockaddr_un))==-1){
printf("bind failure %s\n",strerror(errno));
exit(EXIT_FAILURE);
}
listen(sockfd,5);
int fdacc;
char buf[102400];
int totalread=0;
while (1){
if ((fdacc=accept(sockfd,NULL,NULL))>0){
sleep(3);
int nread=0;
nread=read(fdacc,buf,sizeof(buf));
printf("read %d\n",nread);
while( nread!=0) {
if (nread>0){
totalread += nread;
printf("nread is %d,totalread is %d\n",nread,totalread);
}
nread=read(fdacc,buf,sizeof(buf));
printf("read %d\n",nread);
}
sleep(1);
printf("read total %d bytes\n",nread);
close(fdacc);
close(sockfd);
return;
}else{
printf("accept error %s\n",strerror(errno));
return;
}
}
}
}