存储课程学习笔记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);
    }

    //获取控制区中的对应的共享内存的对应fd
    int 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
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值