存储课程学习笔记6_io接口练习(readv,writev, 借助本地socket实现进程间(sendmsg,recvmsg)通过共享内存数据交互)
已经对io_uring进行简单的练习,有必要对readv,writev,sendmsg,recvmsg进行练习。
这类接口是可以一次性操作不连续的内存进行操作,减少了系统调用次数,也提升了整个io读写性能。
核心主要关注函数对应的参数,主要是结构体struct iovec。
0:总结
1:主要练习sendv和readv函数接口, 构造struct iovec结构体发送或者接收不连续内存的处理。
2:练习AF_UNIX 进行原生socket的通信。
3:基于socket通信的基础上,创建共享内存,多个进程之间实现交互(shm_open, mmap)
4:多个进程之间使用共享资源(这里共享内存),需要考虑互斥。
1:readv和writev简单demo进行练习。
1.1 测试demo代码,关注两个函数和结构struct iovec参数的处理。
把不连续的内存写入连续的内存中。 或者从不连续的内存中读到连续的内存中。
一次性把多个不连续缓冲区的内容写入文件中。
然后把文件中连续内存按不同字节读取到不连续缓冲区中。
//使用readv和writev进行测试 用于将不同的缓冲区写入或者写入不同的缓冲区中。 一次调用,减少系统调用。
//readv ===》网络接收数据存储在不同的多个非连续的内存缓冲区中, 从文件中读取放入不同的内存中。
//writev ===》高效写入 多个缓冲区一次性写入。 零拷贝,文件合并,日志记录,管道写入等。/********************
struct iovec {void *iov_base; // Starting address size_t iov_len; // Number of bytes to transfer
};ssize_t readv(int fd, const struct iovec *iov, int iovcnt);ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
*********************/#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>#include <sys/uio.h>
#include <unistd.h>
int main(int argc, char *argv[])
{if(argc < 2){printf("please use ./test 1 is writev, ./test 2 is readv. \n");return -1;}int para = atoi(argv[1]);if(para != 1 && para != 2){return -2;}//实际上就是操作不连续内存 这里用文件进行演示 if(para == 1) //测试writev写入功能 不同内存写入连续内存 原子操作{int fd = open("test.txt", O_WRONLY | O_CREAT, 0644);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}struct iovec iov[3];const char *str1 = " Test of 0。\n";iov[0].iov_base = (void *)str1;iov[0].iov_len = strlen(str1);const char *str2 = " Use writev test。\n";iov[1].iov_base = (void *)str2;iov[1].iov_len = strlen(str2);const char *str3 = " Over over。\n";iov[2].iov_base = (void *)str3;iov[2].iov_len = strlen(str3);//ssize_t nwrite = writev(fd, iov, 3); //返回的是成功写入的总字节数 if (nwrite == -1) {perror("writev");exit(EXIT_FAILURE);}printf("writev %ld bytes to test.txt\n", nwrite);close(fd);}if(para == 2) //测试readv 将连续内存一次性读出到不同的内存中{int fd = open("test.txt", O_RDONLY);if (fd == -1) {perror("open");exit(EXIT_FAILURE);}struct iovec iov[3];//这里要提供内存char buffer1[10] = {0};char buffer2[15] = {0};char buffer3[30] = {0};iov[0].iov_base = buffer1;iov[0].iov_len = sizeof(buffer1) - 1;iov[1].iov_base = buffer2;iov[1].iov_len = sizeof(buffer2) - 1;iov[2].iov_base = buffer3;iov[2].iov_len = sizeof(buffer3) - 1;ssize_t nread = readv(fd, iov, 3);if (nread == -1) {perror("readv");exit(EXIT_FAILURE);}printf("Read %ld bytes: \n", nread);printf("buffer1: %s\n", buffer1);printf("buffer2: %s\n", buffer2);printf("buffer3: %s\n", buffer3);close(fd);}return 0;
}
1.2 测试结果。
ubuntu@ubuntu:~/start_test$ gcc readv_writev.c -o readv_writev
ubuntu@ubuntu:~/start_test$ ./readv_writev
please use ./test 1 is writev, ./test 2 is readv.
ubuntu@ubuntu:~/start_test$ ./readv_writev 1
writev 48 bytes to test.txt
ubuntu@ubuntu:~/start_test$ ./readv_writev 2
Read 48 bytes:
buffer1: Test of
buffer2: 0。Use writ
buffer3: ev test。Over over。ubuntu@ubuntu:~/start_test$ cat test.txt Test of 0。Use writev test。Over over。
2:不同进程之间用共享内存进行交互。
2.1 练习分析
进程之间的通信方式有很多中,但是,无关联的进程之间通信,有哪些方案呢。
可以借助原生sock实现不同进程之间的信息交互。
使用共享内存的方式,申请一块内存,借助unix sock把堆内存的地址信息在不同进程之间进行交互后,统一使用同一块内存。
2.2 recvmsg练习,需要绑定socket等待对端。。。
借助recvmsg函数参数结构中的控制消息区struct cmsghdr *cmsg 可以把共享内存地址传递进行取数据。
关注两个点:
1:recvmsg接收对端的数据,接收到的数据进行解析。
2:解析接收到数据的控制区携带的数据信息,进行其他业务处理,或者读写数据。
//需要创建本地socket 一个是接收对端的sendmsg 一个是从控制消息区获取共享内存指针 打印对应信息
int recvmsg_test()
{//创建socket 进行绑定 仅供演示 recvmsg阻塞等待消息的接收//创建本地socket并进行绑定int sock_fd = 0;if ((sock_fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) {perror("socket");exit(EXIT_FAILURE);}struct sockaddr_un addr = {0};addr.sun_family = AF_UNIX;strncpy(addr.sun_path, "/tmp/temp_test.sock", sizeof(addr.sun_path) - 1);if (bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {perror("bind");exit(EXIT_FAILURE);}//创建对应的接收缓冲区struct iovec io = {0};char buffer[1024] = {0};struct msghdr msg = {0};//创建msg 做必要的空间申请 cmsg申请空间 struct cmsghdr *cmsg = NULL;char cmsg_buffer[CMSG_SPACE(sizeof(int))];memset(cmsg_buffer, 0, sizeof(cmsg_buffer));msg.msg_control = cmsg_buffer;msg.msg_controllen = sizeof(cmsg_buffer);io.iov_base = buffer;io.iov_len = 1024;msg.msg_iov = &io;msg.msg_iovlen = 1;//阻塞等待 接收对端的消息printf("Waiting for data...\n");if (recvmsg(sock_fd, &msg, 0) == -1) {perror("recvmsg");exit(EXIT_FAILURE);}printf("Received data: %s\n", buffer);//开始处理控制区的数据 获取并进行必要的校验 cmsg = CMSG_FIRSTHDR(&msg);if (cmsg == NULL || cmsg->cmsg_len != CMSG_LEN(sizeof(int))) {fprintf(stderr, "Invalid control message\n");exit(EXIT_FAILURE);}if (cmsg->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS) {fprintf(stderr, "Invalid control message\n");exit(EXIT_FAILURE);}//获取控制区中的对应的共享内存的对应fdint shmem_fd;shmem_fd = *(int*)CMSG_DATA(cmsg);//共享内存fd 通过mmap把共享内存和进程进行关联 获取内容void *shmem_ptr;if ((shmem_ptr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, shmem_fd, 0)) == MAP_FAILED) {perror("mmap");exit(EXIT_FAILURE);}printf("Shared memory content: %s\n", (char*)shmem_ptr);munmap(shmem_ptr, 1024);close(shmem_fd);close(sock_fd);return 0;
}
2.3 sendmsg的练习
int sendmsg_test()
{//借助共享内存 创建共享内存 设置共享内存大小//多进程使用相同共享内存 注意加锁。int shmemfd = shm_open("share_memory", O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);if (shmemfd == -1) {perror("shm_open");exit(EXIT_FAILURE);}ftruncate(shmemfd, 1024);//内存映射 映射上面创建的共享内存到进程中使用void *shmem_ptr;shmem_ptr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, shmemfd, 0);if (shmem_ptr == MAP_FAILED) {perror("mmap");exit(EXIT_FAILURE);}//给创建的共享内存中塞入数据// char *str = "my test of shared memory.\n";// memcpy(shmem_ptr, str, strlen(str));sprintf((char*)shmem_ptr, "Hello from shared memory!");//给共享内存中写入 借助sendmsg把信息发送给对应的目标socket//创建本地socket int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0); //s失败时注意上面资源的释放if (sockfd == -1) {perror("socket");exit(EXIT_FAILURE);}struct sockaddr_un addr = {0};addr.sun_family = AF_UNIX;strncpy(addr.sun_path, "/tmp/temp_test.sock", sizeof(addr.sun_path)-1);
//参考前面的逻辑 设置发送字符串struct iovec iov[3];const char *str1 = " Test of one。\n";iov[0].iov_base = (void *)str1;iov[0].iov_len = strlen(str1);const char *str2 = " Use writev test。\n";iov[1].iov_base = (void *)str2;iov[1].iov_len = strlen(str2);const char *str3 = " Over over。\n";iov[2].iov_base = (void *)str3;iov[2].iov_len = strlen(str3);
/******************
struct msghdr {void *msg_name; // 地址接收者的套接字地址socklen_t msg_namelen; // 地址接收者的套接字地址长度struct iovec *msg_iov; // I/O向量数组,用于指定待发送或接收的数据缓冲区size_t msg_iovlen; // I/O向量数组中元素的数量void *msg_control; // 与协议相关的辅助数据(如控制消息、带外数据等)size_t msg_controllen; // 辅助数据的长度int msg_flags; // 消息标志位,如 MSG_OOB、MSG_PEEK 等
};
*******************/struct msghdr msg = {0};// CMSG_SPACE 一个宏 获取下面cmsg的大小 char cmsg_buffer[CMSG_SPACE(sizeof(int))] = {0};msg.msg_control = cmsg_buffer;msg.msg_controllen = CMSG_LEN(sizeof(int));msg.msg_name = &addr;msg.msg_namelen = sizeof(addr);
//设置相关的控制消息区struct cmsghdr *cmsg = NULL;cmsg = CMSG_FIRSTHDR(&msg); //这个宏获取上面msg中对应的控制区地址 可以先定义cmsg再赋值给msg中cmsg->cmsg_level = SOL_SOCKET;cmsg->cmsg_type = SCM_RIGHTS;cmsg->cmsg_len = CMSG_LEN(sizeof(int));*(int*)CMSG_DATA(cmsg) = shmemfd; //把共享内存地址放到这里 对端可以从这个控制消息数据区获取到//真正数据设置 msg.msg_iov = iov;msg.msg_iovlen = 3;if (-1 == sendmsg(sockfd, &msg, 0)) {perror("sendmsg");exit(EXIT_FAILURE);}close(sockfd);munmap(shmem_ptr, 1024);close(shmemfd);shm_unlink("share_memory");return 0;
}
2.4 main函数入口
//sendmsg和recvmsg 配合AF_UNIX实现不同进行之间通信。//多个进程之间进项交互 可以使用共享内存
//有相关的进程可以用信号量配合共享内存直接使用
//无关联的进程 使用本地socket的方式进行交互
/*****************************
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);struct msghdr {void *msg_name; //* Optional address socklen_t msg_namelen; //* Size of address struct iovec *msg_iov; //* Scatter/gather array size_t msg_iovlen; //* # elements in msg_iov void *msg_control; //* Ancillary data, see below size_t msg_controllen; //* Ancillary data buffer len int msg_flags; //* Flags (unused)
};*******************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>#include <sys/types.h>
#include <sys/socket.h>#include <sys/un.h>
#include <fcntl.h>#include <sys/mman.h>
int sendmsg_test();
int recvmsg_test();
//借助unix套接字 实现不同进程之间消息交互
int main(int argc, char *argv[])
{if(argc < 2){printf("please use ./test 1 is sendmsg, ./test 2 is recvmsg. \n");return -1;}int para = atoi(argv[1]);if(para != 1 && para != 2){return -2;}if(para == 1){return sendmsg_test();}return recvmsg_test();
}
2.5 结果查看
#需要先运行 sendmsg_recvmsg 接收端 接收端创建本地socket文件 等待对端的发送
#这是接收端 先运行
ubuntu@ubuntu:~/start_test$ ./sendmsg_recvmsg 2
Waiting for data...
Received data: Test of one。Use writev test。Over over。Shared memory content: Hello from shared memory!#另外的终端运行 发送 进行通信测试 从上面的接收可以看到 接收到对应的接收 共享内存中的数据通过消息控制区获取到
#这是发送端
ubuntu@ubuntu:~/start_test$ ./sendmsg_recvmsg 1