函数接口
#include<sys/types.h>
#include<sys/socket.h>
int sockpair(int domain, int type, int protocol, int sv[2]);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flag);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
socketpair创建的描述符任意一端既可以读也可以写,但只局限于父子进程中,是全双工的管道
- domin 表示协议族,必须为AF_LOCAL或者AF_UNIX
- type 表示类型,SOCK_STREAM或SOCK_DGRAM为前者时,创建的是管道
- protocol 必须是0
- sv 用于保存创建的套接字对
sendmsg的使用——重点在于结构体msghdr
struct msghdr{
//1
void *msg_name; //optional address
socklen_t msg_namelen; //size of address
//2
struct iovec *msg_iov; //scatter/gather array
size_t msg_iovlen; //elements in msg_iov
//3
void *msg_control; //ancillary data, see be low
size_t msg_controllen; //ancillary data buffer len
//4
int msg_flags; //flags(unused)
- 第一组参数,套接口地址成员(未使用)
- 第二组参数,IO向量缓冲区(聚集写,填充)
- 第三组参数,附属数据缓冲区(填充)
- 第四组参数,接收信息标记位(未使用)
msg_iovlen 指数组元素个数
msg_controllen指控制信息所占用的总字节数
控制信息使用cmsghdr结构体来填充
结构体struct iovec的使用
- writev 聚集写,是一个系统调用
- readv 分散读,是一个系统调用
#include<sys/uio.h>
struct iovec{
void* iov_base; //指向的是一个缓冲区的首地址
size_t iov_len; //指向的是缓冲区中数据的长度
};
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
//其中第二个参数指针iov指向的是一个数组,数组中元素为结构体iovec
聚集写示例
#include <func.h>
#include <sys/uio.h>
int main(int argc, char *argv[]){
char buf1[] = "hello,";
char buf2[] = "world";
int fd;
fd = open("file.txt", O_RDWR | O_CREAT , 0666);
if (fd == -1)
{
perror("open");
exit(EXIT_FAILURE);
}
struct iovec vecs[2];
vecs[0].iov_base = buf1;
vecs[0].iov_len = strlen(buf1);
vecs[1].iov_base = buf2;
vecs[1].iov_len = strlen(buf2);
int ret = writev(fd, vecs, 2);
if (ret < 0)
{
perror("writev");
}
printf("ret:%d\n",ret);
close(fd);
return 0;
}
可以把不同地方的缓冲区聚集起来通过writev一次性全部发送出去,此时只执行一次系统调用,可以提高程序的执行效率。
分散读示例
#include <func.h>
#include <sys/uio.h>
int main(){
char buf1[6] = {0};
char buf2[6] = {0};
struct iovec vecs[2];
vecs[0].iov_base = buf1;
vecs[0].iov_len = sizeof(buf1)-1;
vecs[1].iov_base = buf2;
vecs[1].iov_len = sizeof(buf2);
int fd = open("file.txt", O_RDWR);
if (fd < 0)
{
perror("open");
exit(EXIT_FAILURE);
}
int ret = readv(fd, vecs, 2);
if (ret < 0)
{
perror("readv");
exit(EXIT_FAILURE);
}
printf("read ret : %d\n",ret);
printf("buf1: %s ,buf2: %s\n",buf1, buf2);
close(fd);
return 0;
}
当第一个缓冲区没有填满时,会一直写数据,直到第二个缓冲区填满之后,再填第二个缓冲区。
附属数据的填充——结构体struct cmsghdr
struct cmsghdr{
size_t cmsg_len; //Data byte count,including header(type is socklen_t in POSIX)
int cmsg_level; // Originating protocol
int cmsg_type; // Protocol-specific type
//follower by
//unsigned char cmsg_data[];
}
其成员描述为
成员 | 描述 |
cmsg_len | 附属数据的字节技术,这包含结构头的尺寸。这个值是由CMSG_LEN()宏计算的。 |
cmsg_level | 这个值表明了原始的协议级别(例如,SOL_SOCKET)。 |
cmsg_type | 这个值表明了控制信息类型(例如,SCM_RIGHT)。 |
cmsg_data | 这个成员并不实际存在,只是用来指明实际的额外附属数据所在的位置。 |
cmsg_type类型:
- SCM_RIGHT 附属数据对象是一个文件描述符
- SCM_CREDENTIALS 附属数据对象是一个包含证书信息的结构
由于附属数据结构的复杂性,Linux系统提供了一系列的C宏来简化我们的工作。另外这些宏可以在不同的UNIX平台之间进行移植,并且采取了一些措施来防止将来的改变。
size_t CMSG_LEN(size_t length);
//获取整个结构体的长度
unsigned char* CMSG_DATA(struct cmsghdr* cmsg);
//获取data所在的首地址
CMSG_LEN是通过length再加上结构体前三项的长度,从而得出总长度
结构体struct msghdr的发送和接收实现
父进程发送
int sendFd(int pipefd, int fd){
//1. 准备一个iovec结构体
struct iovec iov;
memset(&iov, 0, sizeof(iov));
char buf[10] = {0};
strcpy(buf, "hello");
iov.iov_base = buf;
iov.iov_len = strlen(buf);
//2. 准备一个cmsghsr结构体
size_t len = CMSG_LEN(sizeof(fd));
struct cmsghdr* cmsg = calloc(1, fd);
cmsg->cmsg_len = len;
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
int *pfd = (int*)CMSG_DATA(cmsg);
*pfd = fd;
//3.组建一个msghdr结构体
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;//必填
msg.msg_iovlen = 1;
msg.msg_control = cmsg;
msg.msg_controllen = len;
//4.发送
int ret = sendmsg(pipefd, &msg, 0);
if (ret == -1)
{
perror("sendmsg");
exit(EXIT_FAILURE);
}
return 0;
}
子进程接收
#include<func.h>
int recvFd(int pipeFd, int *fd){
//1.准备一个iovec结构体
struct iovec iov;
memset(&iov, 0, sizeof(iov));
char buf[10];
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
//2.准备一个cmsghdr结构体
size_t len = CMSG_LEN(sizeof(fd));
struct cmsghdr* pcmsg = calloc(1, len);
pcmsg->cmsg_len = len;
pcmsg->cmsg_level = SOL_SOCKET;
pcmsg->cmsg_type = SCM_RIGHTS;
//3.组建一个msghdr结构体
struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = pcmsg;
msg.msg_controllen = len;
//4.进行接收
int ret = recvmsg(*fd, &msg, 0);
if (ret < 0)
{
perror("recvmsg");
exit(EXIT_FAILURE);
}
printf("recv %d byte.\n", ret);
int *p = (int*)CMSG_DATA(pcmsg);
*fd = *p;
free(pcmsg);
return 0;
}
sendmsg and recvmsg
int sendmsg(int fd, const struct msghdr* msg, int flags);
int recvmsg(int fd, const struct msghdr* msg, int flags);
recvmsg中flags
:指定函数行为的标志,比如是否阻塞等,常见的标志有:
MSG_WAITALL
:等待直到接收到指定大小的数据。MSG_DONTWAIT
:设置为非阻塞模式。MSG_PEEK
:查看消息但不从队列中移除
但是两个函数中通常flags为0。
socketpair 使用匿名管道通信
通过一次sendmsg,一次recvmsg来完成进程间的dup机制