一个对Winsock 完成端口模型封装的类

iocp完成端口的C++封装类,用起来很方便.zip CompletionPort(完成端口)的C++封装类,用起来很方便.zip 立即下载

源代码说明: 
在WINDOWS下进行网络服务端程序开发,毫无疑问,Winsock 完成端口模型是最高效的。Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握Winsock完成端口模型,需要对WINDOWS下的线程、线程同步,Winsock API以及WINDOWS IO机制有一定的了解。如果不了解,推荐几本书:《Inside Windows 2000,《WINDOWS核心编程》,《WIN32多线程程序设计》、《WINDOWS网络编程技术》。在去年,我在C语言下用完成端口模型写了一个WEBSERVER,前些天,我决定用C++重写这个WEBSERVER,给这个WEBSERVER增加了一些功能,并改进完成端口操作方法,比如采用AcceptEx来代替accept和使用LOOKASIDE LIST来管理内存,使得WEBSERVER的性能有了比较大的提高。

在重写的开始,我决定把完成端口模型封装成一个比较通用的C++类,针对各种网络服务端程序的开发,只要简单地继承这个类,改写其中两个虚拟函数就能满足各种需要。到昨天为止,WEBSERVER重写完毕,我就写了这篇文章对完成端口模型做一个总结,并介绍一下我的这个类。

DEMO就是一个ECHOSERVER,记得使用Release模式编译。

 

/*++

Copyright (c) 2004 

模块名: 

     iomodel.h

模块描述:

     Winsock 完成端口类头文件

作者:

     PPP elssann@hotmail.com

开发环境:
    
     Visual C++ 6.0, Windows 2000.
     
修订记录:

     创建于: 2004.1.16

最后修改日期:
      2004.1.23


--
*/


#ifndef      _IOMODEL_H
#define      _IOMODEL_H

//
//Head files
//
#include <winsock2.h>
#include 
<mswsock.h>


/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////

#ifdef __cplusplus
extern "C" {
#endif

        
#define    BUFFER_SIZE           4096
#define    MAXTHREAD_COUNT       8

#define    PORT                  8080
#define    LOCALADDRESS          "172.29.90.96"

#define    IO_READ_COMPLETION    100
#define    IO_WRITE_COMPLETION   200
#define    IO_ACCEPT_COMPLETION  300


//
//自定义枚举数据类型,用来标识套接字IO动作类型
//
typedef enum _IO_OPERATION 
{
        IoAccept, 
//AcceptEx/accept
  IoRead,   //WSARecv/recv/ReadFile
  IoWrite,   //WSASend/send/WriteFile
  IoEnd
}IO_OPERATION, 
*PIO_OPERATION;


//
//自定义结构,即“完成键”(单句柄数据)
//
typedef struct _PER_HANDLE_CONTEXT
{
 SOCKET                     IoSocket;

 _PER_HANDLE_CONTEXT
*       pNext;
}PER_HANDLE_CONTEXT, 
*PPER_HANDLE_CONTEXT;


//
//单IO数据,扩展的WSAOVERLAPPED
//
typedef struct _PER_IO_CONTEXT
{
 WSAOVERLAPPED              ol;
 
char                       szBuffer[BUFFER_SIZE];
 WSABUF                     wsaBuffer;
 SOCKET                     sClient;

 unsigned 
int               unId;

 IO_OPERATION               IoOperation;

 _PER_IO_CONTEXT
*           pNext; 
}PER_IO_CONTEXT, 
*PPER_IO_CONTEXT;


//
// global var
//

static  GUID g_GUIDAcceptEx = WSAID_ACCEPTEX;
static  GUID g_GUIDTransmitFile = WSAID_TRANSMITFILE;

DWORD __stdcall   CompletionRoutine(LPVOID);


//
//完成端口模型类
//


class CompletionPortModel
{
public:
 CompletionPortModel();
 
~CompletionPortModel();

 BOOL                Init();
 BOOL                ThreadLoop();
 BOOL                AllocEventMessage();
 BOOL                PostAcceptEx();
 
 
virtual BOOL        HandleData(
         PPER_IO_CONTEXT lpPerIoContext, 
            
int nFlags
         );
 
virtual BOOL        DataAction(
            PPER_IO_CONTEXT lpPerIoContext,
            PPER_HANDLE_CONTEXT lpNewperHandletext
            );
 
 
void                InsertNode(PPER_IO_CONTEXT pNode, PPER_HANDLE_CONTEXT pHandleNode);
 
void                ReleaseNode(PPER_IO_CONTEXT pNode);
 
void                InsertToLookaside(PPER_IO_CONTEXT lpIoNode, PPER_HANDLE_CONTEXT lpHandleNode);

 PPER_IO_CONTEXT     GetIoFromLookaside();
 PPER_HANDLE_CONTEXT GetHandleFromLookaside();


 


 HANDLE                        m_hCOP;
 SOCKET                        m_ListenSocket;
 
 CRITICAL_SECTION              m_ListCriSection;
 CRITICAL_SECTION              m_HandleCriSection;
 CRITICAL_SECTION              m_IoCriSection;

 LPFN_TRANSMITFILE             lpTransmitFile;

 
volatile PPER_IO_CONTEXT      m_lpIoLookasideLists;
 
volatile PPER_HANDLE_CONTEXT  m_lpHandleLOOKasideLists;
 
protected:
 BOOL                InitWinsock();
 BOOL                BindAndListenSocket();
 BOOL                InitLinkListHead();
 
void                CloseThreadHandle();
 
void                GetAddressAndPort();


 
 UINT                uPort;
 
char                szAddress[20];
 
 HANDLE                        m_hThreadArray[MAXTHREAD_COUNT];
 HANDLE                        m_hEvent;
 
 
volatile LONG                 m_lAcceptExCounter;
 
volatile PPER_IO_CONTEXT      m_lpConnectionListHead;

 LPFN_ACCEPTEX                 lpAcceptEx;

private:

};


#ifdef __cplusplus
}
#endif

#endif  //_IOMODEL_H

================================================================================


 

 

/*++

Copyright (c) 2004 

模块名: 

     iomodel.cpp

模块描述:

     Winsock 完成端口类实现文件

作者:

     PPP elssann@hotmail.com

开发环境:
    
     Visual C++ 6.0, Windows 2000.
     
修订记录:

     创建于: 2004.1.16

最后修改日期:
      2004.1.23


--
*/


