孙鑫MFC第十七讲 进程间通信的方法:剪切板,管道,匿名管道,邮槽

有四种方法

1.剪贴板

 a.创建个ClipBoard的对话框应用程序,加两EditBox和两个Button发送接收。

 b.具体代码:

   发送端代码:

 if(OpenClipboard())

 {

 CString str;

 HANDLE hClip;

 char *pBuf;

 EmptyClipboard();

 GetDlgItemText(IDC_EDIT_SEND,str);

 hClip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);

 pBuf=(char*)GlobalLock(hClip);将句柄转换为指针!

 strcpy(pBuf,str);

 GlobalUnlock(hClip);

 SetClipboardData(CF_TEXT,hClip);

 CloseClipboard();

 }

    接收端代码:

 if(OpenClipboard())

 {

 if(IsClipboardFormatAvailable(CF_TEXT))

 {

  HANDLE hClip;

  char *pBuf;

  hClip=GetClipboardData(CF_TEXT);

  pBuf=(char*)GlobalLock(hClip);

  GlobalUnlock(hClip);

  SetDlgItemText(IDC_EDIT_RECV,pBuf);

  CloseClipboard();

 }

 }

 

2.匿名管道:只能在父子进程之间进行通信

 a.先建一个Parent的单文档应用程序,增加“创建管道”“读取数据”“写入数据”三个菜单

 b.增加成员变量HANDLE类型的hRead,hWrite,初始化变量,并在析构函数中释放句柄

 c.响应菜单代码:

void CParentView::OnPipeCreate()菜单“创建管道”代码

{

 // TOD Add your command handler code here

 SECURITY_ATTRIBUTES sa;

 sa.bInheritHandle=TRUE;

 sa.lpSecurityDescriptor=NULL;

 sa.nLength=sizeof(SECURITY_ATTRIBUTES);

 if(!CreatePipe(&hRead,&hWrite,&sa,0))

 {

 MessageBox("创建匿名管道失败!");

 return;

 }

 STARTUPINFO sui;

 PROCESS_INFORMATION pi;

 ZeroMemory(&sui,sizeof(STARTUPINFO));将数据清0!

 sui.cb=sizeof(STARTUPINFO);

 sui.dwFlags=STARTF_USESTDHANDLES;

 sui.hStdInput=hRead;

 sui.hStdOutput=hWrite;

 sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);

 

 if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL,

  TRUE,0,NULL,NULL,&sui,&pi))创建子进程

 {

 CloseHandle(hRead);

 CloseHandle(hWrite);关闭句柄,将内核对象的使用计数减少1,这样当操作系统发现内核对象的使用计数为0时,将清除内核对象。

 hRead=NULL;

 hWrite=NULL;

 MessageBox("创建子进程失败!");

 return;

 }

 else

 {

 CloseHandle(pi.hProcess);

 CloseHandle(pi.hThread);

 }

}void CParentView::OnPipeRead()菜单“读取数据”代码

{

 // TOD Add your command handler code here

 char buf[100];

 DWORD dwRead;

 if(!ReadFile(hRead,buf,100,&dwRead,NULL))

 {

 MessageBox("读取数据失败!");

 return;

 }

 MessageBox(buf);

}void CParentView::OnPipeWrite()菜单“写入数据”代码

{

 // TOD Add your command handler code here

 char buf[]="http://www.sunxin.org";

 DWORD dwWrite;

 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))

 {

 MessageBox("写入数据失败!");

 return;

 }

}

    d.再建一个Child的单文档,在View中增加两个成员hRead和hWrite.在OnInitialUpdate()中得到句柄的值。

void CChildView::OnInitialUpdate()

{

 CView::OnInitialUpdate();

 

 // TOD Add your specialized code here and/or call the base class

 hRead=GetStdHandle(STD_INPUT_HANDLE);注意这句代码!

 hWrite=GetStdHandle(STD_OUTPUT_HANDLE);

}    e.加菜单“读取数据”“写入数据”其代码如下:

