已经对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