#include 
<iostream.h>
#include 
<winsock2.h>
#include 
<mswsock.h>
#include 
"iomodel.h"


CompletionPortModel::CompletionPortModel()
/*++

函数描述:
    构造函数,初始化线程句柄数组,初始化AcceptEx()调用的计数。初始化临界段代码变量。

Arguments:
     无。

Return Value:
     无。

--
*/


{
 
for (int i=0; i< MAXTHREAD_COUNT; i++)
 
{
  m_hThreadArray[i] 
= INVALID_HANDLE_VALUE;
 }


 m_lAcceptExCounter 
= 0;

 InitializeCriticalSection(
&m_ListCriSection); 
 InitializeCriticalSection(
&m_HandleCriSection);
 InitializeCriticalSection(
&m_IoCriSection);

 m_lpHandleLOOKasideLists 
= NULL;
 m_lpIoLookasideLists 
= NULL;

#ifndef _DEBUG
 GetAddressAndPort();
#endif
}
//end of CompletionPortModel()


CompletionPortModel::
~CompletionPortModel()
/*++

函数描述:
    析构函数,释放链表所有结点。

Arguments:
     无。

Return Value:

--
*/

{
 PPER_IO_CONTEXT lpIoNode;

 
while (m_lpConnectionListHead->pNext)
 
{
  lpIoNode 
= m_lpConnectionListHead->pNext;
  m_lpConnectionListHead
->pNext = lpIoNode->pNext;
  closesocket(lpIoNode
->sClient);
  HeapFree(GetProcessHeap(), 
0, lpIoNode);
 }


 
while(NULL != m_lpIoLookasideLists)
 
{
  lpIoNode 
= m_lpIoLookasideLists;
  m_lpIoLookasideLists 
= m_lpIoLookasideLists->pNext;
  HeapFree(GetProcessHeap(), 
0, lpIoNode);
 }


 PPER_HANDLE_CONTEXT lpHandleNode;
 
while(NULL != m_lpHandleLOOKasideLists)
 
{
  lpHandleNode 
= m_lpHandleLOOKasideLists;
  m_lpHandleLOOKasideLists 
= m_lpHandleLOOKasideLists->pNext;
  HeapFree(GetProcessHeap(), 
0, lpHandleNode);
 }


 DeleteCriticalSection(
&m_ListCriSection); 
 DeleteCriticalSection(
&m_HandleCriSection);
 DeleteCriticalSection(
&m_IoCriSection);
 
}
//end of ~CompletionPortModel()



BOOL CompletionPortModel::Init()
/*++

函数描述:
    初始化,创建完成端口、创建完成端口线程,并调用类成员函数InitWinsock初始化Winsock、
建立一个监听套接字m_ListenSocket,并将此套接字同完成端口关联起来,获取AcceptEx指针。

Arguments:
     无。

Return Value:
     函数调用成功返回TRUE,失败返回FALSE。

--
*/

{
    BOOL bSuccess 
= InitLinkListHead();
 
 
if (FALSE == bSuccess)
 
{
  
return FALSE;
 }

 
 m_hCOP 
= CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
 
if (NULL == m_hCOP)
 
{
  cout 
<< "CreateIoCompletionPort() failed: " << GetLastError() << endl;

  
return FALSE;
 }


 
//
 
//取得系统中CPU的数目,创建和CPU数目相等的线程,如果事先估计到完成端口处理线程会堵塞,
 
//可以考虑创建 SysInfo.dwNumberOfProcessors*2个线程。一般在单处理器上创建和CPU数目相等
 
//的线程就可以了
 
//
 SYSTEM_INFO SysInfo;
 GetSystemInfo(
&SysInfo);
 
if (MAXTHREAD_COUNT < SysInfo.dwNumberOfProcessors)
 
{
  SysInfo.dwNumberOfProcessors 
= MAXTHREAD_COUNT;
 }


 
for (int i=0; i<(int)SysInfo.dwNumberOfProcessors; i++)
 
{
  m_hThreadArray[i] 
= CreateThread(NULL, 0, CompletionRoutine, (LPVOID)this, 0, NULL);
  
if (NULL == m_hThreadArray[i])
  
{
   
while (i>0)
   
{
    CloseHandle(m_hThreadArray[i
-1]);
    m_hThreadArray[i
-1] = INVALID_HANDLE_VALUE;
    i
--;
   }
//end of while

   cout 
<< "CreateThread() failed: " << GetLastError() << endl;
   CloseHandle(m_hCOP);
   HeapFree(GetProcessHeap(), 
0, m_lpConnectionListHead);
   
   
return FALSE;
  }

 }
//end of for

 
//
 
//调用InitWinsock函数初始化Winsock、建立一个监听套接字m_ListenSocket,
 
//并将此套接字同完成端口关联起来,获取AcceptEx指针。
 
//
 bSuccess = InitWinsock();
 
if (!bSuccess)
 
{
  
//
  
//给完成端口线程发送消息,指示线程退出。
  
//
  PostQueuedCompletionStatus(m_hCOP, 0, NULL, NULL);
  CloseThreadHandle();
  CloseHandle(m_hCOP);
  HeapFree(GetProcessHeap(), 
0, m_lpConnectionListHead); 

  
return FALSE;
 }


 
//
 
//调用BindAndListenSocket()绑定套接字并将套接字置于监听状态
 
//
 bSuccess = BindAndListenSocket(); 
 
if (!bSuccess)
 
{
  PostQueuedCompletionStatus(m_hCOP, 
0, NULL, NULL);
  CloseThreadHandle();
  CloseHandle(m_hCOP);
  HeapFree(GetProcessHeap(), 
0, m_lpConnectionListHead);  
  
  
return FALSE;
 }


 
return TRUE; 
}
//end of Init()


void CompletionPortModel::CloseThreadHandle()
/*++

函数描述:
    对每一个创建的线程调用CloseHandle()。

Arguments:
     无。

Return Value:
     无。

--
*/

{
 
for (int i=0; i< MAXTHREAD_COUNT; i++)
 
{
  
if (INVALID_HANDLE_VALUE != m_hThreadArray[i])
  
{
   CloseHandle(m_hThreadArray[i]);
   m_hThreadArray[i] 
= INVALID_HANDLE_VALUE;
  }

 }
//end of for
 
 
return;
}
//end of CloseThreadHandle()



