5-共享内存

巧了我就是萌 提交于 2020-02-28 12:44:05

1. 套接字超时

1.1 accept/read/write超时

  1. 如何设置accept()函数等待连接时的超时时长?
    答:accept()函数本身会一直阻塞到有连接请求而无法计时,因此可以设置由内核监听连接并计时。即由select()/epoll()/poll()函数来计时。
  2. 如何设置read()/write()的等待时长?
    答:read()函数要一直监测缓冲区中有数据才能解除阻塞读数据,因此可以同accept()函数一样,由内核监测。而write()函数要一直监测缓冲区未满才能解除阻塞写数据,因此也可以由内核检测。
//1.设置监听时间
fd_set rdset;
FD_ZERO(&rdset);
FD_SET(fd, &rdset);
struct timeval timeout = {10, 0};
//2.设置为内核检测
int ret = select(fd+1, &rdset, %rdset, NULL, &timeout);
if(ret == 0)	//若无描述符变化
{
	//监测超时
}
else if(ret > 0)	//若有变化
{
	//accept则接受链接
	struct sockaddr addr;
	int len = sizeof(addr);
	int cfd = accept(fd, &addr, &len);
	//read则开始读数据
	char buf[1024] = {0};
	read(fd, buf, sizeof(buf));
	//write则开始写数据
	write(fd, buf, strlen(buf));
}

1.2 connect超时

  1. connect的连接过程?
    答:调用connect()函数, 客户端与服务器进行三次握手;此时由于通讯套接字cfd,所以是阻塞状态;若超过默认75s依然未连接上,则连接失败。
  2. connect如何自定义连接时间?
    答:先将connect设置为非阻塞,即设置套接字cfd的属性;再根据Posix 标准进行设置。
  3. Posix标准connect连接时长?
4. Posix 定义了与 select/epoll 和 非阻塞 connect 相关的规定:
   - 连接成功建立时,socket 描述字变为可写。(连接建立时,写缓冲区空闲,所以可写)
   - 连接建立失败时,socket 描述字既可读又可写。 (由于有未决的错误,从而可读又可写)

5. 连接失败, 错误判定方式:
   - 当用select检测连接时,socket既可读又可写,只能在可读的集合通过getsockopt获取错误码。
   - 当用epoll检测连接时,socket既可读又可写,只能在EPOLLERR中通过getsockopt获取错误码。
  1. 根据Posix标准实现定义connect时长?
//1. 设置connect为非阻塞:即设置cfd属性
int fd1 = fcntl(cfd, F_GETFL);
fd1 |= O_NONBLOCK;
fcntl(cfd, F_SETFL, fd1);
//2. 建立连接请求
int ret = connect(cfd, serveraddr, addrlen);
	//若未连接成功且正在连接中
if(ret == -1 && errno = EINPROCESS)
{
	//开始连接计时:监测cfd变为可写
	fd_set wrset;
    FD_ZERO(&wrset);
    FD_SET(cfd, &wrset);
    struct timeval timeout = {10, 0};	//设置连接时间
	int ret = select(cfd, NULL, &wrset, NULL, &timeout);	//开始监测
	if(ret == 0)	//若cfd不可写,即连接超时
	{
	}
	else if(ret > 0)	//若cfd变为可写,则连接成功/失败
	{
		int opt;
		int len = sizeof(opt);
		getsockopt(cfd, SOL_SOCKET, SO_ERROR, &opt, &len);	//判断是否连接成功
		if(opt == 0)
		{
			//连接成功
		}
		else if(opt == -1)
		{
			//连接失败
		}	
	}	
}

2. 共享内存

  1. 共享内存的工作流程?
    答:(1)进程申请一块指定大小的内存;(2)将申请的内存与当前进程相关联;(3)使用申请的内存;(4)解除改内存的关联并销毁。
  2. 如何申请一块指定大小的内存?
    答:通过shmget()函数。
int shmget(key_t key, size_t size, int shmflg);
参数:key-16进制非0整型数,用来创建内存;size-指定创建内存的大小;shmflg-内存属性,包括访问权限和附加属性,附加属性:IPC_CREAT创建/IPC_EXCL判断是否已创建;
返回值:成功-共享内存引用ID;失败--1
//创建一块4K大小内存
int shmid = shmget(0x10, 4096, IPC_CREAT|0751);
 //判断是否创建
int ret = shmget(0x10, 0, IPC_CREAT|IPC_EXCL|0751);
返回值:存在->0,内存ID; 不存在--1//打开创建的内存
shmget(0x10, 0, 0);
  1. 如何关联申请的内存?
    答:用shmat()函数。
 void *shmat(int shmid, const void *shmaddr, int shmflg);
 shmid-申请的内存id;shmaddr-申请内存的起始地址,一般写NULL由内核指定;shmflg-申请内存的属性,SHM_RDONLY只读属性必须有,若要读写权限写0;
 返回值:成功-申请内存的起始地址;失败-(void *)-1;
//关联并使用申请的内存
void *ptr = shmat(shmid, NULL, 0);
char *s = "hello";
memcpy(ptr, s, strlen(s));
  1. 如何分离关联的共享内存?
    答:用shmdt()函数;
