sendfile 和epool


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;
            }
        }
    }
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 很高兴为您提供一个epoll聊天室代码,如下所示:#include <sys/epoll.h> #include <fcntl.h>int main(int argc, char **argv) { int epfd, nfds; struct epoll_event ev, events[MAX_CLIENTS]; char buf[MAX_MSG_SIZE]; int i, connfd, n; // Create epoll file descriptor epfd = epoll_create(MAX_CLIENTS); if (epfd == -1) { perror("epoll_create failed"); exit(EXIT_FAILURE); } // Add the listening socket to the epoll set ev.events = EPOLLIN; ev.data.fd = sockfd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) == -1) { perror("epoll_ctl: listen_sock"); exit(EXIT_FAILURE); } // Loop forever, waiting for events for (;;) { nfds = epoll_wait(epfd, events, MAX_CLIENTS, -1); if (nfds == -1) { perror("epoll_wait"); exit(EXIT_FAILURE); } for (i = 0; i < nfds; i++) { // Handle new connections if (events[i].data.fd == sockfd) { connfd = accept(sockfd, (struct sockaddr *) &cliaddr, &clilen); if (connfd == -1) { perror("accept"); exit(EXIT_FAILURE); } setnonblocking(connfd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; if (epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev) == -1) { perror("epoll_ctl: connfd"); exit(EXIT_FAILURE); } } else { // Handle data from a client do { n = read(events[i].data.fd, buf, MAX_MSG_SIZE); if (n > 0) { // Send the message to all clients for (j = 0; j < MAX_CLIENTS; j++) { if (clientfd[j] != 0) { write(clientfd[j], buf, n); } } } } while (n > 0); if (n == 0) { // Connection closed by client close(events[i].data.fd); } else { perror("read"); exit(EXIT_FAILURE); } } } } } ### 回答2: 下面是一个使用epoll实现的简单聊天室代码: ```python import socket import select # 创建一个TCP套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定地址和端口 server_address = ('', 8888) server_socket.bind(server_address) # 开始监听 server_socket.listen(10) # 创建一个epoll对象 epoll = select.epoll() # 注册服务器套接字到epoll对象上,并监听读事件 epoll.register(server_socket.fileno(), select.EPOLLIN) # 保存客户端套接字与地址的映射关系 client_sockets = {} client_addresses = {} while True: events = epoll.poll(1) for fileno, event in events: # 如果是服务器套接字上的事件,表示有新的连接请求 if fileno == server_socket.fileno(): client_socket, client_address = server_socket.accept() print('New client connected:', client_address) # 在epoll对象上注册客户端套接字,并监听读事件 epoll.register(client_socket.fileno(), select.EPOLLIN) # 保存客户端套接字和地址 client_sockets[client_socket.fileno()] = client_socket client_addresses[client_socket.fileno()] = client_address # 如果是客户端套接字上的事件,表示有数据到达 elif event & select.EPOLLIN: receiving_socket = client_sockets[fileno] data = receiving_socket.recv(1024) if data: print('Received data:', data.decode('utf-8')) for client_fileno, client_socket in client_sockets.items(): if client_fileno != fileno: client_socket.sendall(data) else: # 如果没有数据到达,表示客户端已断开连接 epoll.unregister(fileno) receiving_socket.close() del client_sockets[fileno] del client_addresses[fileno] # 关闭服务器套接字和epoll对象 server_socket.close() epoll.close() ``` 这段代码实现了一个简单的聊天室服务器,使用了epoll来实现并发处理客户端连接和数据传输。当有新的连接请求时,会将客户端套接字注册到epoll对象上,并监听读事件。当客户端套接字上有数据到达时,会将该数据发送给其他所有客户端。同时也会处理客户端断开连接的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值