BOOL CompletionPortModel::InitWinsock()
/*++

函数描述:
    初始化Winsock,创建一个监听套接字,获取AcceptEx函数指针,为监听套接字分配一个单句柄
 数据,并将监听套接字与完成端口hCOP关联。

Arguments:
     无。

Return Value:
     函数调用成功返回TRUE,失败返回FALSE。

--
*/

{
 WSADATA wsd;
 
int nResult = WSAStartup(MAKEWORD(2,2), &wsd);
 
if (0 != nResult)
 
{
  cout 
<< "WSAStartup() failed" << endl;
  
  
return FALSE;
 }


 m_ListenSocket 
= WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, 
          NULL, 
0, WSA_FLAG_OVERLAPPED); 
    
if (INVALID_SOCKET == m_ListenSocket)
    
{
  cout 
<< "WSASocket() failed: " << WSAGetLastError() << endl;
        WSACleanup();
        
        
return FALSE;
    }

 
 
 DWORD dwResult;

 
//
 
//获取微软SOCKET扩展函数指针
 
//
 nResult = WSAIoctl( 
      m_ListenSocket,
      SIO_GET_EXTENSION_FUNCTION_POINTER,
      
&g_GUIDAcceptEx,
      
sizeof(g_GUIDAcceptEx),
      
&lpAcceptEx,
      
sizeof(lpAcceptEx),
        
&dwResult,
      NULL,
      NULL
      );

 
if (SOCKET_ERROR == nResult)
 
{
  cout 
<< "Get AcceptEx failed: " << WSAGetLastError() << endl;
  closesocket(m_ListenSocket);
        WSACleanup();

  
return FALSE;
 }


 nResult 
= WSAIoctl( 
      m_ListenSocket,
      SIO_GET_EXTENSION_FUNCTION_POINTER,
      
&g_GUIDTransmitFile,
      
sizeof(g_GUIDTransmitFile),
      
&lpTransmitFile,
      
sizeof(lpTransmitFile),
      
&dwResult,
      NULL,
      NULL
      );
 
 
if (SOCKET_ERROR == nResult)
 
{
  cout 
<< "Get TransmitFile failed: " << WSAGetLastError() << endl;
  closesocket(m_ListenSocket);
        WSACleanup();
  
  
return FALSE;
 }



 
//
 
//为监听套接字分配一个单句柄数据
 
//
 PPER_HANDLE_CONTEXT lpListenHandleContext = (PPER_HANDLE_CONTEXT)HeapAlloc(
                    GetProcessHeap(), HEAP_ZERO_MEMORY, 
                    
sizeof(PER_HANDLE_CONTEXT)
                      );
 
if (NULL == lpListenHandleContext)
 
{
  closesocket(m_ListenSocket);
        WSACleanup();

  cout 
<< "HeapAlloc() failed " << endl;


  
return FALSE;
 }


 lpListenHandleContext
->IoSocket = m_ListenSocket;
 lpListenHandleContext
->pNext = NULL;

 
//
 
//将监听套接字m_ListenSocket和已经建立的完成端口关联起来
 
//
 HANDLE hrc = CreateIoCompletionPort(
          (HANDLE)m_ListenSocket,
          m_hCOP,
          (ULONG_PTR)lpListenHandleContext,
          
0
          );
 
if (NULL == hrc)
 
{
  closesocket(m_ListenSocket);
  HeapFree(GetProcessHeap(), 
0, lpListenHandleContext);
  WSACleanup();

  cout 
<< "CreateIoCompletionPort failed: " << GetLastError() << endl;
  
  
return FALSE;
 }


 
return TRUE;
}
//end of InitWinsock()



BOOL CompletionPortModel::BindAndListenSocket()
/*++

函数描述:
    private函数,供Init调用。
 将监听套接字m_ListenSocket绑定到本地IP地址,并置于监听模式。


Arguments:
     无。

Return Value:
     函数调用成功返回TRUE,失败返回FALSE。

--
*/
{
    SOCKADDR_IN InternetAddr;
    InternetAddr.sin_family 
= AF_INET;

#ifdef _DEBUG
    InternetAddr.sin_addr.s_addr 
= inet_addr(LOCALADDRESS);
    InternetAddr.sin_port 
= htons(PORT);   
#else
    InternetAddr.sin_addr.s_addr 
= inet_addr(szAddress);
    InternetAddr.sin_port 
= htons(uPort);    
#endif 
    
    
int nResult = bind(m_ListenSocket, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr));
    
if (SOCKET_ERROR == nResult)
    {
        WSACleanup();
  closesocket(m_ListenSocket);
  
  cout 
<< "bind() failed: " << WSAGetLastError() << endl;
        
        
return FALSE;
    } 

 nResult 
= listen(m_ListenSocket, 20);
    
if (SOCKET_ERROR == nResult)
    {
        WSACleanup();
  closesocket(m_ListenSocket);

  cout 
<< "listen() failed: " << WSAGetLastError() << endl;
        
        
return FALSE;
    }  

 
return TRUE;
}
//end of BindAndListenSocket()