void CChildView::OnPipeRead()

{

 // TOD Add your command handler code here

 char buf[100];

 DWORD dwRead;

 if(!ReadFile(hRead,buf,100,&dwRead,NULL))

 {

 MessageBox("读取数据失败!");

 return;

 }

 MessageBox(buf);

}void CChildView::OnPipeWrite()

{

 // TOD Add your command handler code here

 char buf[]="匿名管道测试程序";

 DWORD dwWrite;

 if(!WriteFile(hWrite,buf,strlen(buf)+1,&dwWrite,NULL))

 {

 MessageBox("写入数据失败!");

 return;

 }

}

 

3.命名管道:还可以跨网络通信,服务器只能在win2000和NT下运行!而客户端可以在95下运行。关键函数CreateNamedPipe

 a.先建一个NamedPipeSRV单文档应用程序,加菜单“创建管道”“读取数据”“写入数据”

 b.在View中增加Handle变量hPipe,注意在析构函数中释放它!

 c.响应菜单,创建命名管道

void CNamedPipeSrvView::OnPipeCreate()

{

 // TOD Add your command handler code here

 hPipe=CreateNamedPipe("\\\\.\\pipe\\MyPipe",

 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,

 0,1,1024,1024,0,NULL);

 if(INVALID_HANDLE_VALUE==hPipe)

 {

 MessageBox("创建命名管道失败!");

 hPipe=NULL;

 return;

 }

 HANDLE hEvent;

 hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);

 if(!hEvent)

 {

 MessageBox("创建事件对象失败!");

 CloseHandle(hPipe);

 hPipe=NULL;

 return;

 }

 OVERLAPPED ovlap;

 ZeroMemory(&ovlap,sizeof(OVERLAPPED));

 ovlap.hEvent=hEvent;

 if(!ConnectNamedPipe(hPipe,&ovlap))

 {

 if(ERROR_IO_PENDING!=GetLastError())

 {

  MessageBox("等待客户端连接失败!");

  CloseHandle(hPipe);

  CloseHandle(hEvent);

  hPipe=NULL;

  return;

 }

 }

 if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE))

 {

 MessageBox("等待对象失败!");

 CloseHandle(hPipe);

 CloseHandle(hEvent);

 hPipe=NULL;

 return;

 }

 CloseHandle(hEvent);

}void CNamedPipeSrvView::OnPipeRead()

{

 // TOD Add your command handler code here

 char buf[100];

 DWORD dwRead;

 if(!ReadFile(hPipe,buf,100,&dwRead,NULL))

 {

 MessageBox("读取数据失败!");

 return;

 }

 MessageBox(buf);

}void CNamedPipeSrvView::OnPipeWrite()

{

 // TOD Add your command handler code here

 char buf[]="http://www.sunxin.org";

 DWORD dwWrite;

 if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))

 {

 MessageBox("写入数据失败!");

 return;

 }

}     d.再建一个NamedPipeCLT单文档工程,加菜单“连接管道”“读取数据”“写入数据”,当然别忘记成员变量hPipe的定义和初始化

     e.响应菜单代码

void CNamedPipeCltView::OnPipeConnect()连接管道

