Linux有很多高级I/O函数,不像基本的IO函数那样常用,但是在特定的场合下使用高级IO函数可以是程序来的更简洁,效率也更高。
#include
int pipe(int fd[2]);
返回:成功返回0,并将一对打开的文件描述符值填入其参数指向的数组,出错-1
通过pipe函数创建的两个文件描述符fd[0]和fd[1]构成了管道的两端,往fd[1]写入的数据通过fd[0]读出。并且,只能通过fd[0]读出数据,通过fd[1]写入数据,不能反过来使用。如果要使用双向的数据传输,需要创建两个管道。默认情况下,这一对文件描述符是阻塞的。此时我们用read系统调用来读取一个空的管道,则read会被阻塞,直到管道中有数据可读;如果我们用write系统调用来往一个满的管道中写数据,则write会被阻塞,直到管道有足够多的空间可写。如果程序将fd[0]和fd[1]设置为非阻塞的,则read和write会有不同的行为。
#include
int dup(int filedes);
int dup2(int filedes, int filedes2);
返回:成功返回新的描述符,出错返回-1
由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值,用dup2则可以用filedes2参数指定新描述的数值。如果filedes2已经打开,则先将其关闭,若filedes等于filedes2,则dup2返回filedes2,而不关闭它。通过dup和dup2创建的文件描述符并不继承原文件描述符的属性。
有时把标准输入从定向到一个文件,或者把一个标准输出从定向到一个网络连接(比如CGI编程的基本原理就是这样的)/**
* CGI基本工作原理示例,用到了dup函数,这只是服务器端的代码
*/
#include
#include
#include
#include
#include
#include
#include
#include
#define SA struct sockaddr
#define SA_IN struct sockaddr_in
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { fprintf(stderr, msg); exit(-1); } while(0)
int main(int argc, char *argv[])
{
if(argc != 3)
err_exit("usage: ./a.out ip port\n");
int port = atoi(argv[2]);
int listenfd, connfd;
SA_IN servaddr;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
err_sys("inet_pton error");
if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)
err_sys("bind error");
if(listen(listenfd, 5) < 0)
err_sys("listen error");
SA_IN cliaddr;
int clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
err_sys("accept error");
else
{
close(STDOUT_FILENO); //关闭标准输出
dup(connfd); //dup返回的新文件描述符一定是当前可用文件描述符中最小值,所以为1(标准输出),此时标准输出就映射到了connfd上
printf("hello!");
close(connfd);
}
close(listenfd);
return 0;
}
上面的程序只是服务器端的代码,客户端的代码可以参考:http://blog.csdn.net/u012796139/article/details/44984879
#include
ssize_t readv(int sockfd, const struct iovec *iov, int iovcnt);
ssize_t writev(int sockfd, const strut iovec *iov, int iovcnt);
返回:成功为读入或写出的字节数,出错-1
这两个函数类似于read和write,不过readv和writev允许单个系统调用读入或写出到一个或多个缓冲区,非别称为分散读和集中写,因为来自读操作的输入数据被分散到多个应用缓冲区中,而来自多个应用的缓冲区的输入数据则被集中提供单个写操作。
这两个函数第二个参数指针都指向iovec结构数组的一个指针,iovec在头文件
struct iovec
{
void *iov_base;
size_t iov_len;
}
web服务器上的集中写,用到了writev函数,这里只写了http的应答,省略了http请求的接受和解析。直接把目标文件发送给客户端,可以直接在浏览器中输入IP地址和端口后就可以看到目标文件了,比如在浏览器中输入:192.168.1.5:60000,192.168.1.5为运行该程序的主机IP地址,60000为相应的端口号。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SA struct sockaddr
#define SA_IN struct sockaddr_in
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { printf(msg); exit(-1); } while(0)
#define err_info(msg) \
do { printf(msg); } while(0)
#define BUFFSIZE 1024
/* 定义的两这种http状态信息 */
const char *status_line[2] = {"200 OK", "500 Internal Server Error"};
int main(int argc, char *argv[])
{
if(argc != 4)
err_exit("usgae: ./a.out ip port filename\n");
int port = atoi(argv[2]);
char *filename = argv[3]; //目标文件为第4个参数
SA_IN servaddr;
int listenfd, connfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
err_sys("inet_pton error");
if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)
err_sys("bind error");
if(listen(listenfd, 5) < 0)
err_sys("listen error");
SA_IN cliaddr;
socklen_t clilen = sizeof(cliaddr);
while(1) //这里是死循环避免在浏览器中输入相应的IP和端口号后无法看到目标文件内容
{
if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
err_sys("accept error");
/* 用于保存http应答的状态行 头部字段和一个空行的缓冲区 */
char head_buf[BUFFSIZE];
char *file_buf; //用于存放目标文件的缓冲区
struct stat file_stat; //获取目标属性
int len = 0; //缓冲区head_buf当前占用字节数
int vaild = 1; //记录文件是否有效
if(stat(filename, &file_stat) < 0)
{
err_info("stat error");
vaild = 0;
}
else
{
if(S_ISDIR(file_stat.st_mode))
{
err_info("the file is a director");
vaild = 0;
}
else if(file_stat.st_mode & S_IROTH)
{
file_buf = new char[file_stat.st_size + 1];
memset(file_buf, 0, BUFFSIZE);
int fd;
if((fd = open(filename, O_RDONLY)) < 0)
{
delete []file_buf;
err_sys("open error");
}
if(read(fd, file_buf, file_stat.st_size) < 0)
{
vaild = 0;
}
close(fd);
}
else
{
vaild = 0;
}
/* 目标文件存在则发送http正常应答 */
int ret;
if(vaild)
{
ret = snprintf(head_buf, BUFFSIZE-1, "%s %s\r\n", "HTTP/1.1", status_line[0]);
len += ret;
ret = snprintf(head_buf+len, BUFFSIZE-1-len, "Content-Length: %lu\r\n", file_stat.st_size);
len += ret;
ret = snprintf(head_buf+len, BUFFSIZE-1-len, "\r\n");
struct iovec iv[2];
iv[0].iov_base = head_buf;
iv[0].iov_len = strlen(head_buf);
iv[1].iov_base = file_buf;
iv[1].iov_len = strlen(file_buf);
writev(connfd, iv, 2); //writev将head_buf和file_buf内容一并写出
delete []file_buf;
}
else //发生了错误
{
ret = snprintf(head_buf, BUFFSIZE-1, "%s %s\r\n", "HTTP/1.1", status_line[1]);
len += ret;
snprintf(head_buf, BUFFSIZE-1-len, "\r\n");
send(connfd, head_buf, strlen(head_buf), 0);
}
close(connfd);
}
}
close(listenfd);
return 0;
}
#include
ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);
返回:成功返回写入out_fd的字节数,出错-1
sendfile在两个文件描述符之间传递数据(完全在内核中操作),避免了内核缓冲区和用户缓冲区之间的数据复制,效率很高,称为零拷贝。in_fd是待读出数据的文件描述符,out_fd是待写入数据的文件描述符,off_t指定从流从哪个位置开始,为空则表示按照文件流默认的起始位置,count表示要传输的字节数。从该函数的man手册中可以知道, in_fd必须是一个支持mmap函数的文件描述符,即它必须指向真实的文件,不能指向socket和管道。在Linux2.6.33之前,out_fd必须指向一个socket,但是在这之后,out_fd可以指向任意文件。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SA struct sockaddr
#define SA_IN struct sockaddr_in
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { printf(msg); exit(-1); } while(0)
#define err_info(msg) \
do { printf(msg); } while(0)
int main(int argc, char *argv[])
{
if(argc != 4)
err_exit("usgae: ./a.out ip port filename\n");
int port = atoi(argv[2]);
char *filename = argv[3]; //目标文件为第4个参数
SA_IN servaddr;
int listenfd, connfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
err_sys("inet_pton error");
if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)
err_sys("bind error");
if(listen(listenfd, 5) < 0)
err_sys("listen error");
int fd;
struct stat file_stat;
if((fd = open(argv[3], O_RDONLY)) < 0)
err_sys("open error");
if(stat(argv[3], &file_stat) < 0)
err_sys("stat error");
SA_IN cliaddr;
socklen_t clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
err_sys("accept error");
if(sendfile(connfd, fd, NULL, file_stat.st_size) < 0)
err_info("sendfile error");
close(connfd);
close(fd);
close(listenfd);
return 0;
}
#include
ssize_t splice(int fd_in, loff_t* off_in, int fd_out, loff_t* fd_out, size_t len, unsigned int flags);
返回:成功返回复制的数据量(字节),出错-1
splice函数用于在两个文件描述符之间移动数据,也是零拷贝操作。fd_in是待数据数据文件描述符,如果fd_in是一个管道,则off_in必须为NULL;若off_in不为NULL,则表示具体的偏移位置,len参数指定移动数据的长度,flags控制如何移动数据。
splice的fd_in和fd_out必须至少有一个是管道文件描述符,splice函数调用成功返回移动字节数量,可以能返回0,表示没有数据移动,发生在从管道读取数据(fd_in是管道文件描述符),而管道中并没有写入任何数据。实际上,管道是一个固定大小的缓冲区。在Linux中,该缓冲区的大小为1页,即4K字节,使得它的大小不象文件那样不加检验地增长。使用单个固定缓冲区也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。
在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。
更多管道资料参考:http://www.cnblogs.com/davidwang456/p/3839874.html
这是使用splice函数实现的一个零拷贝的回射服务器,它把客户端发送的数据原样发回给客户端。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SA struct sockaddr
#define SA_IN struct sockaddr_in
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { printf(msg); exit(-1); } while(0)
#define err_info(msg) \
do { printf(msg); } while(0)
int main(int argc, char *argv[])
{
if(argc != 3)
err_exit("usgae: ./a.out ip port\n");
int port = atoi(argv[2]);
SA_IN servaddr;
int listenfd, connfd;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
err_sys("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);
if(inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)
err_sys("inet_pton error");
if(bind(listenfd, (SA *)&servaddr, sizeof(servaddr)) < 0)
err_sys("bind error");
if(listen(listenfd, 5) < 0)
err_sys("listen error");
SA_IN cliaddr;
socklen_t clilen = sizeof(cliaddr);
if((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0)
err_sys("accept error");
int pipefd[2];
if(pipe(pipefd) < 0)
err_sys("pipe error");
if(splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)
err_sys("splice error");
if(splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)
err_sys("splice error");
close(connfd);
close(listenfd);
return 0;
}
#include
ssize_t tee(int fd_in, int fd_out, size_t len, unsigned int flags);
返回:成功返回赋值的数据字节数,0为没有复制任何数据,出错返回-1
tee在两个管道之间复制数据,也是零拷贝操作。
#include
#include
#include
#include
#include
#include
#define err_sys(msg) \
do { perror(msg); exit(-1); } while(0)
#define err_exit(msg) \
do { printf(msg); exit(-1); } while(0)
#define err_info(msg) \
do { printf(msg); } while(0)
int main(int argc, char *argv[])
{
if(argc != 2)
err_exit("usage: ./a.out new_filename\n");
int fd;
if((fd = open(argv[1], O_CREAT | O_WRONLY | O_TRUNC, 0666)) < 0)
err_sys("open error");
int pipefd_file[2], pipefd_inout[2];
if(pipe(pipefd_file) < 0 || pipe(pipefd_inout) < 0)
err_sys("pipe error");
if(splice(STDIN_FILENO, NULL, pipefd_inout[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)
err_sys("splice error");
if(tee(pipefd_inout[0], pipefd_file[1], 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)
err_sys("tee error");
if(splice(pipefd_inout[0], NULL, STDOUT_FILENO, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)
err_sys("splice error");
if(splice(pipefd_file[0], NULL, fd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE) < 0)
err_sys("splice error");
close(pipefd_inout[0]);
close(pipefd_inout[1]);
close(pipefd_file[0]);
close(pipefd_file[1]);
close(fd);
return 0;
}
1、《Linux高性能服务器编程》第6章 高级IO函数
2、《UNIX网络编程》的相关章节
3、网络编程API-上 (基本API)
4、服务器、客户端简单交互程序