DWORD __stdcall CompletionRoutine(LPVOID Param)
/*++

函数描述:
    完成端口处理线程,循环调用GetQueuedCompletionStatus来获取IO操作结果。

Arguments:

Return Value:
     线程退出代码。

--
*/
{
 CompletionPortModel
* pThis = (CompletionPortModel*)Param;
 DWORD dwNumberBytes;
 PPER_HANDLE_CONTEXT lpHandleContext 
= NULL;
 LPWSAOVERLAPPED lpOverlapped 
= NULL;
 
int nResult;
 BOOL bSuccess;

 
while (TRUE)
 {
  bSuccess 
= GetQueuedCompletionStatus(
           pThis
->m_hCOP,
           
&dwNumberBytes,
           (PULONG_PTR )
&lpHandleContext,
           
&lpOverlapped,
           INFINITE
           );


  
if (FALSE == bSuccess)
  {

#ifndef _DEBUG
   cout 
<< "GetQueuedCompletionStatus() failed: " << GetLastError() << endl;
#endif

   
continue;
  }
  
if (NULL == lpHandleContext)
  {
   
//
   
//PostQueuedCompletionStatus发过来一个空的单句柄数据,表示线程要退出了。
   
//
   return 0;
  }

  PPER_IO_CONTEXT lpPerIoContext 
= (PPER_IO_CONTEXT)lpOverlapped;

#ifdef _DEBUG
  cout 
<< "recv buffer data: " << lpPerIoContext->szBuffer << endl;  
#endif
  
  
if(IoAccept != lpPerIoContext->IoOperation)
  {
   
if((!bSuccess) || (bSuccess && (0 == dwNumberBytes))) 
   {
    closesocket(lpPerIoContext
->sClient);
    lpPerIoContext
->pNext = NULL;
    pThis
->InsertToLookaside(lpPerIoContext, NULL);
    lpHandleContext
->pNext = NULL;
    pThis
->InsertToLookaside(NULL, lpHandleContext);
    
    
continue;
   }
  }

  HANDLE hResult;
  PPER_HANDLE_CONTEXT lpNewperHandleContext;

  
switch(lpPerIoContext->IoOperation)
  {
  
case IoAccept : 
   
if (dwNumberBytes)
   {
    
//
    
//第一次连接成功并且收到了数据,将这个结点从链表中解除
    
//
    EnterCriticalSection(&pThis->m_ListCriSection);
    pThis
->ReleaseNode(lpPerIoContext);
    LeaveCriticalSection(
&pThis->m_ListCriSection);
   }
   nResult 
= setsockopt(
        lpPerIoContext
->sClient, 
        SOL_SOCKET,
        SO_UPDATE_ACCEPT_CONTEXT,
        (
char *)&pThis->m_ListenSocket,
        
sizeof(pThis->m_ListenSocket)
        );
   
if(SOCKET_ERROR == nResult) 
   {
    cout 
<< "SO_UPDATE_ACCEPT_CONTEXT failed to update accept socket" << endl;
    closesocket(lpPerIoContext
->sClient); 
    lpPerIoContext
->pNext = NULL;
    pThis
->InsertToLookaside(lpPerIoContext, NULL);
    
    
continue;
   }

   
   lpNewperHandleContext 
= pThis->GetHandleFromLookaside();
   
if (NULL == lpNewperHandleContext)
   {
    lpNewperHandleContext 
= (PPER_HANDLE_CONTEXT)HeapAlloc(
     GetProcessHeap(),
     HEAP_ZERO_MEMORY,
     
sizeof(PER_HANDLE_CONTEXT)
     );
    
if (NULL == lpNewperHandleContext)
    {
     cout 
<< "HeapAlloc() for lpNewperHandlecontext failed" << endl;
     
     closesocket(lpPerIoContext
->sClient);
     lpPerIoContext
->pNext = NULL;
     pThis
->InsertToLookaside(lpPerIoContext, NULL);
     
     
continue;
    }    
   }

   lpNewperHandleContext
->IoSocket = lpPerIoContext->sClient;
   lpNewperHandleContext
->pNext = NULL;
   
   
//
   
//将新建立的套接字关联到完成端口
   
//
   hResult = CreateIoCompletionPort(
           (HANDLE)lpPerIoContext
->sClient,
           pThis
->m_hCOP,
           (DWORD_PTR)lpNewperHandleContext,
           
0
           );
   
if (NULL == hResult)
   {
    cout 
<< "CreateIoCompletionPort() failed: " << GetLastError();

    closesocket(lpPerIoContext
->sClient);
    lpPerIoContext
->pNext = NULL;
    lpNewperHandleContext
->pNext = NULL;
    pThis
->InsertToLookaside(lpPerIoContext, NULL);
    pThis
->InsertToLookaside(NULL, lpNewperHandleContext);
    
    
continue;
   }

   
if (dwNumberBytes)
   {
    
//
    
//分析处理数据。
    
//
    pThis->HandleData(lpPerIoContext, IO_READ_COMPLETION);
    bSuccess 
= pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
    
if (FALSE == bSuccess)
    {
     
continue;
    }
   }

   
//
   
//如果连接成功但是没有收到数据
   
//
   else
   {
    pThis
->HandleData(lpPerIoContext, IO_ACCEPT_COMPLETION);
    bSuccess 
= pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
    
if (FALSE == bSuccess)
    {
     
continue;
    }
   }
   
break;//end of case IoAccept
   
  
case IoRead:
   pThis
->HandleData(lpPerIoContext, IO_READ_COMPLETION);
   bSuccess 
= pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
   
if (FALSE == bSuccess)
   {
    
continue;
   }

   
break;//end of case IoRead

  
case IoWrite:
   pThis
->HandleData(lpPerIoContext, IO_WRITE_COMPLETION);
   bSuccess 
= pThis->DataAction(lpPerIoContext, lpNewperHandleContext);
   
if (FALSE == bSuccess)
   {
    
continue;
   } 

   
break;

  
default:
   
continue;
   
break;
  }
 }

 
return 0;

}
//end of CompletionRoutine()


