一个win32下的完成端口模式网络编程例子

来源:互联网 发布:淘宝食品如何备案 编辑:程序博客网 时间:2024/05/28 17:07

一个win32下的完成端口模式网络编程例子

         一个一般的网络服务程序的工作流程是:启动监听服务→等待客户连接→接受客户连接→创建新的线程处理客户的请求→客户中断连接退出线程。

    然而创建和撤销线程需要开销,成为影响性能的瓶颈。假若我们能够预先创建好线程,然后用这些线程为不同的客户处理数据请求,而不必为每个客户创建新的线程,那么我们的服务器性能将得到大幅度的提高,尤其要同时处理大量的套接字时。想想,系统不能无限的创建线程,当需要同时处理大量的套接字时,利用有限的线程为大量的客户连接提供服务就很有必要了。

    win32API为我们提供一种完成端口模式的I/O模型用于网络编程。下面是一个例子:

头文件

#pragma once

#include "RSA.h"

#include "AES.h"

#include "AccountManageClass.h"

 

#define SOCKET_OPT_PROC             1

#define SOCKET_OPT_RECV             2

#define SOCKET_OPT_QUIT              3

 

//客户端的单句柄数据类型

typedef struct _SPER_HANDLE_DATA

{

         SOCKET                       sockfd;                        //连接套接字

……                                                                    //其他类型数据

……                                                                    //其他类型数据

}SPER_HANDLE_DATA, *LPSPER_HANDLE_DATA;

 

//单IO操作数据

typedef struct _SPER_IO_DATA{

         OVERLAPPED   overlapped;

         WSABUF           buffer;               //一个数据缓冲区,用于WSASend/WSARecv中的第二个参数

         char                dataBuffer[BUFF_LEN];//实际的数据缓冲区

         int                       dataLength;                        //实际的数据缓冲区长度

         int                                operatorType;           //操作类型

}SPER_IO_DATA, *LPSPER_IO_DATA;

 

class ServerClass

{

public:

         staticconst int LISTEN_NUMER = 32;                                                             //监听队列数量

         staticconst int CHECK_CONNECT_TIME_OUT = 10 * 60 * 1000;           //监测连接超时时间间隔

 

private:

         staticDWORD WINAPI ServerAcceptThread(LPVOID lpParam);    //接受连接线程

         staticDWORD WINAPI ServerWorkerThread(LPVOID lpParam);   //工作者线程

         staticDWORD WINAPI CheckConnectThread(LPVOID lpParam);  //定时检测连接状态函数

public:

         ServerClass();

         ~ServerClass();

 

         boolServerStart(short port);                               //启动服务

 

         voidAcceptThread(HANDLE CompletionPort);        //接受连接处理

         voidWorkerThread(HANDLE CompletionPort);       //具体工作处理

         voidConnectThread();                                                    //定时监测处理

 

private:

         voidServerStop();                                                   //关闭服务

         voidInit();                                                                 //初始化相关变量

         //释放资源

         voidFreeSource(LPSPER_HANDLE_DATA perHandleData, LPSPER_IO_DATA perIoData);

 

private:

         boolm_inited;                                                //是否已初始化标志

         SOCKETm_listen;                                         //TheListen socket

         CRITICAL_SECTIONm_section;                //Crucial sourceprotected

         list<SOCKET>m_custom_sockets;           //The connectedsockets

         HANDLEm_accept_thread;                       //Acceptconnect process thread handle

         HANDLEm_check_thread;                         //Checkconnect timeout thread handle

};

 

//用于传给线程的参数结构

typedef struct _THREAD_PARAM

{

         HANDLECompletionPort;

         ServerClass      *object;

}THREAD_PARAM, *LPTHREAD_PARAM;


 

源文件

#include <string.h>

#include <stdio.h>

#include <stdlib.h>

 

#include <winsock2.h>

#include <mswsock.h>

 

#include <string>

#include <iterator>

#include <list>

using namespace std;

 

#include "ServerClass.h"

using namespace std;

 

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

 

ServerClass::ServerClass()

{

         m_inited= false;

         Init();

}

 

ServerClass::~ServerClass()

{

         ServerStop();

}

 

void ServerClass::Init()

{

         m_accept_thread= NULL;

         m_check_thread= NULL;

         InitializeCriticalSection(&m_section);

         m_inited= true;

}

 

bool ServerClass::ServerStart(short port)

