windows下共享内存和数据同步

本文详细探讨了在Windows系统中,通过共享内存和数据同步解决多进程间数据交互的问题,包括加标志位、文件锁和互斥锁的使用,以及在多核多线程环境下可能遇到的问题与解决策略。

windows下共享内存和数据同步

前言

     当在windows系统下,有多个exe程序同时频繁处理大量文件操作时,而且还要保证效率,假设一个简单的场景:一个程序负责产生数据,另一个程序负责读取数据,那么问题来了,两个进程都是彼此独立的,彼此都不知道各自的存在,如何读取数据呢?我们首先能想到的是使用共享内存。但是,在多核情况下,读写如何同步呢?试想以下场景:
     场景1:出现A进程数据写了一部分数据在共享内存中(没写完),而这时B进程读这块内存,那么读取的这块内存数据就不完整。
     场景2:当A进程完整的把一整包数据写到内存中,而这时B进程在处理其他的事情,来不及读这块内存。而这时B进程有写了第二包包数据到内存中(把先前的数据冲掉了),B进程忙完了手头上的事情,来读这块内存数据,B进程将会丢掉第一包数据。

解决思路

第一种方案:加标志位来保证数据安全。把标志位封装到数据头,当A进程写数据时先把标志位置1(写数据中),这时B进程想读数据,发现数据头标志位是1,等待,直到数据头标志位为1才读取。
第二种方案:使用文件锁,线程A写数据,具有独占锁属性,线程B没有权限读取内存文件。详见微软官方LockFile函数文档:LockFile()
第三种方案:加互斥锁

尝试

第一种方案:加标志位,如果是单核、单线程的状态下,在进程间、或线程间同步不会有问题,但是遇到多核、多线程的情况下,在极端情况下,比如写进程刚写了数据,标志位还没来得及修改,这时读进程获取的标志位就是有问题的,可能就会读到错误的数据。

遇到的问题

代码

服务器端

服务端头文件

#pragma once
/******************************************************************
    ++  File Name :   FFMClass.h
    ++  Description: 共享内存类
    ---------------------------------------------------------------
    ++  Author:  Fei ZhaoDong
    ++  Create time: 2004/3/25 上午 10:00:00
    ++  Version:     1.1
    ++  Modifier:
    ++  Activities:
    ++  Update List: 2004/3/30 下午 02:59:45
*******************************************************************/
// FFMClass.h: interface for the CFFMClass.
//
//
#ifndef FZD_FFMCLASS_H
#define FZD_FFMCLASS_H
//#include <Winbase.h>
#include <aclapi.h>
#include <mutex>
#include <shared_mutex>
#include <iostream>
#include <chrono>
#include "ElapsedTimer.h"

using namespace std;

//
// Macro definition
// 以下为共享内存定义
#define DEFAULT_FILENAME NULL    // 默认的文件名
#define DEFAULT_MAPNAME  "Local//_FZD_MAP_"   // 默认的共享内存名
#define DEFAULT_MAPSIZE  201<<20  // 默认的共享内存大小:200兆
const DWORD NETRGUSER_CFM_CODE = 0x1211DBFF; // 校验码, 用于命令数据
const DWORD NETRGUSER_CMD_NONE = 0;   // 初始化指令码, 无指令
// 以下为错误码定义
#define ERROR_LEN    256    // 错误描述长度
#define ERROR_INVALID_CMDCODE 0xE00001FF  // 已经存在完全一样的共享内存
#define ERROR_NO_MAPFILE             0xE00002FF  // 未分配共享内存文件
#define ERROR_INVALID_CFMCODE 0xE00003FF  // 校验码不匹配
//
// 内存文件格式定义
#pragma pack(1)
// 用于存储命令数据的内存文件格式
typedef struct _tagDATA_HEADER
{
    DWORD dwConfirmCode; // 校验码
    DWORD nCommandCode;  // 指令识别码
    DWORD dwDataSize;  // 数据的大小
	BYTE  dwReserved[32]; // 保留 读写标志位,
	BYTE  dwElapsed[64];  //时间戳 
    BYTE  bInfo[1];   // 数据起始地址
    _tagDATA_HEADER()
    {
        dwConfirmCode = NETRGUSER_CFM_CODE;
        nCommandCode = NETRGUSER_CMD_NONE;
        dwDataSize = 0;
        ZeroMemory(dwReserved, 19);
        ZeroMemory(bInfo, 1);
    }
} DATA_HEADER, *LPDATA_HEADER;
typedef DWORD(WINAPI* PSetEntriesInAcl)(ULONG, PEXPLICIT_ACCESS, PACL, PACL*);
// 用于存储应答数据的共享内存文件格式 (暂不用)
typedef struct _tagANS_HEADER
{
} ANS_HEADER, *LPANS_HEADER;

#pragma pack()

//
// 类定义,共享内存服务端
class CFFMServer
{
public:
    CFFMServer();
    virtual ~CFFMServer();
    CFFMServer(char* szFileName, char* szMapName, DWORD dwSize);
protected:
    PSetEntriesInAcl m_fnpSetEntriesInAcl;
    HANDLE m_hFile;   // 映射文件句柄
    HANDLE m_hFileMap;   // 内存文件句柄
    LPVOID m_lpFileMapBuffer; // 缓冲区指针
    char* m_pFileName;  // 映射文件名
    char* m_pMapName;  // 内存文件名
    DWORD m_dwSize;   // 缓冲区大小
    BOOL m_bCreateFlag;  // 是否已创建共享内存
    DWORD   m_dwLastError;  // 错误代码

	BOOL m_bRetLockFile;    //文件锁标志
	LPCTSTR m_lpMutexName;  //互斥量名字
	HANDLE m_hMutex;   //互斥量,独占锁
	HANDLE m_hSemaphore;//信号量,
	mutable std::shared_mutex m_mutex;
	std::shared_lock<std::shared_mutex> m_readLock;
	std::unique_lock<std::shared_mutex> m_writeLock;

	//三组共享内存
	HANDLE m_hFileArr[3];// 映射文件句柄数组
	HANDLE m_hFileMapArr[3];   // 内存文件句柄数组