BOOL CompletionPortModel::PostAcceptEx()
/*++

Fucntion Description:

 连续发出10个AcceptEx调用。

Arguments:

Return Value:

 函数调用成功返回TRUE,失败返回FALSE。
 
--
*/
{
  
while (m_lAcceptExCounter < 10)
  {
   SOCKET AcceptSocket 
= WSASocket(AF_INET, SOCK_STREAM, IPPROTO_IP, 
          NULL, 
0, WSA_FLAG_OVERLAPPED);  
   
if (INVALID_SOCKET == AcceptSocket)
   {
    cout 
<< "WSASocket failed " << endl;
    
    
return FALSE;
   }

   PPER_IO_CONTEXT lpAcceptExIoContext 
= GetIoFromLookaside();
   
if (NULL == lpAcceptExIoContext)
   {
    lpAcceptExIoContext 
= (PPER_IO_CONTEXT)HeapAlloc(
     GetProcessHeap(), HEAP_ZERO_MEMORY, 
sizeof(PER_IO_CONTEXT));
    
if (NULL == lpAcceptExIoContext)
    {
     cout 
<< "HeapAlloc() failed " << endl;
     closesocket(AcceptSocket);
     
     
return FALSE;
    }
   }

   ZeroMemory(
&(lpAcceptExIoContext->ol), sizeof(lpAcceptExIoContext->ol));
   lpAcceptExIoContext
->sClient = AcceptSocket;
   lpAcceptExIoContext
->IoOperation = IoAccept;
   lpAcceptExIoContext
->pNext = NULL;
   ZeroMemory(lpAcceptExIoContext
->szBuffer, BUFFER_SIZE);
   lpAcceptExIoContext
->wsaBuffer.buf = lpAcceptExIoContext->szBuffer;
   lpAcceptExIoContext
->wsaBuffer.len = BUFFER_SIZE;
   lpAcceptExIoContext
->unId = lpAcceptExIoContext->sClient;
   
   DWORD dwBytes;
   BOOL bSuccess 
= lpAcceptEx(
     m_ListenSocket,
     lpAcceptExIoContext
->sClient,
     lpAcceptExIoContext
->szBuffer,
     lpAcceptExIoContext
->wsaBuffer.len - ((sizeof(SOCKADDR_IN) + 16) * 2),
     
sizeof(SOCKADDR_IN) + 16,
     
sizeof(SOCKADDR_IN) + 16,
     
&dwBytes,
     
&(lpAcceptExIoContext->ol));

   
if (FALSE == bSuccess)
   {
    
int nResult = WSAGetLastError();
    
if (nResult != ERROR_IO_PENDING)
    {
     cout 
<< "AcceptEx() failed :" << nResult << endl;
     closesocket(AcceptSocket);
     HeapFree(GetProcessHeap(), 
0 , lpAcceptExIoContext);
     
     
return FALSE;
    }
    
    InsertNode(lpAcceptExIoContext, NULL); 
    InterlockedExchangeAdd(
&m_lAcceptExCounter, 1);
   } 
  }

  InterlockedExchangeAdd(
&m_lAcceptExCounter, -10);

 
return TRUE;
}
//end of PostAccetExRoutine()


void CompletionPortModel::InsertNode(PPER_IO_CONTEXT pNode, PPER_HANDLE_CONTEXT pHandleNode)
/*++

Fucntion Description:

 根据参数类型将传递进来结点插入到相应的链表头。

Arguments:

    pNode       -  要插入链表中的结点
 pHandleNode -  要插入链表中的结点
 
Return Value:

 无.

--
*/
{
 
if (NULL != pNode)
 {  
  EnterCriticalSection(
&m_ListCriSection);
  pNode
->pNext = m_lpConnectionListHead->pNext;
  m_lpConnectionListHead
->pNext = pNode;
  LeaveCriticalSection(
&m_ListCriSection);
 }

 
return;
}
//end of InsertNode


BOOL CompletionPortModel::ThreadLoop()
/*++

Fucntion Description:
  
   主线程循环,用WaitForSigleObject等待m_hEvent,已经发出的AcceptEx()调用耗尽,FD_ACCEPT
事件将被触发,WaitForSigleObject成功返回,然后调用PostAcceptEx()来新发出10个AcceptEx()调用。
WaitForSigleObject每次等待10秒,超时返回后,对系统中已经建立成功了的并且还没有收发过数据的
SOCKET连接进行检测,如果某个连接已经建立了30秒,并且还没收发过数据,则强制关闭。

Arguments:
     
   无。

Return Value:
      
   函数调用成功返回TRUE,调用失败返回FALSE;
     
--
*/
{
 
int nOptval;
 
int nOptlen;
 
int nResult;
 DWORD dwResult;
 
int nCounter = 0;

#ifdef _DEBUG
 
int nTimeOut = 0;
#endif

 cout 
<< "Server is running.........." << nCounter << " times" << endl; 
 
 
while (TRUE)
 {  
  dwResult 
= WaitForSingleObject(m_hEvent, 10000);
 
  
if (WAIT_FAILED == dwResult)
  {
   PostQueuedCompletionStatus(m_hCOP, 
0, NULL, NULL);
   cout 
<< "WSAWaitForMultipleEvents() failed: " << WSAGetLastError() << endl;

   
return FALSE;
  }
  
  
if (WAIT_TIMEOUT == dwResult)
  { 
   nCounter
++;
   cout 
<< "Server is running.........." << nCounter << " times" << endl;
   
#ifdef _DEBUG
   nTimeOut
++;
   cout 
<< nTimeOut << "*******TIME_OUT********" << nTimeOut << endl;
#endif
   
   PPER_IO_CONTEXT lpCurentNode 
= m_lpConnectionListHead->pNext;
   PPER_IO_CONTEXT lpPreNode 
= m_lpConnectionListHead;
   PPER_IO_CONTEXT lpTempNode; 
   
   
while (NULL != lpCurentNode)
   { 
    EnterCriticalSection(
&m_ListCriSection);
    nOptlen 
= sizeof(nOptval);
    nResult 
= getsockopt(
     lpCurentNode
->sClient,
     SOL_SOCKET,
     SO_CONNECT_TIME,
     (
char*)&nOptval,
     
&nOptlen
     );
#ifdef _DEBUG
    cout 
<< "nOptval = " << nOptval << endl;
#endif _DEBUG
    
    
if (SOCKET_ERROR == nResult)
    {
     cout 
<< "SO_CONNECT_TIME failed: " << WSAGetLastError() << endl;
     lpPreNode 
= lpCurentNode;
     lpCurentNode 
= lpCurentNode->pNext;
     LeaveCriticalSection(
&m_ListCriSection);
     
     
continue;
    }
    
if ((nOptval!=0xFFFFFFFF) && (nOptval>30))
    {
     lpPreNode
->pNext = lpCurentNode->pNext;
     lpTempNode 
= lpCurentNode;
     lpCurentNode 
= lpCurentNode->pNext;
     closesocket(lpTempNode
->sClient);
     lpTempNode
->pNext = NULL;
     InsertToLookaside(lpTempNode, NULL);
    }
    
else
    {
     lpPreNode 
= lpCurentNode;
     lpCurentNode 
= lpCurentNode->pNext;
    }
    LeaveCriticalSection(
&m_ListCriSection);
   }
  }
  
else
  {
   
if (WAIT_TIMEOUT != dwResult)
   {
    
if (FALSE == PostAcceptEx())
    {
     PostQueuedCompletionStatus(m_hCOP, 
0, NULL, NULL);
     
     
return FALSE;
    }
   }
  }
 }

 
return TRUE;
}
//end of CheckConnectTime


void CompletionPortModel::ReleaseNode(PPER_IO_CONTEXT pNode)
/*++

Fucntion Description:

     将参数中传递的结点从链表中解除,但不释放结点。以便不让ThreadLoop函数对其进行超时检测。
此函数在完成端口线程里收发数据成功后调用。

Arguments:
     
  要从链表中释放的结点。

Return Value:

     无。
     
--
*/
{
 PPER_IO_CONTEXT pTempNode 
= m_lpConnectionListHead->pNext;
 PPER_IO_CONTEXT pPreNode 
=m_lpConnectionListHead;
 PPER_IO_CONTEXT pDeleteNode;
 
 
while (NULL != pTempNode)
 {
  
if (pNode->unId == pTempNode->unId)
  {  
   pPreNode
->pNext = pTempNode->pNext;
   pDeleteNode 
= pTempNode;
   pTempNode 
= pTempNode->pNext;
   
   
return;
  }
  
else
  { 
   pPreNode 
= pTempNode;
   pTempNode 
= pTempNode->pNext;
  }
 }

 
return;
}
//end of RealeseNode


BOOL CompletionPortModel::HandleData(PPER_IO_CONTEXT lpPerIoContext, 
int nFlags)
/*++

Fucntion Description:

      根据传进来的nFlags参数对lpPerIoContext进行设置,并指示下一步IO操作。

Arguments:
     
      lpPerIoContext - 调用GetQueueCompletionStatus函数得到的上一次IO操作的结果(扩展的
                      WSAOVERLAPPED结构)。
      nFlags         - 指明已经完成上一次IO的操作类型。

Return Value:

      函数调用成功返回TRUE,失败返回FALSE。
     
--
*/
{
 
//
 
//nFlags == IO_READ_COMPLETION表示完成的上一次IO操作是WSARecv。
 
//
 if (IO_READ_COMPLETION == nFlags)
 {
  
//
  
//完成了WSARecv,接下来需要调用WSASend把刚接收到的数据发送回去,把
  
//lpPerIoContext->ContinueAction = ContinueWrite;
  
//
  lpPerIoContext->IoOperation = IoWrite;
  ZeroMemory(
&(lpPerIoContext->ol), sizeof(WSAOVERLAPPED));
  
  
//
  
//接收到的数据在lpPerIoContext->wsaBuffer.buf里,可以调用
  
//自定义函数对数据自行处理,本例中,简单的将数据再发送回去
  
//
  strcpy(lpPerIoContext->szBuffer, lpPerIoContext->wsaBuffer.buf);
  lpPerIoContext
->wsaBuffer.buf = lpPerIoContext->szBuffer;
  lpPerIoContext
->wsaBuffer.len = BUFFER_SIZE;
  
  
return TRUE;
 }

 
if (IO_WRITE_COMPLETION == nFlags)
 {
  
//
  
//上一次IO操作WSASend数据发送完成,将后续操作标志设置为关闭
  
//如果不需要关闭而是要继续发送,将lpPerIoContext->IoOperation设置为
  
//IoWrite,如果要继续接收,将lpPerIoContext->IoOperation设置为
  
//IoRead,并初始化好BUFFER,本例中,设置关闭
  
//
  lpPerIoContext->IoOperation = IoEnd;

  
return TRUE;
 }
 
if (IO_ACCEPT_COMPLETION == nFlags)
 {
  
//
  
//刚建立了一个连接,并且没有收发数据,,,,
  
//
  lpPerIoContext->IoOperation = IoRead;
  ZeroMemory(
&(lpPerIoContext->ol), sizeof(WSAOVERLAPPED));
  ZeroMemory(lpPerIoContext
->szBuffer, BUFFER_SIZE);
  lpPerIoContext
->wsaBuffer.len = BUFFER_SIZE;
  lpPerIoContext
->wsaBuffer.buf = lpPerIoContext->szBuffer;

  
return TRUE;
 }
 
 
 
return FALSE;
}
// end of HandleData()


BOOL CompletionPortModel::InitLinkListHead()
/*++

Fucntion Description:

     初始化链表头指针。

Arguments:
     无。

Return Value:
     函数调用成功返回TRUE,失败返回FALSE。
     
--
*/
{
 m_lpConnectionListHead 
= (PPER_IO_CONTEXT)HeapAlloc(GetProcessHeap(), 
  HEAP_ZERO_MEMORY, 
sizeof(PER_IO_CONTEXT));
 
 
if (NULL == m_lpConnectionListHead)
 {
  cout 
<< "HeapAlloc() failed " << endl;
  
  
return FALSE;
 }
 m_lpConnectionListHead
->pNext = NULL;
 
 
return TRUE;
}
// end of InitLinkListHead()


BOOL CompletionPortModel::AllocEventMessage()
/*++

Fucntion Description:

     将FD_ACCEPT事件注册到m_hEvent,这样当可用AcceptEx调用被耗尽的时候,就会触发FD_ACCEPT
  事件,然后ThreadLoop里的WaitForSingleObject就会成功返回,导致PostAcceptEx被调用。

Arguments:
     无。

Return Value:
     函数调用成功返回TRUE,失败返回FALSE。
     
--
*/
{
 m_hEvent 
= CreateEvent(NULL, FALSE, FALSE, NULL);
 
if (NULL == m_hEvent)
 {
  PostQueuedCompletionStatus(m_hCOP, 
0, NULL, NULL);
  cout 
<< "CreateEvent() failed: " << GetLastError() << endl;

  
return FALSE;
 }
 
 
int nResult = WSAEventSelect(m_ListenSocket, m_hEvent, FD_ACCEPT);
 
if (SOCKET_ERROR == nResult)
 {
  PostQueuedCompletionStatus(m_hCOP, 
0, NULL, NULL);
  CloseHandle(m_hEvent);

  cout 
<< "WSAEventSeclet() failed: " << WSAGetLastError() << endl;
  
  
return FALSE;
 }

 
return TRUE;
}
//end of AllocEventMessage()


BOOL CompletionPortModel::DataAction(PPER_IO_CONTEXT lpPerIoContext, PPER_HANDLE_CONTEXT lpNewperHandleContext)
/*++

Fucntion Description:

     根据参数lpPerIoContext的成员IoOperation的值来进行下一步IO操作。

Arguments:
     
  lpPerIoContext        - 将随WSASend或者WSARecv一起投递的扩展WSAOVERLAPPED结构。
  lpNewperHandleContext - AcceptEx调用成功后给新套接字分配的“单句柄数据”。

Return Value:
     函数调用成功返回TRUE,失败返回FALSE。
     
--
*/
{
 
int nResult;
 DWORD dwIosize 
= 0;
 DWORD dwFlags 
=0;
 
if (IoWrite == lpPerIoContext->IoOperation)
 {
  nResult 
= WSASend(lpPerIoContext->sClient,
     
&(lpPerIoContext->wsaBuffer),
     
1,
     
&dwIosize,
     
0,
     
&(lpPerIoContext->ol),
     NULL
     );
  
if((SOCKET_ERROR==nResult) && (ERROR_IO_PENDING != WSAGetLastError()))
  {
   cout 
<< "WSASend() failed: " << WSAGetLastError() << endl;
   
   closesocket(lpPerIoContext
->sClient);
   lpPerIoContext
->pNext = NULL;
   lpNewperHandleContext
->pNext = NULL;
   InsertToLookaside(lpPerIoContext, NULL);
   InsertToLookaside(NULL, lpNewperHandleContext);
   
   
return FALSE;
  }
 }
 
 
if (IoRead == lpPerIoContext->IoOperation)
 {
  nResult 
= WSARecv(lpPerIoContext->sClient,
     
&(lpPerIoContext->wsaBuffer),
     
1,
     
&dwIosize,
     
&dwFlags,
     
&(lpPerIoContext->ol),
     NULL
     );
     
  
if((SOCKET_ERROR==nResult) && (ERROR_IO_PENDING != WSAGetLastError()))
  {
   cout 
<< "WSARecv() failed: " << WSAGetLastError() << endl;
    
   closesocket(lpPerIoContext
->sClient);
   lpNewperHandleContext
->pNext = NULL;
   lpPerIoContext
->pNext = NULL;
   InsertToLookaside(lpPerIoContext, NULL);
   InsertToLookaside(NULL, lpNewperHandleContext);
     
   
return FALSE;
  }
   
 }
    
 
if (IoEnd == lpPerIoContext->IoOperation)
 {
  closesocket(lpPerIoContext
->sClient);
  lpNewperHandleContext
->pNext = NULL;
  InsertToLookaside(NULL, lpNewperHandleContext);
  lpPerIoContext
->pNext = NULL;
  InsertToLookaside(lpPerIoContext, NULL);
 }

 
return TRUE;
}
// end of DataAction()


void CompletionPortModel::GetAddressAndPort()
/*++

Fucntion Description:

     由类构造函数调用的函数,用来输入服务器要绑定的本地IP地址和端口。

Arguments:
     
  无。

Return Value:
     
 无。   
     
--
*/
{
 cout 
<< " Please input a port: ";
 cin 
>> uPort;
 cout 
<< " Please input localaddress:";
 cin 
>> szAddress;

 system(
"cls");

 
return;
}
// end of GetAddressAdnPort



void CompletionPortModel::InsertToLookaside(PPER_IO_CONTEXT lpIoNode, PPER_HANDLE_CONTEXT lpHandleNode)
/*++

Fucntion Description:

     给旁视列表的链表中插入一个空闲的结点。

Arguments:
     
  lpIoNode     - 要插入的结点,类型为PPER_IO_CONTEXT。
  lpHandleNode - 要插入的结点,类型为PPER_HANDLE_CONTEXT。

Return Value:
     
 无。   
     
--
*/
{
 
if (NULL != lpIoNode)
 {
  
if (NULL == m_lpIoLookasideLists)
  {
   m_lpIoLookasideLists 
= lpIoNode;
   
   
return;
  }
  lpIoNode
->pNext = m_lpIoLookasideLists->pNext;
  m_lpIoLookasideLists
->pNext = lpIoNode;
  
  
return;
 }

 
if (NULL != lpHandleNode)
 {
  
if (NULL == m_lpHandleLOOKasideLists)
  {
   m_lpHandleLOOKasideLists 
= lpHandleNode;
   
   
return;
  }
  lpHandleNode
->pNext = m_lpHandleLOOKasideLists->pNext;
  m_lpHandleLOOKasideLists
->pNext = lpHandleNode;
  
  
return;
 }

 
return;
}


PPER_IO_CONTEXT CompletionPortModel::GetIoFromLookaside()
/*++

Fucntion Description:

     从旁视列表中解除一个结点并将其返回。

Arguments:
     
  无。

Return Value:
     
 返回一个PPER_IO_CONTEXT类型的结点。   
     
--
*/
{
 
if (NULL == m_lpIoLookasideLists)
 {
  
return NULL;
 }
 EnterCriticalSection(
&m_IoCriSection);
 PPER_IO_CONTEXT lpReturnNode 
= m_lpIoLookasideLists;
 m_lpIoLookasideLists 
= m_lpIoLookasideLists->pNext;
 LeaveCriticalSection(
&m_IoCriSection);

 
return lpReturnNode;
}


PPER_HANDLE_CONTEXT CompletionPortModel::GetHandleFromLookaside()
/*++

Fucntion Description:

     从旁视列表中解除一个结点并将其返回。

Arguments:
     
  无。

Return Value:
     
 返回一个PPER_HANDLE_CONTEXT类型的结点。   
     
--
*/
{
 
if (NULL == m_lpHandleLOOKasideLists)
 {
  
return NULL;
 }
 EnterCriticalSection(
&m_HandleCriSection);
 PPER_HANDLE_CONTEXT lpReturnNode 
= m_lpHandleLOOKasideLists;
 m_lpHandleLOOKasideLists 
= m_lpHandleLOOKasideLists->pNext;
 LeaveCriticalSection(
&m_HandleCriSection);
 
 
return lpReturnNode;
}

================================================================================
/*++

Copyright (c) 2004 

模块名: 

     main.cpp

模块描述:

     主线程文件

作者:

     PPP elssann@hotmail.com

开发环境:
    
     Visual C++ 6.0, Windows 2000.
     
修订记录:

     创建于: 2004.1.16

最后修改日期:
      2004.1.23


--
*/