{

 // TOD Add your command handler code here

 if(!WaitNamedPipe("\\\\.\\pipe\\MyPipe",NMPWAIT_WAIT_FOREVER))

 {

 MessageBox("当前没有可利用的命名管道实例!");

 return;

 }

 hPipe=CreateFile("\\\\.\\pipe\\MyPipe",GENERIC_READ | GENERIC_WRITE,

 0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

 if(INVALID_HANDLE_VALUE==hPipe)

 {

 MessageBox("打开命名管道失败!");

 hPipe=NULL;

 return;

 }

}void CNamedPipeCltView::OnPipeRead()读取数据

{

 // TOD Add your command handler code here

 char buf[100];

 DWORD dwRead;

 if(!ReadFile(hPipe,buf,100,&dwRead,NULL))

 {

 MessageBox("读取数据失败!");

 return;

 }

 MessageBox(buf);

}void CNamedPipeCltView::OnPipeWrite()写入数据

{

 // TOD Add your command handler code here

 char buf[]="命名管道测试程序";

 DWORD dwWrite;

 if(!WriteFile(hPipe,buf,strlen(buf)+1,&dwWrite,NULL))

 {

 MessageBox("写入数据失败!");

 return;

 }

}

 

4.邮槽,使用时应将消息长度限制在424字节以下,关键函数CreateMailSlot()

 a.先建一个MailSlotSRV工程,加菜单“接收数据”

 b.消息响应代码:

void CMailslotSrvView::OnMailslotRecv()菜单“接收数据”的代码

{

 // TOD Add your command handler code here

 HANDLE hMailslot;

 hMailslot=CreateMailslot("\\\\.\\mailslot\\MyMailslot",0,

 MAILSLOT_WAIT_FOREVER,NULL);

 if(INVALID_HANDLE_VALUE==hMailslot)

 {

 MessageBox("创建油槽失败!");

 return;

 }

 char buf[100];

 DWORD dwRead;

 if(!ReadFile(hMailslot,buf,100,&dwRead,NULL))

 {

 MessageBox("读取数据失败!");

 CloseHandle(hMailslot);

 return;

 }

 MessageBox(buf);

 CloseHandle(hMailslot);

}

   c.加工程MailSlotCLT,加菜单“发送数据”

   d.加消息响应,添加代码,客户端也比较简单。

void CMailslotCltView::OnMailslotSend()菜单“发送数据”的代码

{

 // TOD Add your command handler code here

 HANDLE hMailslot;

 hMailslot=CreateFile("\\\\.\\mailslot\\MyMailslot",GENERIC_WRITE,

 FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

 if(INVALID_HANDLE_VALUE==hMailslot)

 {

 MessageBox("打开油槽失败!");

 return;

 }

 char buf[]="http://www.sunxin.org";

 DWORD dwWrite;

 if(!WriteFile(hMailslot,buf,strlen(buf)+1,&dwWrite,NULL))

 {

 MessageBox("写入数据失败!");

 CloseHandle(hMailslot);

 return;

 }

 CloseHandle(hMailslot);

}

 

 

5.以上4种方法各有优缺点:剪贴板比较简单。邮槽是基于广播的,可以一对多发送。但只能一个发送,一个接收,要想同时发送接收,须写两次代码。

命名管道和邮槽可以进行网络通信。

 

剪切板

[cpp]  view plain copy
  1. void CJieQieBanDlg::OnSend()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.   
  5.     if(OpenClipboard())//首先要打开剪切板  
  6.     {  
  7.         CString str;  
  8.         HANDLE hChip;  
  9.         char *pBuf;  
  10.   
  11.         EmptyClipboard();//清空剪切板  
  12.         GetDlgItemText(ID_FASONG,str);//获取发送数据的内容  
  13.         hChip=GlobalAlloc(GMEM_MOVEABLE,str.GetLength()+1);//分配空间,返回一个句柄  
  14.   
  15.         pBuf=(char*)GlobalLock(hChip);//上锁,返回一个字符指针  
  16.         strcpy(pBuf,str);//复制内容  
  17.         GlobalUnlock(hChip);//解锁  
  18.   
  19.         SetClipboardData(CF_TEXT,hChip);//设置剪切板的内容  
  20.   
  21.         CloseClipboard();  
  22.     }  
  23.       
  24. }  


 

[cpp]  view plain copy
  1. void CJieQieBanDlg::OnRecv()   
  2. {  
  3.     // TODO: Add your control notification handler code here  
  4.       
  5.     if(OpenClipboard())//打开剪切板  
  6.     {  
  7.         if(IsClipboardFormatAvailable(CF_TEXT))//判断剪切板的内容格式是否是我们需要的  
  8.         {  
  9.             HANDLE hChip;  
  10.             char *pBuf;  
  11.             hChip=GetClipboardData(CF_TEXT);//返回一个句柄  
  12.             pBuf=(char*)GlobalLock(hChip);//获取内容  
  13.             GlobalUnlock(hChip);  
  14.             SetDlgItemText(ID_JIESHOU,pBuf);//设置内容  
  15.             CloseHandle(hChip);  
  16.   
  17.   
  18.         }  
  19.     }  
  20.   
  21. }  


 

匿名管道:

1父进程:

a添加两个CXXView成员变量HANDLE hRead,HANDLE hWrite

b构造函数赋值hRead=NULL,hWrite=NULL,析构函数if(!hRead) CloseHandle(hRead)...

c

[cpp]  view plain copy
  1. void CParentView::OnPipeCreate()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     SECURITY_ATTRIBUTES  sa;  
  6.     sa.bInheritHandle=TRUE;  
  7.     sa.lpSecurityDescriptor=NULL;  
  8.     sa.nLength=sizeof(SECURITY_ATTRIBUTES);  
  9.   
  10.     if(!CreatePipe(&hRead,&hWrite,&sa,0))  
  11.     {  
  12.         MessageBox("创建管道失败");  
  13.         return ;  
  14.     }  
  15.   
  16.     PROCESS_INFORMATION  pi;  
  17.       
  18.     STARTUPINFO sui;  
  19.     ZeroMemory(&sui,sizeof(STARTUPINFO));//这一句很重要,将数据清零  
  20.     sui.cb=sizeof(STARTUPINFO);  
  21.     sui.dwFlags=STARTF_USESTDHANDLES;  
  22.     sui.hStdInput=hRead;  
  23.     sui.hStdOutput=hWrite;  
  24.     sui.hStdError=GetStdHandle(STD_ERROR_HANDLE);  
  25.   
  26.     if(!CreateProcess("..\\Child\\Debug\\Child.exe",NULL,NULL,NULL,TRUE,0,NULL,NULL,&sui,&pi))  
  27.     {  
  28.         CloseHandle(hWrite);  
  29.         CloseHandle(hRead);  
  30.         hRead=NULL;  
  31.         hWrite=NULL;  
  32.         MessageBox("子进程创建失败");  
  33.         return ;  
  34.     }  
  35.     else  
  36.     {  
  37.         CloseHandle(pi.hProcess);  
  38.         CloseHandle(pi.hThread);  
  39.     }  
  40.   
  41.   
  42.       
  43. }  