	//计算时间
	ElapsedTimer m_elspsedTimer;

private:
    void _Init();    // 初始化参数
    BOOL _IsWinNTLater();  // 判断当前操作系统
public:
    BOOL Create(char* szFileName = DEFAULT_FILENAME,
                char* szMapName = DEFAULT_MAPNAME,
                DWORD dwSize = DEFAULT_MAPSIZE); // 新建共享内存
    LPVOID GetBuffer();      // 获取内存文件指针
    DWORD GetSize();      // 获取内存文件大小
    void Destory();       // 销毁已有的共享内存
    BOOL WriteCmdData(      // 写入命令数据
        DWORD nCommandCode,
        DWORD dwDataSize,
        const LPVOID pBuf);
        
    BOOL CanWrite();
    
    BOOL WriteOver();
};

#endif // FZD_FFMCLASS_H

服务端实现文件

/******************************************************************
    ++  File Name : FFMClass.cpp
    ++  Description: 共享内存类
    ---------------------------------------------------------------
    ++  Author:  Fei ZhaoDong
    ++  Create time: 2004/3/25 上午 10:00:00
    ++  Version:     1.0
    ++  Modifier:
    ++   Activities:
    ++  Update List: 2004/3/29 下午 02:59:45
*******************************************************************/
// FFMClass.cpp: implementation of the CFFMClass.
//
//
#include "FFMService.h"
#include <string.h>
#include <iostream>
#include <cstdlib>
#include <cstdio>
#include <io.h>
#include <string>
//#include <aclapi.h>
//#include <windows.h>
//#include <aclapi.h>
//#include <lmerr.h>
//
//#include <stdio.h>
//
// CFFMServer
//

using namespace std;

#define  CLINET_FLAG 2
#define TEST_FILE_PATH "../TestNumFile.txt"
#define TIME_BUFFER_SIZE 64

char SemaphoreName[50] = "lockShardMemory";

