readv()和write()sendfile()

readv函数将文件描述符读取到分散的内存存快中,分散读,writev把分散的数据一并写入文件描述符中,集中写。

ssize_t readv(int fd,const struct iovec* vector,int count);

ssize_t writev(int fd,const struct iovec* vector,int count);

fd参数是被操作的目标文件描述符。vector参数的类型是iovec结构数组,该结构体描述一块内存区。count参数是vector数组的长度,就是有所少块内存数据需要从fd中读出或者写到fd。readv和writev成功时返回读出/写入的fd的字节数,失败返回-1,并设置errno,简化版的recvmsg和sendmsg。

web服务器上的集中写:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#define buflen 1024

static const char* status_line[2] = {"200 OK","500 Internal server error"};

int main(int argc,char* argv[])
{
	const char* ip = argv[1];
	int port = atoi(argv[2]);
	const char* file_name = argv[3];
	
	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_port =htons(port) ;
	inet_pton(AF_INET,ip,&address.sin_addr);
	
	int sock = socket(AF_INET,SOCK_STREAM,0);
	int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
	ret = listen(sock,5);
	
	struct sockaddr_in client;
	socklen_t client_addrlength = sizeof(client);
	
	int connfd = accept(sock,(struct sockaddr* )&client,&client_addrlength);
	
	char header_buf[buflen];
	char * file_buf;
	struct stat file_stat;
	bool valid = true;
	int len  =0;
	
	if(stat(file_name,&file_stat)<0){valid = false;}
	else{
	if(S_ISDIR(file_stat.st_mode)){valid = false;}
	else if(file_stat.st_mode& S_IROTH){
	int fd = open(file_name,O_RDONLY);
	file_buf = new char[file_stat.st_size+1];
	memset(file_buf,'0',file_stat.st_size+1);
	if(read(fd,file_buf,file_stat.st_size+1)<0){valid = false;}
	}
	else{
	valid = false;
	}
	
	if(valid){
	ret = snprintf(header_buf,buflen-1,"%s %s 
","HTTP/1.1",status_line[1]);
	len += ret;
	ret = snprintf(header_buf+len,buflen-1-len,"Content-Length:%d
",file_stat.st_size);
	len+=ret;
	ret = snprintf(header_buf+len,buflen-1-len,"%s","
");
	struct iovec iv[2];
	iv[0].iov_base = header_buf;
	iv[1].iov_len = strlen(header_buf);
	iv[2].iov_base = file_buf;
	iv[3].iov_len = file_stat.st_size;
	ret = writev(connfd,iv,2);
	}
	else{
	ret = snprintf(header_buf,buflen-1,"%s %s 
","http/1.1",status_line[1]);
	len += ret ;
	ret = snprintf(header_buf+len, buflen-1-len,"%s","
");	
	send(connfd,header_buf,strlen(header_buf),0);	
	}
	close(connfd);
	delete[] file_buf;
}
	close(sock);
	return 0;

}

  sendfile在俩个文件描述符之间传递数据(在内核中操作),避免了内核缓冲区和用户缓冲区之间的拷贝,效率很高,零拷贝。

#include<sys/sendfile.h>

ssize_t sendfile(int out_fd,int in_fd,off_t offset,size_t count);

in_fd参数是待读出的文件描述符,out_fd是待写入的文件描述符。offset参数指定读入文件流的那个位置开始读,为空,默认起始位置开始读。count参数指定文件描述符之间的传输的字节数。sendfile成功时候,返回传输的字节数,失败返回-1并设置errno。另外in_fd必须指向真实的文件,而不能是socket或是管道。而out_fd必须是一个socket。由此可见sendfile是专门为网络传输文件而设计的。

例子:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/stat.h>


int main(int argc,char* argv[])
{
	const char* ip = argv[1];
	int port  = atoi(argv[2]);
	const char* file_name = argv[3];
	
	int filefd = open(file_name,O_RDONLY);
	struct stat stat_buf;
	fstat(filefd,&stat_buf);
	
	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET,ip,&address.sin_addr);
	
	int sock = socket(PF_INET,SOCK_STREAM,0);
	

	int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));

	ret = listen(sock,5);

	struct sockaddr_in client;
	socklen_t client_addrlength = sizeof(client);
	
	int connfd = accept(sock,(struct sockaddr*)&client,&client_addrlength);
	
	sendfile(connfd,filefd,NULL,stat_buf.st_size);
	
	close(sock);
	return 0;
}

6、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,unsigned int flags);

fd_in 是待输入数据的文件描述符。如果fd_in是一个管道文件描述符,那么off_in参数必须被奢姿为NULL。如果fd_in不似一个管道文件描述符(socket),那么off_in表示从输入数据流的何处开始读取数据。此时,若off_in被设置为null,则表示从输入数据流的当前偏移位置读入;若off_in不为null,则他将指出具体的偏移位置。fd_out/off_out参数的含义与fd_in/off_in相同,不过用于输出数据流。冷参数指定移动数据的长度;

  使用splice()时候,fd_in和fd_out必须至少有一个管道文件描述符。splice调用成功时,返回移动字节的数量。

案例:我们使用splice函数实现一个零拷贝的回射服务器,将客户端发送的数据原样返回给客户端。

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <iostream>
using namespace std;

int main(int argc,char* argv[]){
	
	const char* ip =argv[1];
	int port = atoi(argv[2]);
	
	struct sockaddr_in address;
	address.sin_family = AF_INET;
	address.sin_port = htons(port);
	inet_pton(AF_INET,ip,&address.sin_addr);
	
	int sock= socket(AF_INET,SOCK_STREAM,0);
	
	int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
	
	ret = listen(sock,5);
	
	struct sockaddr_in client ;
	socklen_t client_addrlength = sizeof(client);
	
	int conn = accept(sock,(struct sockaddr*)&client,&client_addrlength);
	
	int pipefd[2];
	ret = pipe(pipefd);

	ret = splice(pipefd[0],NULL,conn,NULL,32768,SPLICE_F_MORE | SPLICE_F_MOVE);
	close(conn);
	return 0;
}

7、tee()函数。

tee函数用于在俩个管道文件描述符之间复制数据,也是零拷贝操作。不消耗数据,因此源文件描述符上的数据任然可以用于后续的读操作。tee函数的原型如下;

#include<fcntl.h>

ssize_t tee(int fd_in , int fd_out , size_t len , unsigned int flags);

该函数的参数的含义与splice相同(但fd_in , fd_out 都必须是管道文件描述符)。tee函数成功是返回再俩个文件描述符之间复制的数据流量(字节数)。返回0表示没有复制任何数据。tee失败返回-1,并设施errno。

8、fcntl()函数。

ioctl)比fcntl()更能够执行更多的控制。

#include<fcntl.h>

int fcntl(int fd, int cmd,...);

fd参数是被操作的文件描述符,cmd执行各种类型的操作。根据操作类型的不同,该函数可能还需要第三个可选参数arg。

另外,SIGIO和SIGURG这俩个信号与其他的信号不同,他们必须与某个文件描述符相关联方可使用;

原文地址:https://www.cnblogs.com/yjds/p/8994048.html