d

[cpp]  view plain copy
  1. void CParentView::OnXieru()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[]="hello,i am writing...";  
  6.       
  7.     unsigned long len;  
  8.     if(!WriteFile(hWrite,temp,strlen(temp)+1,&len,NULL))  
  9.     {  
  10.         MessageBox("写入失败");  
  11.         return ;  
  12.     }  
  13.   
  14.   
  15.       
  16. }  


e

[cpp]  view plain copy
  1. void CParentView::OnDuqu()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[100];  
  6.     unsigned long len;  
  7.   
  8.     if(!ReadFile(hRead,temp,100,&len,NULL))  
  9.     {  
  10.         MessageBox("读取失败");  
  11.         return ;  
  12.     }  
  13.   
  14.     MessageBox(temp);  
  15.   
  16.       
  17. }  


子进程
a,CXXVIEW中添加成员变量HANDLE hRead,HANDLE hWrite,在构造函数和析构函数进行响应的操作

bCXXView中添加虚函数OnInitialUpdate

[cpp]  view plain copy
  1. void CChildView::OnInitialUpdate()   
  2. {  
  3.     CView::OnInitialUpdate();  
  4.       
  5.     // TODO: Add your specialized code here and/or call the base class  
  6.   
  7.     hRead=GetStdHandle(STD_INPUT_HANDLE);  
  8.     hWrite=GetStdHandle(STD_OUTPUT_HANDLE);  
  9.       
  10. }  


 