CFFMServer::CFFMServer()
{
    m_dwLastError = 0;
    m_fnpSetEntriesInAcl = NULL;
    _Init();
}
CFFMServer::~CFFMServer()
{
    Destory();
}
CFFMServer::CFFMServer(char* szFileName, char* szMapName, DWORD dwSize)
{
    // 以自定义设置创建共享内存块
    _Init();
    Create(szFileName, szMapName, dwSize);
}
// 初始化各个参数
void CFFMServer::_Init()
{
    m_hFile = NULL;
    m_hFileMap = NULL;
    m_lpFileMapBuffer = NULL;
    m_pFileName = NULL;
    m_pMapName = NULL;
    m_dwSize = 0;
    m_bCreateFlag = FALSE;
	m_lpMutexName = "fileLock";
}
// 判断是否NT4.0以上操作系统
BOOL CFFMServer::_IsWinNTLater()
{
    return FALSE;
    OSVERSIONINFO Ver;
    BOOL bAbleVersion = FALSE;
    Ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
    
    if(GetVersionExA(&Ver))
    {
        if(Ver.dwPlatformId == VER_PLATFORM_WIN32_NT
                && Ver.dwMajorVersion >= 4)
        {
            bAbleVersion = TRUE;
        }
    }
    else
    {
        m_dwLastError = GetLastError();
    }
    return bAbleVersion;
}
// 释放当前共享内存,并重新初始化参数
void CFFMServer::Destory()
{
    if(m_lpFileMapBuffer != NULL)
    {
        UnmapViewOfFile(m_lpFileMapBuffer);
        m_lpFileMapBuffer = NULL;
    }
    if(m_hFileMap != NULL)
    {
        CloseHandle(m_hFileMap);
        m_hFileMap = NULL;
    }
    if(m_hFile && m_hFile != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hFile);
        m_hFile = NULL;
    }
    if(m_pFileName != NULL)
    {
        free(m_pFileName);
        m_pFileName = NULL;
    }
    if(m_pMapName != NULL)
    {
        free(m_pMapName);
        m_pMapName = NULL;
    }
    _Init();
}
static void FreeSidEx(PSID oSID)
{
    try
    {
        FreeSid(oSID);
    }
    catch(...)
    {
    }
}
// 创建共享内存块
BOOL CFFMServer::Create(char* szFileName, char* szMapName, DWORD dwSize)
{
    // 释放已有的共享内存块
    if(m_bCreateFlag)
    {
        Destory();
    }
    // 拷贝各个参数
    if(szFileName)
    {
        m_pFileName = _strdup(szFileName);
    }
    if(szMapName)
    {
        m_pMapName = _strdup(szMapName);
    }
    else
    {
        m_pMapName = _strdup(DEFAULT_MAPNAME);
    }
    if(dwSize > 0)
    {
        m_dwSize = dwSize;
    }
    else
    {
        m_dwSize = DEFAULT_MAPSIZE;
    }
    // 以下创建共享内存
    if(m_pFileName)
    {
        m_hFile = CreateFile(
                      m_pFileName,
                      GENERIC_READ | GENERIC_WRITE,
                      FILE_SHARE_READ | FILE_SHARE_WRITE,
                      NULL,
                      OPEN_ALWAYS,
                      FILE_ATTRIBUTE_NORMAL,
                      NULL
                  );
    }
    else
    {
        // 默认情况下,在页面文件中创建共享内存
        m_hFile = INVALID_HANDLE_VALUE;
    }
    if(_IsWinNTLater())
    {
        // Set DACL
        const int NUM_ACES = 2;   // number if ACEs int DACL
        // evryone -- read
        // creator -- full access
        // 初始化参数
        PSID pEveryoneSID = NULL; // everyone群组SID
        PSID pCreatorSID = NULL; // creator群组SID
        PACL pFileMapACL = NULL; // 准备新内存文件的DACL
        PSECURITY_DESCRIPTOR  pSD = NULL; // 内存文件的SD
        SECURITY_ATTRIBUTES   saFileMap; // 内存文件的SA
        EXPLICIT_ACCESS    ea[NUM_ACES]; // 外部访问结构
        BOOL bHasErr = FALSE; // 返回值
        // 以下创建SID
        SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
        SID_IDENTIFIER_AUTHORITY SIDAuthCreator = SECURITY_CREATOR_SID_AUTHORITY;
        // Evryone
        if(!AllocateAndInitializeSid(&SIDAuthWorld, 1, SECURITY_WORLD_RID,
                                     0, 0, 0, 0, 0, 0, 0, &pEveryoneSID))
        {
            bHasErr = TRUE;
            goto Finish;
        }
        // Creator
        if(!AllocateAndInitializeSid(&SIDAuthCreator, 1, SECURITY_CREATOR_OWNER_RID,
                                     0, 0, 0, 0, 0, 0, 0, &pCreatorSID))
        {
            bHasErr = TRUE;
            goto Finish;
        }
        // 填充ACE
        ZeroMemory(&ea, NUM_ACES * sizeof(EXPLICIT_ACCESS));
        // S-1-1-0 evryone, 唯读权限
        ea[0].grfAccessPermissions = GENERIC_READ | GENERIC_WRITE;
        ea[0].grfAccessMode = SET_ACCESS;
        ea[0].grfInheritance = NO_INHERITANCE;
        ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
        ea[0].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
        ea[0].Trustee.ptstrName = (LPTSTR)pEveryoneSID;
        // S-1-3-0 creator owner, 完全权限
        ea[1].grfAccessPermissions = STANDARD_RIGHTS_ALL;
        ea[1].grfAccessMode = SET_ACCESS;
        ea[1].grfInheritance = NO_INHERITANCE;
        ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
        ea[1].Trustee.TrusteeType = TRUSTEE_IS_WELL_KNOWN_GROUP;
        ea[1].Trustee.ptstrName = (LPTSTR)pCreatorSID;
        // 创建并填充ACL
        if(NULL == m_fnpSetEntriesInAcl)
        {
            HINSTANCE hLib = ::LoadLibrary("Advapi32.dll");
            if(NULL != hLib)
            {
                m_fnpSetEntriesInAcl = (PSetEntriesInAcl)GetProcAddress(hLib, "SetEntriesInAclA");
                ::FreeLibrary(hLib);
                hLib = NULL;
            }
        }
        if(ERROR_SUCCESS != m_fnpSetEntriesInAcl(NUM_ACES, ea, NULL, &pFileMapACL))
        {
            bHasErr = TRUE;
            goto Finish;
        }
        // 创建并初始化SD
        pSD = (PSECURITY_DESCRIPTOR)LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
        if(NULL == pSD)
        {
            bHasErr = TRUE;
            goto Finish;
        }
        if(!InitializeSecurityDescriptor(pSD, SECURITY_DESCRIPTOR_REVISION))
        {
            bHasErr = TRUE;
            goto Finish;
        }
        // 添加ACL到SD中去
        if(!SetSecurityDescriptorDacl(pSD,
                                      TRUE,     // fDaclPresent flag
                                      pFileMapACL,
                                      FALSE))   // not a default DACL
        {
            bHasErr = TRUE;
            goto Finish;
        }
        // 设置SA
        saFileMap.nLength = sizeof(SECURITY_ATTRIBUTES);
        saFileMap.bInheritHandle = FALSE;
        saFileMap.lpSecurityDescriptor = pSD;
        // 创建共享内存文件
        if(m_hFile != NULL)
        {
            m_hFileMap = CreateFileMapping(
                             m_hFile,
                             &saFileMap,
                             PAGE_READWRITE,
                             0,
                             m_dwSize,
                             m_pMapName);
            if(NULL == m_hFileMap)
            {
                m_hFileMap = OpenFileMapping(FILE_MAP_READ | FILE_MAP_WRITE,
                                             TRUE, m_pMapName);
                if(NULL == m_hFileMap)
                {
                    m_dwLastError = GetLastError();
                    Destory();
                    goto Finish;
                }
            }
        }
Finish:
        //if (pSD != NULL)
        //{
        //  LocalFree(pSD);
        //}
        pSD = NULL;
        if(pFileMapACL != NULL)
        {
            LocalFree(pFileMapACL);
            pFileMapACL = NULL;
        }
        if(pEveryoneSID != NULL)
        {
            FreeSidEx(pEveryoneSID);
            pEveryoneSID = NULL;
        }
        if(pCreatorSID != NULL)
        {
            FreeSidEx(pCreatorSID);
            pCreatorSID = NULL;
        }
        if(bHasErr)
        {
            m_dwLastError = GetLastError();
            return FALSE;
        }
    }
    else
    {
        // 创建共享内存文件
        if(m_hFile)
        {
            m_hFileMap = CreateFileMapping(
                             m_hFile,
                             NULL,
                             PAGE_READWRITE,
                             0,
                             m_dwSize,
                             m_pMapName);
            if(NULL == m_hFileMap)
            {
                m_dwLastError = GetLastError();
                Destory();
                return FALSE;
            }
        }
    }
    // 映射文件指针到用户
    if(m_hFileMap)
    {
        m_lpFileMapBuffer = MapViewOfFile(
                                m_hFileMap,
                                FILE_MAP_ALL_ACCESS,
                                0,
                                0,
                                m_dwSize);
        if(NULL == m_lpFileMapBuffer)
        {
            m_dwLastError = GetLastError();
            Destory();
            return FALSE;
        }
        ZeroMemory(GetBuffer(), GetSize());
    }
    m_bCreateFlag = TRUE;
    return TRUE;
}
// 获取内存文件指针
LPVOID CFFMServer::GetBuffer()
{
    return (m_lpFileMapBuffer) ? (m_lpFileMapBuffer) : (NULL);
}
// 获取内存文件大小
DWORD CFFMServer::GetSize()
{
	//m_dwSize += 1000;
    return m_dwSize;
}
BOOL CFFMServer::WriteCmdData(DWORD nCommandCode, DWORD dwDataSize, const LPVOID pBuf)
{
    // 检验数据的合理性
    if(NULL == GetBuffer())
    {
        m_dwLastError = ERROR_NO_MAPFILE;
        SetLastError(ERROR_NO_MAPFILE);
        return FALSE;
    }
    if(NETRGUSER_CMD_NONE == nCommandCode)
    {
        m_dwLastError = ERROR_INVALID_CMDCODE;
        SetLastError(ERROR_INVALID_CMDCODE);
        return FALSE;
    }
    if(dwDataSize > 0 && pBuf == NULL)
    {
        m_dwLastError = ERROR_INVALID_USER_BUFFER;
        SetLastError(ERROR_INVALID_USER_BUFFER);
        return FALSE;
    }
	if (dwDataSize + sizeof(DATA_HEADER) > GetSize())
	{
		m_dwLastError = ERROR_BUFFER_OVERFLOW;
		SetLastError(ERROR_BUFFER_OVERFLOW);
		return FALSE;
	}
    // 填写数据结构
    // 文件头

    DATA_HEADER dataHeader;
    int size = sizeof(DATA_HEADER);	
    dataHeader.nCommandCode = nCommandCode;
    dataHeader.dwDataSize = dwDataSize;
	
	////计算时间
	//clock_t benin, end;
	//benin = clock();
	//char szTime[32];
	//long dTime = (long)benin;
	//ZeroMemory(szTime, 32);
	//sprintf(szTime, "%hd", dTime);
	//memcpy(dataHeader.dwReserved, szTime, 32);

	char szTime[64];
	ZeroMemory(szTime, 64);
	ElapsedTimer elapsed;
	elapsed.reset();
	int64_t timeStamp = elapsed.getTimeStamp();
	//int64_t elspsedTime = m_elspsedTimer.elapsedMs(); //毫秒
	 _i64toa_s(timeStamp, szTime, TIME_BUFFER_SIZE, 10);
	//sprintf(szTime, "%hd", timeStamp);
	memcpy(dataHeader.dwElapsed, szTime, 64);
    ZeroMemory(GetBuffer(), GetSize());
    memcpy(GetBuffer(), &dataHeader, sizeof(DATA_HEADER));
    // 数据块
    LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
    memcpy(pData->bInfo, pBuf, dwDataSize);
    pData->dwReserved[0] = 1;
	pData->dwReserved[1] = 1;
	pData->dwReserved[2] = 1;

	ElapsedTimer elapsedEnd;
	int64_t endCount = elapsedEnd.getTimeStamp();
	int64_t passTime = endCount - timeStamp;
	double dTime = static_cast<double>(passTime);

    return TRUE;
}
BOOL CFFMServer::CanWrite()
{
    LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
	bool ret = pData->dwReserved[0] == 0 && 
		pData->dwReserved[1] == 0 && 
		pData->dwReserved[2] == 0;
	 bool ret1 = pData->dwReserved[2] == 0;
    return ret;
}
BOOL CFFMServer::WriteOver()
{
    DATA_HEADER dataHeader;
    int size = sizeof(DATA_HEADER);
    dataHeader.nCommandCode = 1;
    dataHeader.dwDataSize = 2;
    ZeroMemory(GetBuffer(), GetSize());
    dataHeader.dwReserved[5] = 10;
    memcpy(GetBuffer(), &dataHeader, sizeof(DATA_HEADER));
    
    return true;
}