{

         if(!m_inited)

                   Init();

 

         //创建完成端口句柄

         HANDLECompletionPort;

         CompletionPort= CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);

         if(NULL== CompletionPort)

         {

                   perror("ServerStart(): WSAStartup failed: %d", GetLastError());

                   returnfalse;

         }

 

         //根据系统的CPU核数创建处理线程

         SYSTEM_INFOinfo;

         GetSystemInfo(&info);

         for(DWORDi=0; i < info.dwNumberOfProcessors; i++)

         {

                   LPTHREAD_PARAMlpParam = new THREAD_PARAM;

                   lpParam->CompletionPort= CompletionPort;

                   lpParam->object= this;

                   HANDLEThreadHandle = CreateThread(NULL, 0, ServerClass::ServerWorkerThread, lpParam,0, NULL);

                   if(NULL== ThreadHandle)

                   {

                            deletelpParam;

                            CloseHandle(ThreadHandle);

                   }

         }

 

         //为防止用户建立连接不发送数据,创建检测连接超时线程,将此类连接关断

         {

                   LPTHREAD_PARAMlpParam = new THREAD_PARAM;

                   lpParam->CompletionPort= CompletionPort;

                   lpParam->object= this;

                   m_check_thread= CreateThread( NULL, 0, ServerClass::CheckConnectThread,  lpParam, 0, NULL);

                   if(NULL== m_check_thread)

                   {

                            deletelpParam;

                            perror("CreateCheckConnectThread failed");

                            returnfalse;

                   }

         }

 

         //启动socket库

         WSADATAdata;

         if(WSAStartup(MAKEWORD(2,2),&data)!= 0)

         {

                   perror("ServerStart(): WSAStartup failed: %d", WSAGetLastError());

                   returnfalse;

         }

 

         m_listen= WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);

         if(INVALID_SOCKET== m_listen)

         {

                   perror("ServerStart(): Listen =  WSASocket failed: %d",WSAGetLastError());

                   returnfalse;

         }

 

         //Setreuse the address, then can use the same socket address immediately whenrestart the server

         intnOpt = 1;

         setsockopt(m_listen,SOL_SOCKET, SO_REUSEADDR, (char*)&nOpt, sizeof(nOpt));

 

         SOCKADDR_INaddr;

         addr.sin_family= AF_INET;

         addr.sin_addr.s_addr= htonl(INADDR_ANY);

         addr.sin_port= htons(port);

 

         if(SOCKET_ERROR== bind(m_listen,(PSOCKADDR)&addr,sizeof(addr)))

         {

                   perror("ServerStart(): bind failed: %d", WSAGetLastError());

                   returnfalse;

         }

 

         if(listen(m_listen,ServerClass::LISTEN_NUMER) == SOCKET_ERROR)

         {

                   perror("ServerStart(): listen failed: %d", WSAGetLastError());

                   returnfalse;

         }

 

         //创建监听接受客户连接线程

         {

                   LPTHREAD_PARAMlpParam = new THREAD_PARAM;

                   lpParam->CompletionPort= CompletionPort;

                   lpParam->object= this;

                   m_accept_thread= CreateThread( NULL, 0, ServerClass::ServerAcceptThread,  lpParam, 0, NULL);

                   if(NULL== m_accept_thread)

                   {

                            deletelpParam;

                            perror("CreateUpdateRegisterThread failed");

                            returnfalse;

                   }

         }

         returntrue;

}

 

void ServerClass::ServerStop()

{

         if(NULL!= m_check_thread)

         {

                   TerminateThread(m_check_thread,0);

                   m_check_thread= NULL;

         }

 

         //closeall connected socket

         EnterCriticalSection(&m_section);

         for(list<SOCKET>::iteratoriter = m_custom_sockets.begin(); iter != m_custom_sockets.end(); iter++)

         {

                   if(*iter!= INVALID_SOCKET)

                            closesocket(*iter);

         }

         LeaveCriticalSection(&m_section);

         closesocket(m_listen);

         WaitForSingleObject(m_accept_thread,1000);

         WSACleanup();

         DeleteCriticalSection(&m_section);

         m_inited= false;

}

 

voidServerClass::FreeSource(LPSPER_HANDLE_DATA perHandleData, LPSPER_IO_DATAperIoData)

{

         //现将该socket从检测队列中删除

         EnterCriticalSection(&m_section);

         if(NULL!= perHandleData && closesocket(perHandleData->sockfd) !=SOCKET_ERROR)

         {

                   for(list<SOCKET>::iteratoriter = m_custom_sockets.begin(); iter != m_custom_sockets.end();)

                   {

                            if(*iter== perHandleData->sockfd)

                            {

                                     m_custom_sockets.erase(iter++);

                                     break;

                            }

                            ++iter;

                   }

         }

         LeaveCriticalSection(&m_section);

 

         //再释放所申请的内存

         if(NULL!= perHandleData)

         {

                   GlobalFree(perHandleData);

                   perHandleData= NULL;

         }

         if(NULL!= perIoData)

         {

                   GlobalFree(perIoData);

                   perIoData= NULL;

         }

         #ifdef        _DEBUG

         perror("closesocketand free perHandleData perIoData!");

         #endif

}

 