[cpp]  view plain copy
  1. void CChildView::OnXieru()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[]="hello,wel to my blog,i am afei";  
  6.     unsigned long len;  
  7.     if(!WriteFile(hWrite,temp,strlen(temp)+1,&len,NULL))  
  8.     {  
  9.         MessageBox("写入数据失败");  
  10.         return ;  
  11.     }  
  12.       
  13. }  


 

[cpp]  view plain copy
  1. void CChildView::OnDuqu()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[100];  
  6.     unsigned long len;  
  7.   
  8.     if(!ReadFile(hRead,temp,100,&len,NULL))  
  9.     {  
  10.         MessageBox("读取数据失败");  
  11.         return ;  
  12.     }  
  13.     MessageBox(temp);  
  14.       
  15. }  


先启动父进程,然后通过父进程建立子进程,然后就能实现父子进程之间的通信了。

 

命名管道:(可跨网络)

1服务器端:

a,CXXView中添加成员变量HANDLE hPipe

b,

[cpp]  view plain copy
  1. void CNamePipeSerView::OnPipeCreate()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.       
  5.     hPipe=CreateNamedPipe("\\\\.\\pipe\\mypipe",PIPE_ACCESS_DUPLEX|FILE_FLAG_OVERLAPPED,  
  6.                 0,1,1024,1024,0,NULL);//要转义  
  7.   
  8.     if(INVALID_HANDLE_VALUE==hPipe)  
  9.     {  
  10.         MessageBox("命名管道创建失败");  
  11.         hPipe=NULL;  
  12.         return ;  
  13.     }  
  14.   
  15.     HANDLE hEvent=CreateEvent(NULL,TRUE,FALSE,NULL);  
  16.     if(!hEvent)  
  17.     {  
  18.         MessageBox("对象事件创建失败");  
  19.         return ;  
  20.     }  
  21.   
  22.     OVERLAPPED oa;  
  23.     ZeroMemory(&oa,sizeof(OVERLAPPED));  
  24.     oa.hEvent=hEvent;  
  25.     if(!ConnectNamedPipe(hPipe,&oa))  
  26.     {  
  27.         if(ERROR_IO_PENDING!=GetLastError())  
  28.         {  
  29.   
  30.           
  31.         MessageBox("命名管道连接失败");  
  32.         CloseHandle(hEvent);  
  33.         CloseHandle(hPipe);  
  34.         hEvent=NULL;  
  35.         hPipe=NULL;  
  36.   
  37.         return ;  
  38.         }  
  39.     }  
  40.   
  41.     if(WAIT_FAILED==WaitForSingleObject(hEvent,INFINITE))  
  42.     {  
  43.         MessageBox("对象等待失败");  
  44.         CloseHandle(hEvent);  
  45.         CloseHandle(hPipe);  
  46.         hEvent=NULL;  
  47.         hPipe=NULL;  
  48.         return ;  
  49.     }  
  50.   
  51. }  


 

[cpp]  view plain copy
  1. void CNamePipeSerView::OnXieru()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[]="hello,i am afei";  
  6.       
  7.     unsigned long len;  
  8.     if(!WriteFile(hPipe,temp,strlen(temp)+1,&len,NULL))  
  9.     {  
  10.         MessageBox("写入文件失败");  
  11.         return ;  
  12.     }  
  13.   
  14.       
  15. }  


 

[cpp]  view plain copy
  1. void CNamePipeSerView::OnDuqu()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[100];  
  6.       
  7.     unsigned long len;  
  8.     if(!ReadFile(hPipe,temp,100,&len,NULL))  
  9.     {  
  10.         MessageBox("读取文件失败");  
  11.         return ;  
  12.     }  
  13.       
  14.     MessageBox(temp);  
  15.       
  16. }  


客户端:

[cpp]  view plain copy
  1. void CNamePipeCliView::OnLianjie()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     if(!WaitNamedPipe("\\\\.\\pipe\\mypipe",NMPWAIT_WAIT_FOREVER))  
  6.     {  
  7.         MessageBox("没有可用的命名管道实例");  
  8.         return ;  
  9.     }  
  10.   
  11.     hPipe=CreateFile("\\\\.\\pipe\\mypipe",GENERIC_READ|GENERIC_WRITE,0,NULL,  
  12.         OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);  
  13.   
  14.     if(INVALID_HANDLE_VALUE==hPipe)  
  15.     {  
  16.         MessageBox("打开管道失败");  
  17.         hPipe=NULL;  
  18.         return ;  
  19.     }  
  20.   
  21.   
  22.       
  23. }  


 

[cpp]  view plain copy
  1. void CNamePipeCliView::OnXieru()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.       
  5.     char temp[]="i love C++,MFC...";  
  6.     unsigned long len;  
  7.     if(!WriteFile(hPipe,temp,strlen(temp)+1,&len,NULL))  
  8.     {  
  9.   
  10.         MessageBox("写入文件失败");  
  11.   
  12.         return ;  
  13.     }  
  14.   
  15.       
  16. }  


 

[cpp]  view plain copy
  1. void CNamePipeCliView::OnDuqu()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     char temp[100];  
  6.   
  7.     unsigned long len;  
  8.     if(!ReadFile(hPipe,temp,100,&len,NULL))  
  9.     {  
  10.         MessageBox("读取文件失败");  
  11.         return ;  
  12.     }  
  13.   
  14.     MessageBox(temp);  
  15.       
  16. }  


这样就可以实现两个进程之间的通信了,

 

 

邮槽:(一对多)

 

 1服务器端(接收数据)

[cpp]  view plain copy
  1. void CYouCaoSerView::OnJieshou()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     HANDLE hMail;  
  6.     hMail=CreateMailslot("\\\\.\\mailslot\\mymail",0,MAILSLOT_WAIT_FOREVER,NULL);  
  7.   
  8.     if(INVALID_HANDLE_VALUE==hMail)  
  9.     {  
  10.         MessageBox("创建油槽失败");  
  11.         return ;  
  12.     }  
  13.   
  14.     char temp[200];  
  15.     DWORD dwlen;  
  16.     if(!ReadFile(hMail,temp,200,&dwlen,NULL))  
  17.     {  
  18.         CloseHandle(hMail);  
  19.         MessageBox("读取失败");  
  20.         return ;  
  21.     }  
  22.     CloseHandle(hMail);  
  23.     MessageBox(temp);  
  24.   
  25.       
  26. }  


2客户端:(发送数据)

 

[cpp]  view plain copy
  1. void CYouCaoCView::OnFasong()   
  2. {  
  3.     // TODO: Add your command handler code here  
  4.   
  5.     HANDLE hMail;  
  6.     hMail=CreateFile("\\\\.\\mailslot\\mymail",GENERIC_WRITE,FILE_SHARE_READ,NULL,  
  7.                     OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);  
  8.   
  9.     if(INVALID_HANDLE_VALUE==hMail)  
  10.     {  
  11.         MessageBox("打开油槽失败");  
  12.         return ;  
  13.     }  
  14.   
  15.     char temp[]="hello,welcome to my blog,i am afei";  
  16.     DWORD dwlen;  
  17.     if(!WriteFile(hMail,temp,strlen(temp)+1,&dwlen,NULL))  
  18.     {  
  19.         CloseHandle(hMail);  
  20.         MessageBox("写入数据失败");  
  21.         return ;  
  22.     }  
  23.   
  24.     CloseHandle(hMail);  
  25.   
  26.       
  27. }  


要先打开服务器端的接收消息,这样客户端的发送消息才能打开油槽

 

要实现可以发送又可以接收的油槽运用程序,可以在同一个程序中,既编写客户端又编写服务器

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值