客户端

客户端头文件

#pragma once
/******************************************************************
    ++  File Name :   FFMClass.h
    ++  Description: 共享内存类
    ---------------------------------------------------------------
    ++  Author:  Fei ZhaoDong
    ++  Create time: 2004/3/25 上午 10:00:00
    ++  Version:     1.1
    ++  Modifier:
    ++  Activities:
    ++  Update List: 2004/3/30 下午 02:59:45
*******************************************************************/
// FFMClass.h: interface for the CFFMClass.
//
//
#ifndef FZD_FFMCLASS_H
#define FZD_FFMCLASS_H
//#include <Winbase.h>
#include <aclapi.h>

//
// Macro definition
// 以下为共享内存定义
#define DEFAULT_FILENAME NULL    // 默认的文件名
#define DEFAULT_MAPNAME  "Local//_FZD_MAP_"   // 默认的共享内存名
#define DEFAULT_MAPSIZE  (0xFFFF + 1)  // 默认的共享内存大小
const DWORD NETRGUSER_CFM_CODE = 0x1211DBFF; // 校验码, 用于命令数据
const DWORD NETRGUSER_CMD_NONE = 0;   // 初始化指令码, 无指令
// 以下为错误码定义
#define ERROR_LEN    256    // 错误描述长度
#define ERROR_INVALID_CMDCODE 0xE00001FF  // 已经存在完全一样的共享内存
#define ERROR_NO_MAPFILE             0xE00002FF  // 未分配共享内存文件
#define ERROR_INVALID_CFMCODE 0xE00003FF  // 校验码不匹配
//
// 内存文件格式定义
#pragma pack(1)
// 用于存储命令数据的内存文件格式
typedef struct _tagDATA_HEADER
{
    DWORD dwConfirmCode; // 校验码
    DWORD nCommandCode;  // 指令识别码
    DWORD dwDataSize;  // 数据的大小
    BYTE  dwReserved[19]; // 保留
    BYTE  bInfo[1];   // 数据起始地址
    _tagDATA_HEADER()
    {
        dwConfirmCode = NETRGUSER_CFM_CODE;
        nCommandCode = NETRGUSER_CMD_NONE;
        dwDataSize = 0;
        ZeroMemory(dwReserved, 19);
        ZeroMemory(bInfo, 1);
    }
} DATA_HEADER, *LPDATA_HEADER;
typedef DWORD(WINAPI* PSetEntriesInAcl)(ULONG, PEXPLICIT_ACCESS, PACL, PACL*);
// 用于存储应答数据的共享内存文件格式 (暂不用)
typedef struct _tagANS_HEADER
{
} ANS_HEADER, *LPANS_HEADER;

#pragma pack()


//
// 类定义,共享内存客户端
class CFFMClient
{
public:
    CFFMClient();
    virtual ~CFFMClient();
    CFFMClient(DWORD dwAccess, char* szMapName, DWORD dwSize);
protected:
    HANDLE m_hFileMap;   // 内存文件句柄
    LPVOID m_lpFileMapBuffer; // 内存文件指针
    char* m_pMapName;  // 内存文件名
    DWORD m_dwSize;   // 缓冲区大小
    BOOL m_bOpenFlag;  // 是否已经打开了一个内存文件
    DWORD   m_dwLastError;  // 错误代码
private:
    void _Init();    // 初始化参数
public:
    BOOL Open(DWORD dwAccess = FILE_MAP_READ | FILE_MAP_WRITE,
              char* szMapName = DEFAULT_MAPNAME,
              DWORD dwSize = 0);      // 打开一个内存文件
    LPVOID GetBuffer();       // 获取当前内存文件的指针
    void Destory();        // 关闭当前对内存文件的访问
    DWORD GetSize();      // 获取内存文件大小
    BOOL GetCmdDataSize(DWORD* pDataSize);  // 读取命令数据大小
    int ReadCmdData(       // 读取命令数据
        DWORD dwCommandCode,
        DWORD dwBufSize,
        LPVOID pOutBuf);
    BOOL WriteCmdData(      // 写入命令数据
        DWORD memSize,
        DWORD nCommandCode,
        DWORD dwDataSize,
        const LPVOID pBuf);
        
