在平时的收发数据总是会调用系统调用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;
}
}
}
因为服务器没有进行循环,所以发送一次服务器即断开
效果: