使用splice函式實現0拷貝的回顯伺服器

Achi_Lyr發表於2020-11-27

在平時的收發資料總是會呼叫系統呼叫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;
   }

     }

}

因為伺服器沒有進行迴圈,所以傳送一次伺服器即斷開

效果:
在這裡插入圖片描述

相關文章