    BOOL CanRead();
    
    BOOL IsOver();
};
#endif // FZD_FFMCLASS_H

在这里插入代码片


客服端实现文件

/******************************************************************
    ++  File Name : FFMClass.cpp
    ++  Description: 共享内存类
    ---------------------------------------------------------------
    ++  Author:  Fei ZhaoDong
    ++  Create time: 2004/3/25 上午 10:00:00
    ++  Version:     1.0
    ++  Modifier:
    ++   Activities:
    ++  Update List: 2004/3/29 下午 02:59:45
*******************************************************************/
// FFMClass.cpp: implementation of the CFFMClass.
//
//
#include "FFMClient.h"
//#include <aclapi.h>
//#include <windows.h>
//#include <aclapi.h>
//#include <lmerr.h>
//
//#include <stdio.h>
//
// FFMClient
//

//
// CFFMClient
//

using namespace std;

#define  CLINET_FLAG 2

char SemaphoreName[50] = "lockShardMemory";



CFFMClient::CFFMClient()
{
    m_dwLastError = 0;
    _Init();
}
CFFMClient::~CFFMClient()
{
    Destory();
}
CFFMClient::CFFMClient(DWORD dwAccess, char* szMapName, DWORD dwSize)
{
    _Init();
    if(!Open(dwAccess, szMapName, dwSize))
    {
        Destory();
    }
}
// 初始化参数
void CFFMClient::_Init()
{
    m_hFileMap = NULL;
    m_lpFileMapBuffer = NULL;
    m_pMapName = NULL;
    m_bOpenFlag = FALSE;
}
// 关闭当前对内存文件的访问
void CFFMClient::Destory()
{
    if(m_lpFileMapBuffer)
    {
        UnmapViewOfFile(m_lpFileMapBuffer);
        m_lpFileMapBuffer = NULL;
    }
    if(m_hFileMap)
    {
        CloseHandle(m_hFileMap);
        m_hFileMap = NULL;
    }
    if(m_pMapName)
    {
        free(m_pMapName);
        m_pMapName = NULL;
    }
    _Init();
}
// 打开一个内存文件
BOOL CFFMClient::Open(DWORD dwAccess, char* szMapName, DWORD dwSize)
{
    if(m_bOpenFlag)
    {
        Destory();
    }
    if(szMapName != NULL)
    {
        m_pMapName = _strdup(szMapName);
    }
    else
    {
        m_pMapName = _strdup(DEFAULT_MAPNAME);
    }
    m_hFileMap = OpenFileMapping(dwAccess, TRUE, m_pMapName);
    if(NULL == m_hFileMap)
    {
        m_dwLastError = GetLastError();
        Destory();
        return FALSE;
    }
    m_lpFileMapBuffer = MapViewOfFile(m_hFileMap, dwAccess, 0, 0, dwSize);
    if(NULL == m_lpFileMapBuffer)
    {
        m_dwLastError = GetLastError();
        Destory();
        return FALSE;
    }
    m_bOpenFlag = TRUE;
    return TRUE;
}
// 获取当前内存文件的指针
LPVOID CFFMClient::GetBuffer()
{
    return (m_lpFileMapBuffer) ? (m_lpFileMapBuffer) : (NULL);
}
// 读取命令数据大小
BOOL CFFMClient::GetCmdDataSize(DWORD* pDataSize)
{
    if(pDataSize == NULL)
    {
        return FALSE;
    }
    *pDataSize = 0;
    LPDATA_HEADER pHeader = (LPDATA_HEADER)GetBuffer();
    if(NULL == pHeader)
    {
        m_dwLastError = ERROR_NO_MAPFILE;
        SetLastError(ERROR_NO_MAPFILE);
        return FALSE;
    }
    if(NETRGUSER_CFM_CODE != pHeader->dwConfirmCode)
    {
        m_dwLastError = ERROR_INVALID_CFMCODE;
        SetLastError(ERROR_INVALID_CFMCODE);
        return FALSE;
    }
    if(NETRGUSER_CMD_NONE == pHeader->nCommandCode)
    {
        m_dwLastError = ERROR_INVALID_CMDCODE;
        SetLastError(ERROR_INVALID_CMDCODE);
        return FALSE;
    }
    *pDataSize = pHeader->dwDataSize;
    return TRUE;
}
// 读取命令数据
int CFFMClient::ReadCmdData(DWORD dwCommandCode, DWORD dwBufSize, LPVOID pOutBuf)
{
    if(pOutBuf == NULL)
    {
        return FALSE;
    }
    
    
    LPDATA_HEADER pHeader = (LPDATA_HEADER)GetBuffer();
    if(NULL == pHeader)
    {
        m_dwLastError = ERROR_NO_MAPFILE;
        SetLastError(ERROR_NO_MAPFILE);
        return FALSE;
    }
    if(NETRGUSER_CFM_CODE != pHeader->dwConfirmCode)
    {
        m_dwLastError = ERROR_INVALID_CFMCODE;
        SetLastError(ERROR_INVALID_CFMCODE);
        return FALSE;
    }
    if(NETRGUSER_CMD_NONE == pHeader->nCommandCode)
    {
        m_dwLastError = ERROR_INVALID_CMDCODE;
        SetLastError(ERROR_INVALID_CMDCODE);
        return FALSE;
    }
    if(pHeader->dwDataSize > dwBufSize)
    {
        m_dwLastError = ERROR_BUFFER_OVERFLOW;
        SetLastError(ERROR_BUFFER_OVERFLOW);
        return FALSE;
    }
    if(pHeader->nCommandCode != dwCommandCode)
    {
        m_dwLastError = ERROR_INVALID_CMDCODE;
        SetLastError(ERROR_INVALID_CMDCODE);
        return FALSE;
    }
    ZeroMemory(pOutBuf, dwBufSize);
    // 拷贝数据到缓冲区
    memcpy(pOutBuf, pHeader->bInfo, pHeader->dwDataSize);
    int ret = pHeader->dwDataSize;
    pHeader->dwReserved[2] = 0;
    return ret;
}
BOOL CFFMClient::WriteCmdData(DWORD memSize, DWORD nCommandCode, DWORD dwDataSize, const LPVOID pBuf)
{
    if(!memSize)
    {
        memSize = DEFAULT_MAPSIZE;
    }
    m_dwSize = memSize;
    // 检验数据的合理性
    if(NULL == GetBuffer())
    {
        m_dwLastError = ERROR_NO_MAPFILE;
        SetLastError(ERROR_NO_MAPFILE);
        return FALSE;
    }
    if(NETRGUSER_CMD_NONE == nCommandCode)
    {
        m_dwLastError = ERROR_INVALID_CMDCODE;
        SetLastError(ERROR_INVALID_CMDCODE);
        return FALSE;
    }
    if(dwDataSize > 0 && pBuf == NULL)
    {
        m_dwLastError = ERROR_INVALID_USER_BUFFER;
        SetLastError(ERROR_INVALID_USER_BUFFER);
        return FALSE;
    }
    if(dwDataSize + sizeof(DATA_HEADER) > GetSize())
    {
        m_dwLastError = ERROR_BUFFER_OVERFLOW;
        SetLastError(ERROR_BUFFER_OVERFLOW);
        return FALSE;
    }
    // 填写数据结构
    // 文件头
    DATA_HEADER dataHeader;
    dataHeader.nCommandCode = nCommandCode;
    dataHeader.dwDataSize = dwDataSize;
    ZeroMemory(GetBuffer(), GetSize());
    memcpy(GetBuffer(), &dataHeader, sizeof(DATA_HEADER));
    // 数据块
    LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
    memcpy(pData->bInfo, pBuf, dwDataSize);
    return TRUE;
}
BOOL CFFMClient::CanRead()
{
    LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
	bool ret = pData->dwReserved[CLINET_FLAG] == 1;
    return ret;
}
BOOL CFFMClient::IsOver()
{
    LPDATA_HEADER pData = (LPDATA_HEADER)GetBuffer();
    return pData->dwReserved[5] == 10;
}
// 获取内存文件大小
DWORD CFFMClient::GetSize()
{
    return m_dwSize;
}
代码测试