void ServerClass::AcceptThread(HANDLECompletionPort)

{

         SOCKETAccept;

         LPSPER_HANDLE_DATAPerHandleData = NULL;

         LPSPER_IO_DATAperIoData = NULL;

         DWORDSendBytes;

 

         while(true)

         {

                  #ifdef        _DEBUG

                   perror("Waitfor new connect...");

                   #endif

                   Accept= WSAAccept(m_listen, NULL, NULL, NULL, 0);

                   if(INVALID_SOCKET== Accept)        //the listen socket hasbeen closed

                            break;

                   #ifdef        _DEBUG

                   perror("Accept%d connect...", Accept);

                   #endif

                   PerHandleData= (LPSPER_HANDLE_DATA)GlobalAlloc(GPTR, sizeof(SPER_HANDLE_DATA));

                   if(NULL== PerHandleData)

                   {

                            perror("GlobalAllocPerHandleData failed: %d", GetLastError());

                            closesocket(Accept);

                            continue;

                   }

                   perIoData= (LPSPER_IO_DATA)GlobalAlloc(GPTR, sizeof(SPER_IO_DATA));

                   if(NULL== perIoData)

                   {

                            perror("GlobalAllocperIoData failed: %d", GetLastError());

                            GlobalFree(PerHandleData);

                            closesocket(Accept);

                            continue;

                   }

 

                   PerHandleData->sockfd= Accept;

                   ZeroMemory(&(perIoData->overlapped),sizeof(OVERLAPPED));

                   if(NULL== CreateIoCompletionPort((HANDLE)Accept, CompletionPort, (DWORD)PerHandleData,0))

                   {

                            perror("CreateIoCompletionPortfailed: %d", GetLastError());

                            GlobalFree(PerHandleData);

                            GlobalFree(perIoData);

                            closesocket(Accept);

                            continue;

                   }

                   LINGERlinger = {1, 1};

                   setsockopt(Accept,SOL_SOCKET, SO_LINGER, (char *)&linger, sizeof(linger));

                   //这里开始接收或发送第一个数据包(这里代码是发送,如果是接收请用WSARecv,并根据居体情况设置perIoData->operatorType

                   perIoData->buffer.buf= (char*)&(perIoData->dataBuffer);

                   perIoData->buffer.len= perIoData->dataLength = sizeof(perIoData->dataBuffer);

                   perIoData->operatorType= SOCKET_OPT_RECV;

                   if(WSASend(Accept,&(perIoData->buffer), 1, &SendBytes, 0, &(perIoData->overlapped),NULL) == SOCKET_ERROR)

                   {

                            if(WSAGetLastError()!= WSA_IO_PENDING)

                            {

                                     perror("Sendpublic key failed");

                                     GlobalFree(PerHandleData);

                                     GlobalFree(perIoData);

                                     closesocket(Accept);

                                     continue;

                            }

                   }

                   EnterCriticalSection(&m_section);

                   m_custom_sockets.push_back(Accept);

                   LeaveCriticalSection(&m_section);

         }

}

 

void ServerClass::WorkerThread(HANDLECompletionPort)