int shmdt(const void* shmaddr);
shmaddr-共享内存的起始地址;
返回值:成功-0;失败--1
int ret = shmdt(shmaddr);
  1. 如何销毁申请的共享内存?
    答:关机后自动销毁,若不关机可用shmctl()函数
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid-共享内存ID;cmd-对共享内存的操作,IPC_STAT获取共享内存当前状态,
IPC_SET设置共享内存当前状态,IPC_RMID销毁当前共享内存; 
buf-存储cmd信息,cmd是IPC_STAT时存储共享内存状态信息,IPC_SET时初始化并将buf内容设置到共享内存
IPC_RMIND时则无用,设置为NULL;
返回值:根据cmd不同,返回值含义不同。
//销毁共享内存
shmctl(shmid, IPC_RMID, NULL);
  1. 共享内存实现进程间通信的步骤?
//写操作
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
{
	//1.创建共享内存
	int shmid = shmget(0x10, 1034, IPC_CREAT|0751);
	if(shmid < 0)
	{
		perror("shmget");
		exit(0);
	}
	//2.建立连接
	 void *shmaddr = shmat(shmid, NULL, 0);
	 if(shmaddr == (void *)-1)
	 {
		perror("shmat");
		exit(0);
	 }
	 //3.读共享内存数据
	 char *s = "hello"
	 memcpy(shmaddr, s, strlen(s));
	 //4.取消关联
	 printf("按任意键取消链接...\n");
	 fgetc(stdin);
	 shmdt(shmaddr);
	 //5.销毁共享内存
	 shmctl(shmid, IPC_RMID, NULL);
	 
	 return 0;
}  
//读操作
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdlib>
#include <stdio.h>
#include <string.h>
int main()
{
	//1.打开共享内存
	int shmid = shmget(0x10, 0, 0);
	if(shmid < 0)
	{
		perror("shmget");
		exit(0);
	}
	//2.建立连接
	 void *shmaddr = shmat(shmid, NULL, 0);
	 if(shmaddr == (void *)-1)
	 {
		perror("shmat");
		exit(0);
	 }
	 //3.读共享内存数据
	 char buf[1024] = {0};
	 memcpy(buf, shmaddr, sizeof(buf));
	 printf("%s\n", buf);
	 //4.取消关联
	 printf("按任意键取消链接...\n");
	 fgetc(stdin);
	 shmdt(shmaddr);
	 //5.销毁共享内存
	 shmctl(shmid, IPC_RMID, NULL);
	 
	 return 0;
}
  1. 操作系统如何知道共享内存有多少进程关联?
    答:每调用一次shmat()函数,内存信息中的引用计数就会+1,调用shmdt(),引用计数-1;
  2. 是否可以对同一共享内存删除多次?
    答:可以,多个进程都可以调用shmctl()进行删除,没调用一次就会做一次删除标记,当所有进程取消关联,共享内存就会销毁。
    当调用一次shmdt()函数后,已关联的进程可以继续使用,未关联的进程无法再关联。
  3. 共享内存与内存映射区进行进程通信的区别?
    答:(1)通信效率:shm直接内存操作效率高;mmap须同步磁盘文件效率低,智能血缘关系之间通信;
    (2)内存共享性:shm多个进程共享一块内存;mmap多个进程操作自己进程中的用户数据区,共享的是磁盘文件;
    (3)数据安全:shm与进程无关,但与内存有关;mmap与内存无关,但与进程有关。因此若进程突然退出不影响shm,但mmap将失败;若突然断电关机不影响mmap,但shm将失败。
  4. 如何使用ftok()函数?
    答:ftok()函数随机生成一个shm的键值
key_t ftok(const char *pathname, int proj_id);
pathname-路径,可随意指定
proj_jd-随意指定的字符
// string -> char* 
// .c_str()  /    data()
key_t t = ftok("/home/", 'a');
shmget(t, 0, 0);
  1. 共享内存的API封装
    答:
class BaseShm
{
public:
    BaseShm(int key)
    {
        getShmID(key, 0, 0);
    }
    BaseShm(int key, int size)
    {
        getShmID(key, size, IPC_CREAT|0664);
    }
    BaseShm(string name)
    {
        key_t key = ftok(name.data(), 'x');
        getShmID(key, 0, 0);
    }
    BaseShm(string name, int size)
    {
        key_t key = ftok(name.data(), 'x');
        getShmID(key, size, IPC_CREAT|0664);
    }
    
    void* mapShm()
    {
        m_ptr = shmat(m_shmid, NULL, 0);
        return m_ptr;
    }
    
    int unmapShm()
    {
        int ret = shmdt(m_ptr);
        return ret;
    }
    
    int delShm()
    {
        int ret = shmctl(m_shmid, IPC_RMID, NULL);
        return ret;
    }
    
private:
    int getShmID(int key, int size, int flag)
    {
        m_shmid = shmget(key, size, flag);
        return shmid;
    }
    
    int m_shmid;
    void* m_ptr;
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!