服务端测试代码

// ServerCom.cpp : Defines the entry point for the console application.
//

#include <stdio.h>
#include <windows.h>
#include "FFMService.h"
#include <time.h>
#include <ctime>
#include <chrono>
#pragma endregion
#define MAP_PREFIX          "Local\\"
#define MAP_NAME            "SampleMap"
#define FULL_MAP_NAME       MAP_PREFIX MAP_NAME

// Max size of the file mapping object.
#define MAP_SIZE            201 << 20

// File offset where the view is to begin.
#define VIEW_OFFSET         0

// The number of bytes of a file mapping to map to the view. All bytes of the
// view must be within the maximum size of the file mapping object (MAP_SIZE).
// If VIEW_SIZE is 0, the mapping extends from the offset (VIEW_OFFSET) to
// the end of the file mapping.
#define VIEW_SIZE           1024

// Unicode string message to be written to the mapped view. Its size in byte
// must be less than the view size (VIEW_SIZE).
#define MESSAGE             L"Message from the first process."


int main_0(int argc, char* argv[])
{
	HANDLE hMapFile = NULL;
	PVOID pView = NULL;

	

	// Create the file mapping object.
	hMapFile = CreateFileMapping(
		INVALID_HANDLE_VALUE,   // Use paging file - shared memory
		NULL,                   // Default security attributes
		PAGE_READWRITE,         // Allow read and write access
		0,                      // High-order DWORD of file mapping max size
		MAP_SIZE,               // Low-order DWORD of file mapping max size
		FULL_MAP_NAME           // Name of the file mapping object
	);
	if (hMapFile == NULL)
	{
		wprintf(L"CreateFileMapping failed w/err 0x%08lx\n", GetLastError());
		goto Cleanup;
	}
	wprintf(L"The file mapping (%s) is created\n", FULL_MAP_NAME);

	// Map a view of the file mapping into the address space of the current
	// process.
	pView = MapViewOfFile(
		hMapFile,               // Handle of the map object
		FILE_MAP_ALL_ACCESS,    // Read and write access
		0,                      // High-order DWORD of the file offset
		VIEW_OFFSET,            // Low-order DWORD of the file offset
		VIEW_SIZE               // The number of bytes to map to view
	);
	if (pView == NULL)
	{
		wprintf(L"MapViewOfFile failed w/err 0x%08lx\n", GetLastError());
		goto Cleanup;
	}
	wprintf(L"The file view is mapped\n");

	// Prepare a message to be written to the view.
	PWSTR pszMessage = MESSAGE;
	DWORD cbMessage = (wcslen(pszMessage) + 1) * sizeof(*pszMessage);

	// Write the message to the view.
	memcpy_s(pView, VIEW_SIZE, pszMessage, cbMessage);

	wprintf(L"This message is written to the view:\n\"%s\"\n",
		pszMessage);

	// Wait to clean up resources and stop the process.
	wprintf(L"Press ENTER to clean up resources and quit");
	getchar();

Cleanup:

	if (hMapFile)
	{
		if (pView)
		{
			// Unmap the file view.
			UnmapViewOfFile(pView);
			pView = NULL;
		}
		// Close the file mapping object.
		CloseHandle(hMapFile);
		hMapFile = NULL;
	}

	return 0;
}


//开始时间
LARGE_INTEGER beginTickCount()
{
	LARGE_INTEGER frequency, startCount, stopCount;
	bool ret;
	//返回性能计数器每秒滴答的个数
	ret = QueryPerformanceFrequency(&frequency);
	if (ret) {
		ret = QueryPerformanceCounter(&startCount);
	}

	return startCount;
}