{

         DWORDbytes;

         LPSPER_HANDLE_DATAperHandleData = NULL;

         LPSPER_IO_DATAperIoData = NULL;

         DWORDFlags;

         intret;

         DWORDRecvBytes, SendBytes;

         while(true)

         {

                   bytes= -1;

                   ret= GetQueuedCompletionStatus(CompletionPort, &bytes,(LPDWORD)&perHandleData, (LPOVERLAPPED*)&perIoData, INFINITE);

 

                   //如果连接的客户已关闭套接字或出现错误,则关闭套接字,并释放资源

                   if(bytes== 0 || perIoData->operatorType == SOCKET_OPT_QUIT)

                   {

                            FreeSource(perHandleData,perIoData);

                            continue;

                   }

                   if(perIoData->operatorType== SOCKET_OPT_PROC)

                   {/*上一次操作WSASend或WSARecv设置perIoData->operatorType为SOCKET_OPT_PROCm,则当WSASend或WSARecv操作完成奖调用此处的代码*/

                            /*在这里可以对收发结果进行处理

                            若为WSARecv则接收到的数据存放在perIoData的dataBuffer中,接收到的数据长度为perIoData 的dataLength。

                            如果这里会对数据进行处理并要把数据发送给客户,则调用下面的的代码*/

if(需要发送数据)

                            {

                                     memset(&(perIoData->dataBuffer),0, sizeof(MESSAGE_PACKAGE));

                                     memcpy(&(perIoData->dataBuffer),&发送的数据, sizeof(MESSAGE_PACKAGE));

                                     perIoData->buffer.buf= (char*)&(perIoData->dataBuffer);

                                     perIoData->buffer.len= perIoData->dataLength;

                                     perIoData->operatorType= SOCKET_OPT_RECV;

                                     if(WSASend(perHandleData->sockfd,&(perIoData->buffer), 1, &SendBytes, 0,&(perIoData->overlapped), NULL) == SOCKET_ERROR)

                                     {

                                               if(WSAGetLastError()!= WSA_IO_PENDING)

                                               {

                                                        FreeSource(perHandleData,perIoData);

                                               }

                                     }

                                     continue;

                            }

                   }

                   //如果是其他操作,则继续接收客户发送的数据

                   ZeroMemory(&(perIoData->overlapped),sizeof(OVERLAPPED));

                   memset(&perIoData->dataBuffer,0, sizeof(perIoData->dataBuffer));

                   Flags= 0;

                   perIoData->buffer.buf= (char*)&(perIoData->dataBuffer);

                   perIoData->buffer.len= perIoData->dataLength = sizeof(perIoData->dataBuffer);

                   perIoData->operatorType= SOCKET_OPT_PROC;

                   if(WSARecv(perHandleData->sockfd,&(perIoData->buffer),1, &RecvBytes,&Flags, &(perIoData->overlapped),NULL) ==SOCKET_ERROR)

                   {

                            if(WSAGetLastError()!= WSA_IO_PENDING)

                            {

                                     FreeSource(perHandleData,perIoData);

                            }

                   }

         }

}

 

void ServerClass::ConnectThread()

{

         intret;

         intnSeconds;

         intnLen = sizeof(nSeconds);

         return;

         while(true)

         {

                   Sleep(ServerClass::CHECK_CONNECT_TIME_OUT);

 

                   //Closeall connect timeout socket

                   EnterCriticalSection(&m_section);

                   for(list<SOCKET>::iteratoriter = m_custom_sockets.begin(); iter != m_custom_sockets.end();)

                   {

                            ret= getsockopt(*iter, SOL_SOCKET, SO_CONNECT_TIME, (char *)&nSeconds,&nLen);

                            if(nSeconds!= -1 && nSeconds > 60 || ret == SOCKET_ERROR)

                            {

                                     closesocket(*iter);

                                     m_custom_sockets.erase(iter++);

                                     #ifdef        _DEBUG

                                     perror("Connecttimeout...");

                                     #endif

                                     continue;

                            }

                            ++iter;

                   }

                   LeaveCriticalSection(&m_section);

         }

}

 

DWORD WINAPIServerClass::ServerAcceptThread(LPVOID lpParam)

{

         HANDLECompletionPort = ((LPTHREAD_PARAM)lpParam)->CompletionPort;

         ServerClass*object = ((LPTHREAD_PARAM)lpParam)->object;

         object->AcceptThread(CompletionPort);

         deletelpParam;

         return0;

}

 

DWORD WINAPIServerClass::ServerWorkerThread(LPVOID lpParam)

{

         HANDLECompletionPort = ((LPTHREAD_PARAM)lpParam)->CompletionPort;

         ServerClass*object = ((LPTHREAD_PARAM)lpParam)->object;

         object->WorkerThread(CompletionPort);

         deletelpParam;

         return0;

}

 

DWORD WINAPIServerClass::CheckConnectThread(LPVOID lpParam)

{

         #ifdef_DEBUG

         perror("CheckConnectThread...");

         #endif

         ServerClass*object = ((LPTHREAD_PARAM)lpParam)->object;

         object->ConnectThread();

         deletelpParam;

         return0;

}

 

调用ServerStop()启动服务后要保持主线程运行,否则主线程结束,程序将退出。

可以这样使用

int main()

{

HANDLE hHandle =CreateEvent(NULL, TRUE, FALSE, NULL);

ServerClass server;

         if(server.ServerStart())

         {

                   printf("%sfile %d line: Server Ready...", __FILE__, __LINE__);

         }

         WaitForSingleObject(hHandle,INFINITE);

}

0 0