6.1 pipe函数
pipe函数可以用来创建一个单向管道,来实现进程间的通信。
arduino
#include <unistd.h>
int pipe(int fd[2])
pipe创建的管道是单向且阻塞的 。该函数成功时返回0,并将一对打开的文件描述符填入其参数指向的数组。如果失败则返回-1,并设置errno。
如果管道的写入端描述符的引用计数为0时,那么针对该管道的读取端描述符返回0,即读取到了文件结束标记;如果管道的读取端描述符的引用计数为0,那针对该管道的写入端描述符的write操作将失败,并引发SIGPIPE信号。
管道是字节流,默认大小为65536,且可以通过fcntl函数来修改管道容量。
而socketpair函数可以很方便的创建双向管道。
arduino
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int fd[2]);
6.2 dup函数和dup2函数
该函数可以标准输入重定向到一个文件,或者把标准输出重定向到一个网络连接。
arduino
#include <unistd.h>
int dup(int file_descriptor)
int dup2(int file_descriptor_one, int file_descriptor_tow)
dup函数重新创建一个新的文件描述符,该新文件描述符和原有文件描述符file_descriptor指向相同的文件、管道或者网络连接。但是它们返回的文件描述符数值不太一样。
服务器发送数据代码:
ini
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.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] );
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
ret = listen( sock, 5 );
assert( ret != -1 );
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
/*关闭标准输入文件符*/
close( STDOUT_FILENO );
dup( connfd );
printf( "abcd\n" );
close( connfd );
}
close( sock );
return 0;
}
客户端接收数据代码:
arduino
#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>
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] );
struct sockaddr_in server_address;
bzero( &server_address, sizeof( server_address ) );
server_address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &server_address.sin_addr );
server_address.sin_port = htons( port );
int sockfd = socket( PF_INET, SOCK_STREAM, 0 );
assert( sockfd >= 0 );
if ( connect( sockfd, ( struct sockaddr* )&server_address, sizeof( server_address ) ) < 0 )
{
printf( "connection failed\n" );
}
else
{
/*由客户端来接收服务端重定向到网络socket的数据*/
char buffer[1024];
memset(buffer, '\0', 1024);
int ret = recv(sockfd, buffer, 1023, 0);
printf("the dup data: %s", buffer);
}
close( sockfd );
return 0;
}
客户端接收数据实验截图:

6.3 readv函数和writev函数
readv函数将数据从文件描述符读到分散的内存块中,即分散读;writev函数则将多块分散的内存数据一并写入文件描述符中,即集中写。
arduino
#include <sys/uio.h>
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结构数据;
readv和writev成功时返回读取/写入fd的字节数,失败则返回-1。
ini
struct iovec iv[2];
iv[0].iov_base = header_buf;
iv[0].iov_len = strlen(header_buf);
iv[1].iov_base = file_buf;
iv[1].iov_len = file_stat.st_size;
ret = writev(connfd, iv, 2);
6.4 sendfile函数
sendfile函数直接在两个文件描述符之间传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区之间的数据拷贝,效率很高,这被称为零拷贝。
arduino
#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参数指定在文件描述符in_fd和out_fd之间传输的字节数;
sendfile成功时返回传输的字节数,失败则返回-1并设置errno。
in_fd必须是一个支持类似mmap函数的文件描述符,即它必须指向真实的文件,不能是socket和管道;而out_fd则必须是一个socket
服务端发送文件代码:
c
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
int main( int argc, char* argv[] )
{
if( argc <= 3 )
{
printf( "usage: %s ip_address port_number filename\n", basename( argv[0] ) );
return 1;
}
const char* ip = argv[1];
int port = atoi( argv[2] );
const char* file_name = argv[3];
int filefd = open( file_name, O_RDONLY );
assert( filefd > 0 );
/*struct stat用来描述一个Linux系统文件系统中的文件属性的结构*/
struct stat stat_buf;
/*fstat获取文件的信息*/
fstat( filefd, &stat_buf );
struct sockaddr_in address;
bzero( &address, sizeof( address ) );
address.sin_family = AF_INET;
inet_pton( AF_INET, ip, &address.sin_addr );
address.sin_port = htons( port );
int sock = socket( PF_INET, SOCK_STREAM, 0 );
assert( sock >= 0 );
int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
assert( ret != -1 );
ret = listen( sock, 5 );
assert( ret != -1 );
struct sockaddr_in client;
socklen_t client_addrlength = sizeof( client );
int connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
if ( connfd < 0 )
{
printf( "errno is: %d\n", errno );
}
else
{
/*直接从内核缓冲区拷贝数据*/
sendfile( connfd, filefd, NULL, stat_buf.st_size );
close( connfd );
}
close( sock );
return 0;
}
以下是stat结构体的信息:
arduino
#include <sys/stat.h>
struct stat {
dev_t st_dev; // 文件所在设备号
ino_t st_ino; // I节点号
mode_t st_mode; // 文件类型和权限
nlink_t st_nlink; // 硬链接数量
uid_t st_uid; // 所有者ID
gid_t st_gid; // 所有者的组ID
dev_t st_rdev; // 设备号(仅特殊文件)
off_t st_size; // 总字节大小
blksize_t st_blksize; // 文件系统I/O的块大小
blkcnt_t st_blocks; // 分配的512B块数量
struct timespec st_atim; // 文件数据的最后访问时间
struct timespec st_mtim; // 文件数据的最后修改时间
struct timespec st_ctim; // i节点状态的最后更改时间
#define st_atime st_atim.tv_sec
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
服务端实验截图:

客户端实验结果:

6.5 mmap函数和munmap函数
mmap函数用于申请一段内存空间。我们可以将这段内存作为进程间通信的共享内存,也可以将文件直接映射到其中。munmap函数则释放由mmap创建的这段内存空间。
linux共享内存\] [zhuanlan.zhihu.com/p/633054182](https://link.juejin.cn?target=https%3A%2F%2Fzhuanlan.zhihu.com%2Fp%2F633054182 "https://zhuanlan.zhihu.com/p/633054182")
```arduino
#include