//计算经过时间时间
LONGLONG calcPassTimeMicrosecond(LARGE_INTEGER &startCount, LARGE_INTEGER &stopCount)
{
	bool ret;
	LARGE_INTEGER frequency;
	LONGLONG elapsed;
	//返回性能计数器每秒滴答的个数
	ret = QueryPerformanceFrequency(&frequency);

	if (ret) {
		elapsed = (stopCount.QuadPart - startCount.QuadPart) * 1000000 / frequency.QuadPart;
		cout << "QueryPerformanceFrequency & QueryPerformanceCounter =" << elapsed << "us" << endl;
	}

	return elapsed;
}

/**
 * QueryPerformanceCounter的精度为us级
 */
const unsigned long SLEEP_TIME_MILL = 10;
void calcByQueryPerformanceCounter() {
	LARGE_INTEGER frequency, startCount, stopCount;
	bool ret;
	//返回性能计数器每秒滴答的个数
	ret = QueryPerformanceFrequency(&frequency);
	if (ret) {
		ret = QueryPerformanceCounter(&startCount);
	}
	Sleep(SLEEP_TIME_MILL);
	if (ret) {
		QueryPerformanceCounter(&stopCount);
	}
	if (ret) {
		LONGLONG elapsed = (stopCount.QuadPart - startCount.QuadPart) * 1000000 / frequency.QuadPart;
		//cout << "QueryPerformanceFrequency & QueryPerformanceCounter =" << elapsed << "us";
	}
}


void running(int seconds)
{
	Sleep(seconds * 1000);
	cout << "Sleep for " << seconds << "秒" << endl;
}

int main()
{
	//double df = 1e17;
	CFFMServer* cff = new CFFMServer();
	cff->Create();
	char *aa = new char[200 << 20];
	char *bb = new char[200 << 20];
	for (int i = 0; i < (200 << 20); i++)
	{
		aa[i] = i;
	}

	int count = 1024;

	clock_t benin, end;

	unsigned int mm = 0;

	OutputDebugString("调试信息:\n");
	printf("开始写入共享内存数据.......\n");

	benin = clock();

	char szTime[64];
	double dTime = (double)benin;
	ZeroMemory(szTime, 64);
	sprintf(szTime, "%f", dTime);

	DWORD startTime = GetTickCount();//计时开始

	LARGE_INTEGER _frequency, _startCount, _stopCount;
	LONGLONG _elapsed;
	_startCount = beginTickCount();

	//auto tp1 = std::chrono::high_resolution_clock::now().time_since_epoch().count();
	//cout << "获取系统时间戳::std::chrono::high_resolution_clock::now():" << tp1 << "纳秒" << endl;

	//auto tp2 = std::chrono::system_clock::now().time_since_epoch().count();
	//cout << "获取系统时间戳:std::chrono::system_clock::now().time_since_epoch().count=" << tp2 << "100纳秒" << endl;

	//auto tp3 = std::chrono::steady_clock::now().time_since_epoch().count();
	//cout << "获取系统时间戳:std::chrono::steady_clock::now().time_since_epoch().count=" << tp3 << "纳秒" << endl;

	//auto tp4 = std::chrono::system_clock::now();
	//std::time_t tt = std::chrono::system_clock::to_time_t(tp4);    //微秒
	//std::cout << "seconds from 1970-01-01 00:00:00 UTC:" << tt << std::endl;  //应该不是从1970年以来的微秒数,而是从开机以来经过的微秒数

	////时间间隔
	//auto tp5 = std::chrono::steady_clock::now();
	//Sleep(100);
	//auto tp6 = std::chrono::steady_clock::now();
	//cout << "间隔时间微秒:" << std::chrono::duration_cast<std::chrono::microseconds>(tp6 - tp5).count() << "微秒" << endl;
	//cout << "间隔时间纳秒:" << std::chrono::duration_cast<std::chrono::nanoseconds>(tp6 - tp5).count() << "纳秒" << endl;

	//std::chrono::system_clock::time_point tt1;   //默认值为 1970-01-01 08:00:00 即0
	//std::chrono::system_clock::time_point tt2(std::chrono::seconds(2));
	//cout << "初始化:std::chrono::system_clock::time_point start_time2(std::chrono::seconds(2))="
	//	<< tt2.time_since_epoch().count() << "默认单位是微秒" << endl;

	//cout << "std::chrono::duration_cast<std::chrono::nanoseconds>(tt2.time_since_epoch()).count()="
	//	<< std::chrono::duration_cast<std::chrono::nanoseconds>(tt2.time_since_epoch()).count() << "纳秒" << endl;
	//cout << "std::chrono::duration_cast<std::chrono::microseconds>(tt2.time_since_epoch()).count()= "
	//	<< std::chrono::duration_cast<std::chrono::microseconds>(tt2.time_since_epoch()).count() << "微秒" << endl;
	//cout << "std::chrono::duration_cast<std::chrono::milliseconds>(tt2.time_since_epoch()).count() = "
	//	<< std::chrono::duration_cast<std::chrono::milliseconds>(tt2.time_since_epoch()).count() << "毫秒" << endl;
	//cout << "std::chrono::duration_cast<std::chrono::seconds>(tt2.time_since_epoch()).count()="
	//	<< std::chrono::duration_cast<std::chrono::seconds>(tt2.time_since_epoch()).count() << "秒" << endl;

	//cout << "当前时间戳:std::chrono::system_clock::now().time_since_epoch().count()精度是100纳秒" << std::chrono::system_clock::now().time_since_epoch().count() << endl;
	////测试时钟精度
	//cout << "system clock          : ";
	//cout << chrono::system_clock::period::num << "/" << chrono::system_clock::period::den << "s" << endl;
	//cout << "steady clock          : ";
	//cout << chrono::steady_clock::period::num << "/" << chrono::steady_clock::period::den << "s" << endl;
	//cout << "high resolution clock : ";
	//cout << chrono::high_resolution_clock::period::num << "/" << chrono::high_resolution_clock::period::den << "s" << endl;
	//

	//_XTIME_NSECS_PER_TICK          100纳秒
	//steady_clock high_resolution_clock

	//SYSTEM_INFO systemInfo;
	//GetSystemInfo(&systemInfo);
	//int cpuNumber = systemInfo.dwNumberOfProcessors;

	//SetThreadAffinityMask(GetCurrentThread(), 0x00000001); //线程运行在1号cpu
	LARGE_INTEGER startTimeCalc, endTimeCalc;
	LARGE_INTEGER freqCalc;

	QueryPerformanceFrequency(&freqCalc);
	QueryPerformanceCounter(&startTimeCalc);

	//cout << "start.QuarPart = " << startTimeCalc.QuadPart << endl;
	//running(1); //暂停1秒
	//QueryPerformanceCounter(&endTimeCalc);
	//cout << "end.QuarPart = " << endTimeCalc.QuadPart << endl;

	//std::cout << "consume value = end.QuadPart - start.QuadPart = " << (endTimeCalc.QuadPart - startTimeCalc.QuadPart) << std::endl;
	//std::cout << "(consume value/(double)freq.QuadPart) Time consumed = " << (endTimeCalc.QuadPart - startTimeCalc.QuadPart) / (double)freqCalc.QuadPart << "(s)" << std::endl;  //output consumed time  


	while (count > 0)
	{

		if (cff->CanWrite())
		{

			if (count == 1024 - 1)
			{
				benin = clock();
			}
			aa[0] = mm;
			mm++;
			cff->WriteCmdData(2, 200 << 20, aa);
			count--;
		}
		else
		{
			// Sleep(1);
		}

	}
	cff->WriteOver();

	_stopCount = beginTickCount();

	_elapsed = calcPassTimeMicrosecond(_startCount, _stopCount);
	cout << "服务端写入2G数据用时:" << _elapsed << "微秒" << endl;
	cout << "*******************" << endl;
	end = clock();

	printf("服务端写入2G数据用时:%f\n", (end - benin) / 1000.0);


	/*CRITICAL_SECTION cs;
	EnterCriticalSection(&cs);*/
	_startCount = beginTickCount();

	memcpy(bb, aa, (2 << 20));
	_stopCount = beginTickCount();
	_elapsed = calcPassTimeMicrosecond(_startCount, _stopCount);
	cout << "服务端memcpy写入200兆数据用时:" << _elapsed << "微秒" << endl;
	//LeaveCriticalSection(&cs);

	DWORD endTime = GetTickCount();//计时结束
	//printf("服务端写入2G数据用时:%f\n", (end - begin) / CLOCKS_PER_SEC);
	//printf("服务端写入2G数据用时:%f\n", (endTime - startTime));
	//cout << "服务端写入2G数据用时:" << endTime - startTime << "毫秒" << endl;

	fflush(stdout);

	std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<LONGLONG>(1000)));

	system("pause");

	return 0;
}
客户端测试代码
// ClientCom.cpp : Defines the entry point for the console application.
//