#include 
"iomodel.h"
#include 
<winsock2.h>
#include 
<windows.h>
#include 
<iostream.h>

#pragma comment(lib, "ws2_32.lib")


void main()
{
 CompletionPortModel p;
 p.Init();
 p.AllocEventMessage();
 
if (FALSE == p.PostAcceptEx())
 {
  
return;
 }
 p.ThreadLoop();

 
return;
}

php对接建行h5网页支付 php对接建行h5网页支付前言H5页面支付H5页面支付回调通知退款及其他的业务 前言 最近接到公司乙方要求,需要再支付上面增加建行的支付渠道。之前一直用的都是微信和支付宝这种第三方支付平台,银行直接对接比较少。没办法就只能上网搜索,搜索一番返现网上对接建行的资料比较少,没办法只好摸着石头过河,中间也借鉴了一些大佬的文章内容。 H5页面支付 根据建行的h5网页支付对接文档,还是可以看出是比较简单的。... 阅读详情

相关推荐

优刻得首个「国产千卡智算集群」落地,支持智源千亿大模型训练

在人工智能引领的时代浪潮中,算力已成为技术进步与创新的核心驱动力。面对当下AI算力需求的飙升、高端AI芯片供应受限的挑战,加之OpenAI带来的技术封锁,唯有坚定不移的发展自主可控的国产技术方案,持续壮大国产智算集群规模,才能一路突破围追堵截,进一步促进国产大模型产业生态繁荣。集群还提供了完善高效的软件栈工具,集成多种主流的深度学习编程框架,通过测试验证,确保了对行业领先人工智能模型的高效支持,做到开箱即用,用户可快速投入模型的开发和部署,加速了其在人工智能领域的布局和发展。方面取得了显著成效。

Ucloud_TShare的博客 1473

XDMA架构:异构SoC数据搬运与布局转换的革命

在现代异构计算系统中,高效数据搬运是提升整体性能的关键。传统DMA引擎虽然能处理连续内存传输,但在处理非连续数据布局转换时存在显著性能瓶颈。XDMA(eXtensible DMA)架构通过硬件地址生成引擎、分布式设计和插件系统三大创新,将数据搬运与布局转换统一到硬件流水线中。这种架构特别适合AI计算场景,如KV缓存管理和稀疏矩阵处理,实测显示在4D矩阵reshape场景下性能提升达151.2倍。通过支持N维仿射地址计算和实时数据操作插件,XDMA为异构SoC提供了高带宽、低延迟的数据传输解决方案,显著提升系

weixin_30298497的博客 421

Cocos Creator3.5.2使用Spine切换皮肤

3、然后获取当前spine中是否有皮肤数据,如果spine连皮肤数据都没有,接着写可能会error,这里的 skins[skin],是判断皮肤列表中是否有需要更换的皮肤。2、因为spine往往意味着异步加载,换肤时也经常出现频繁异步的情况,所以下一步我们要可以判断当前spine组件里是否已经加载好了spine文件。换肤前隐藏好需要换肤的节点,避免穿帮;官网文档的换肤流程太简化,对于实际使用来说会有各种各样的问题。1、首先获取对应的spine组件。这里的skin自然是外部传入的需要换肤的皮肤名。

tc654321的博客 1818

一个对Winsock完成端口模型封装的类

一个对Winsock完成端口模型封装的类2004-6-12 作者:elssann 转载自:中国电脑教育报 网友评论 0 条 点击进入论坛  在WINDOWS下进行网络服务端程序开发,毫无疑问,Winsock 完成端口模型是最高效的。Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口模型懂了之后是比较简单的,但是要想掌握Winsock完成端口模型,需要对W

wzwind的专栏 871

软件自动化测试脚本如何编写,编写自动化测试脚本的几点注意事项

我们需要自动化测试脚本的原因:1、验收测试是一项非常复杂和费力的被用来确保软件质量的环节。它的目的就是使软件能得到充分运行并且能使其符合原始需求。2、测试的目的无非就是满足了两个结果:测试通过或测试不通过。为了能实现每个测试结果,必须要明确每个测试用例。并且对于整个系统而言在合同中必须定义测试用例的失败标准,而且这个失败标准数必须是可接受的。3、测试用例的通过条件必须有一个清晰的标准,标准可以被定...

weixin_39575775的博客 2251

A2B V1.0协议学习笔记(A2B System Specification V1.0)

A2B全称是 Automotive Audio Bus 汽车音频总线,主要是解决传统音频总线线多、线重、成本贵等问题。本文是以A2B V1.0规范为准,现在ADI又推出了A2B V2.0,物理层不兼容,采用了QPSK编码方式,后续有时间再更新。A2B总线的优点:支持1主多从的架构(最多10个子节点)支持50Mbps(具体是49.152 Mbps)的高带宽传输以及

qflook的博客 4428

【转】一个对Winsock 完成端口模型封装的类

文章标题:一个对Winsock 完成端口模型封装的类原 作 者:不详原 出 处:不详发 布 者:loose_went发布类型:转载发布日期:2004-08-16 源代码说明:  在WINDOWS下进行网络服务端程序开发,毫无疑问,Winsock 完成端口模型是最高效的。Winsock的完成端口模型借助Widnows的重叠IO和完成端口来实现,完成端口

Gary's Blog --- A C++ programmer 1294

ExpressLRS(ELRS)接收机技术全景:从历史演进、技术底层到产业生态全解析——上

ExpressLRS(ELRS)是一种由全球开源社区驱动的双向射频遥控链路协议栈,其接收机作为核心终端,承担信号接收解调、遥控指令解析、飞控数据交互与遥测回传等关键功能。自2018年问世以来,ELRS凭借极致性能、低成本、开放生态和强兼容性,从航模DIY方案发展为消费级无人机领域的事实标准,并逐步渗透工业级场景。 ELRS接收机采用模块化分层架构,支持2.4GHz和900MHz双频段,通过LoRa、FLRC、FSK多调制模式实现低延迟与远距离平衡。其核心技术包括自适应跳频抗干扰、TDD双向链路和极简数据包。

bother3000的博客 303
上一篇: 硬盘分区表和文件分配表格式
下一篇: 异步IO、APC、IO完成端口、线程池与高性能服务器(一~五)
Pregunta
博客等级 码龄23年 6粉丝 19原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值