使用splice函数实现0拷贝的回显服务器

在平时的收发数据总是会调用系统调用recv和send等函数,这些函数都会进入内核进行数据的拷贝,所以说还是比较耗费资源的,所以就有了实现0拷贝的函数,这里就使用其中的splice函数实现一个回显服务器,用于将客户端发送的消息原样发送给客户端,并且不使用recv、send函数

首先介绍一下splice:
splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作,定义如下:

#include <fcntl.h>
ssize_t splice(int fd_in, loff_t * off_in,int fd_out, loff_t off_out,size_t len,unsingned int flags);

参数介绍
fd_in 参数是待输入数据的文件描述符,如果fd_in是一个管道文件描述符,那么off_in参数必须设置为NULL;若不是,比如是socket,那么off_in表示聪从输入数据的何处开始读取数据,若设置为null则表示从当前偏移位置开始读入;

fd_out和fd_in 的参数含义相同,只不过是用于输出

len指定移动数据的长度

flags则控制数据如何移动

flags的值可以是以下的值:

SPLICE_F_MOVE: 如果合适的话,按整页内存移动数据
SPLICE_F_NONBLOCK: 非阻塞的splice操作,但是实际效果还是会受到文件描述符本身的阻塞状态限制
SPLICE_F_MORE:  给内核的一个提示:后续的splice操作将读取更多的数据
SPLICE_F_GIFT:  对splice没有效果

注意:使用splice时,fd_in和fd_out必须至少有一个是管道文件描述符。

**返回值:**成功返回移动的字节数;返回0表示没有数据需要移动;失败返回-1并设置errno

errno的常见以及含义:

EBADF 或 EINVAL :参数所指文件描述符有错
ENOMEM:内存不够
ESPIPE:参数fd_in是管道文件描述符,但off_in不是NULL

下面用splice实现一个回显服务器:

服务器端代码:

#define _GNU_SOURCE  1
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <poll.h>
#include <fcntl.h>
#include <libgen.h>  //basename
#include <errno.h>

int main(int argc,char ** argv){
    if(argc <= 2){
        printf("usage: %s ip_address port_number\n",basename(argv[0]));
        return 1;
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);
    int ret = 0;
    struct sockaddr_in  serv_addr;
    bzero(&serv_addr,sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    inet_pton(AF_INET,ip,&serv_addr.sin_addr);
    serv_addr.sin_port = htons(port);

    int listenFd = socket(AF_INET,SOCK_STREAM,0);
    assert(listenFd >= 0);
    ret = bind(listenFd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    assert(ret != -1);
    ret = listen(listenFd,5);
    assert(ret != -1);

    struct sockaddr_in client_addr;
    socklen_t clit_len = sizeof(client_addr);

    int connfd = accept(listenFd,(struct sockaddr *)&client_addr,&clit_len);
    if(connfd < 0){
        printf("errno is %d\n",errno);
    }else
    {
        int pipefd[2];
        assert(ret != -1);
        ret = pipe(pipefd);
        ret = splice(connfd,NULL,pipefd[1],NULL,32678,SPLICE_F_MORE|SPLICE_F_MOVE);
        ret = splice(pipefd[0],NULL,connfd,NULL,32768,SPLICE_F_MORE|SPLICE_F_MOVE);
        assert(ret != -1);

        close(connfd);
    }
    close(listenFd);
    return 0;
    

}

客户端:

#include <unistd.h>
#include <sys/socket.h>
#include <netdb.h>
#include <iostream>
#include <string>
#include <cassert>
#include <sys/types.h>
#include <netinet/in.h>  //htons
#include <string.h> // memset
#include <arpa/inet.h> // inet_addr
using namespace std;

int main(){
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
 //   assert(sockfd == 0);

    struct sockaddr_in servAddr;
    memset(&servAddr,0,sizeof(servAddr));

    servAddr.sin_family = AF_INET;
    servAddr.sin_port = htons(6666);
    servAddr.sin_addr.s_addr = inet_addr("192.168.10.128");

    int ret = connect(sockfd,(struct sockaddr*)&servAddr,sizeof(servAddr));
    if(ret == -1){
       cout<<"connect eerror"<<endl;
       cout<<strerror(errno)<<endl;
    }
   // assert(ret == 0);

    while(1){
    char sendbuffer[1024];
    memset(sendbuffer,0,sizeof(sendbuffer));
   // int ret = recv(sockfd,buffer,sizeof(buffer),0);
   //
   cout<<"Please Input:"<<endl;
   cin>>sendbuffer;
   send(sockfd,sendbuffer,sizeof(sendbuffer),0);
   char recvbuf[1024];
   int ret = recv(sockfd,recvbuf,sizeof(recvbuf),0);
   if(ret){
	   cout<<"recv serv:"<<recvbuf<<endl;
   }else if(ret == 0){
	   cout<<"link failure!"<<endl;  //duankai
	   close(sockfd);
	   break;
   }

     }

}

因为服务器没有进行循环,所以发送一次服务器即断开

效果:
在这里插入图片描述

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页