#include <stdio.h>
#include <windows.h>
#include <time.h>
#include "FFMClient.h"
#pragma endregion
#define MAP_PREFIX          "Local\\"
#define MAP_NAME            "SampleMap"
#define FULL_MAP_NAME       MAP_PREFIX MAP_NAME
#define random(a,b) (rand()%(b-a)+a)

// File offset where the view is to begin.
#define VIEW_OFFSET         0

// The number of bytes of a file mapping to map to the view. All bytes of the
// view must be within the maximum size of the file mapping object. If
// VIEW_SIZE is 0, the mapping extends from the offset (VIEW_OFFSET) to the
// end of the file mapping.
#define VIEW_SIZE           1024


int main_1(int argc, char* argv[])
{
    HANDLE hMapFile = NULL;
    PVOID pView = NULL;
    
    // Try to open the named file mapping identified by the map name.
    hMapFile = OpenFileMapping(
                   FILE_MAP_READ,          // Read access
                   FALSE,                  // Do not inherit the name
                   FULL_MAP_NAME           // File mapping name
               );
    if(hMapFile == NULL)
    {
        wprintf(L"OpenFileMapping failed w/err 0x%08lx\n", GetLastError());
        goto Cleanup;
    }
    wprintf(L"The file mapping (%s) is opened\n", FULL_MAP_NAME);
    
    // Map a view of the file mapping into the address space of the current
    // process.
    pView = MapViewOfFile(
                hMapFile,               // Handle of the map object
                FILE_MAP_READ,          // Read access
                0,                      // High-order DWORD of the file offset
                VIEW_OFFSET,            // Low-order DWORD of the file offset
                VIEW_SIZE               // The number of bytes to map to view
            );
    if(pView == NULL)
    {
        wprintf(L"MapViewOfFile failed w/err 0x%08lx\n", GetLastError());
        goto Cleanup;
    }
    wprintf(L"The file view is mapped\n");
    
    // Read and display the content in view.
    wprintf(L"Read from the file mapping:\n\"%s\"\n", (PWSTR)pView);
    
    // Wait to clean up resources and stop the process.
    wprintf(L"Press ENTER to clean up resources and quit");
    getchar();
    
Cleanup:

    if(hMapFile)
    {
        if(pView)
        {
            // Unmap the file view.
            UnmapViewOfFile(pView);
            pView = NULL;
        }
        // Close the file mapping object.
        CloseHandle(hMapFile);
        hMapFile = NULL;
    }
    
    return 0;
}


int main(int argc, char* argv[])
{
    CFFMClient*   ff = new CFFMClient();
    ff->Open();
    int cmd = 2;
    FILE* fp = fopen("./test_222.dat", "wb");
	char* buf = new char[201 << 20];
	long long int cccc = 0;
	int countxx = 0;

	clock_t begin, end;
	printf("客户端读取数据...\n");
	begin = clock();

    while(true)
    {
    
        if(ff->CanRead())
        {
            int ret = ff->ReadCmdData(cmd, 201 << 20, buf);
          int tt =   fwrite(buf, 1, ret, fp);
		  cccc+=tt;
		  countxx++;
        }
        else
        {
            // Sleep(1);
        }
        
        if(ff->IsOver())
        {
			end = clock();
			printf("%f\n", (end - begin) / 1000.0);
            break;
        }  
    }

	printf("服务端写入200兆数据用时:%f\n", (end - begin) / 1000.0);

	fflush(stdout);
    
    fclose(fp);
    
    
    system("pause");
    
    return 0;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值