封装了P2P连接与数据传输过程的DLL(一)

类别:VC语言 点击:0 评论:0 推荐:


写在前面的话

  PSerSocket.DLL(在下文中将简称为DLL)将进行P2P连接时的连接过程和传送信息的方法封装在了DLL里面,使开发P2P的过程相对简单化和标准化,因为使用了数据流的方式来传输对象,从而使数据的传送变得轻松和直观,使开发者不用去解读一个个的字符串,可以充分地利用Windows的异步消息机制直接去处理一个个的指令对象。
  DLL采用了自定义的一套数据流方式来传送和合成指令对象,而没有使用VC的序列化对象,避免了VC序列化对象不能在不同语言平台上合成的问题。DLL的数据流对象可以在VC和JAVA之间自由传输,从而使开发者不用去处理多平台传输时的复杂问题。
  DLL封装了建立连接和传送信息的细节,使不同的开发者开发的P2P产品互相连接成为了可能,即使于国际标准化的接轨,也只需要重新下载新的DLL就可以啦,而不需要每个开发者都去改写程序。随着DLL的新的功能的增加与完善,每个开发者都可以直接地将这些功能集成进原有的软件中,将大大地加快开发的速度和可靠性。
 DLL具有非常强大的扩展性,它通过回调函数和消息与外部进行交互,可以使开发者随时得到程序当前运行情况的通报。
 
  P2P网络的建立,不是一个或几个公司能够完成的,只有全体开发者的共同努力,才可以建立起中国自己的P2P网络。


使用DLL来建立连接所需要的步骤
  使用DLL来建立一个聊天连接或传送、接收文件的连接是非常容易的,你只需按以下步骤就可以实现:
 
  1 初始化DLL。通过调用以下方法和设置属性来实现。
    PDefine::SInitSocketStream();
    PThreadParm::SCreateTCPListenPort(1500);  //1500是你自己的监听端口号。
    PDefine::cSMsgInfo.pWnd=this;
    PDefine::cSMsgInfo.bIsAutoDel=false;

    PDefine::pSGetFriendCallBackFun=CPPSerDlg::SGetFriendFromID;
    /*
    一个回调函数,是CPPSerDlg类中的一个静态方法。DLL通过向这个回调函数传递一个好友的ID号,来得到该好友的PFriend对象的指针。
    在测试时,你可以固定返回一个已创建好的PFriend对象的指针。
    */
  
    PDefine::pSGetFileNCallBackFun=CPPSerDlg::SGetFileNameByFileID;
    /*
    一个回调函数,是CPPSerDlg类中的一个静态方法。DLL通过向这个回调函数传递一个文件的数字标识,来得到具体的文件名称,包含路径和扩展名,是你想传送给被连接方的文件名称。
    在测试时,你可以固定返回一个进行传输测试的文件。这个文件最好有几十兆,因为下载速度会非常快。
    */
 
  2 在你的窗口中响应2个消息。
    CREATE_NEW_OBJ_MSG     //在消息的响应函数中,直接调用PDefine::SCreateNewObj(wParam,lParam)静态方法。
    CREATE_NEW_THREAD_MSG    //在消息的响应函数中,直接调用PDefine::SCreateNewThread(wParam,lParam)静态方法。
 
  3 创建一个PFriend对象,填充PFriend对象的三个属性:
    m_lpszHostAddress;   //被连接方的主机地址。
    m_nHostPort;    //被连接方的主机的监听端口。
    m_sFriendID;    //好友的ID,在测试时你可以随便设定一个12位长的字符串。

  4 创建一个PRecvInfo结构。(如果你想创建一个聊天连接,直接跳到第6步。)
    PRecvInfo* prf=new PRecvInfo();
    prf->szBlockMap=NULL;       //固定赋值为NULL。
    prf->nFileLen=110273244;      //想下载的文件的总的字节数。
    prf->nSplitNo=30;        //将文件分割成多少个块下载,取值从1..999。
    prf->sSaveName="e:\\test\\cs1004";    //被保存的文件名,不含扩展名。
    prf->sExpendName=".exe";      //文件的扩展名。

  5 创建一个PThreadParm对象,来建立接收文件的过程。
    PThreadParm* ptp=new PThreadParm();
    ptp->SetMaxThreadNo(5);       //设定下载时的最大线程个数。
    ptp->m_pRecvInfo=prf;       //前面创建的PRecvInfo结构的指针。
    ptp->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789"); //该连接的PFriend对象的指针。
  
    //以接收的身份开始下载。
    ptp->StartThread(RECV_TYPE);     //以接收的身份开始下载。
  
    以上过程,就可以建立一个与PFriend对象所指定方的连接,并且完成一个文件的下载。
 
  6 创建一个PThreadParm对象,来建立一个聊天连接。
    PThreadParm* ptp=new PThreadParm();
    ptp->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789"); //该连接的PFriend对象的指针。
  
    ptp->StartThread(CHAT_TYPE);          //创建一个聊天连接。
  
  应该可以看出来,创建连接的过程是如此的简单。当然,上面的过程省略了很多的细节,使你无法响应DLL发出的通知消息。
  这是给开发者使用的,因此DLL提供了大量的接口,并且在运行过程中,你也需要响应一些消息,去设定一些参数。下面我将一步步地介绍DLL中定义的类以及消息的使用方法。


DLL的工作方式
  DLL通过回调函数和消息与外部进行交互,要想正确地使用这个DLL,就需要了解DLL中定义的4个类,以及回调函数和结构,了解DLL向外部窗口发送的每个消息的含义。

DLL中定义的类
  
  DLL中定义的类共有四个:PDefine、PFriend、PBaseAct、PThreadParm。
   PDefine     定义了需要使用的结构、回调函数、常量以及DLL定义的一些消息和静态方法。这个类不需要去创建实例,它里面的都是静态方法、属性和一些定义。
     
      PFriend     想与之建立连接的一方都称为好友,每一个好友都必需有一个PFriend对象与之对应,在这个对象中记录了这个好友的唯一标识、他的IP地址、监听端口等相关信息。
      PBaseAct     是所有可以转变为数据流进行传输的指令对象的基类,用户需要自定义传输对象时,都需要从该类来派生出新的类。
     
      PThreadParm   描述并记录了一个或一组连接,是创建连接时必需使用的类。
     
      只要撑握了这4个类,你就撑握了这个DLL。
     

如何使用这个DLL呢?
 下面是使用这个DLL的实例,具体程序可以参照随包发送的PPSer项目。
 
  第一步:初始化DLL。
   通过调用PDefine类中的静态方法,以及设定一些静态属性的值来完成DLL的初始化工作。
   1 PDefine::SInitSocketStream();
   2 PThreadParm::SCreateTCPListenPort(1500);
   3 PBaseAct::SSetUserGetObject(CPPSerDlg::UserGetObject);
   4 PDefine::cSMsgInfo.pWnd=this;
   5 PDefine::cSMsgInfo.bIsAutoDel=false;
   6 PDefine::pSGetFriendCallBackFun=CPPSerDlg::SGetFriendFromID;
   7 PDefine::pSGetFileNCallBackFun=CPPSerDlg::SGetFileNameByFileID;
   
  第1行语句用来初始化WinSock,使建立Socket连接成为可能。这句必需首先调用。
  第2行用来创建一个TCP监听端口,方法的参数用来指定想创建的监听端口号。
  第3行设定DLL所需要使用的回调函数的指针。回调函数在PDefine中被定义。这个回调函数的类型为GOCALLBACK,返回值为CRuntimeClass*。它在PDefine中被声明为:
    typedef CRuntimeClass* (WINAPI *GOCALLBACK)(BYTE nType,LPCTSTR szVersion);
   它通过传递的2个参数来返回指定对象的CRuntimeClass*。在这里,这个函数被定义为CPPSerDlg中的一个静态方法,它在CPPSerDlg.h文件中的定义如下:
    static CRuntimeClass* WINAPI UserGetObject(BYTE nType,LPCTSTR szVersion);
   关于这个函数体的实现,将在后面介绍。
  第4行填充一人PRecvInfo结构中的值,这个结构保存了响应消息的窗口的指针,结构在PDefine中被定义。PDefine::cSMsgInfo变量中保存的窗口指针,是指向用来响应DLL内的一些关键消息的窗口的指针。
  第5行表示该结构是否会被DLL自动删除,指定为false表示不会被DLL自动删除。
  第6行设定一个回调函数的指针,这个函数的类型为GETPALCALLBACK,返回值为PFriend*。它在PDefine中被声明为:
    typedef PFriend* (WINAPI* GETPALCALLBACK)(LPCTSTR szFriendID);
   这个函数根据传递的好友的ID号来返回一个PFriend*对象的指针。在这里,这个函数被定义为CPPSerDlg中的一个静态方法,它在CPPSerDlg.h文件中的定义如下:
    static PFriend* WINAPI SGetFriendFromID(LPCTSTR sID);
   关于这个函数体的实现,将在后面介绍。
  第7行设定一个回调函数的指针,这个函数的类型为GETFILENCALLBACK,返回值无。它在PDefine中被声明为:
    typedef void (WINAPI *GETFILENCALLBACK)(int nFileID,PFriend* pFriend,CString &sFileN);
   这个函数根据传递的nFileID的文件数字标识,来返回一个nFileID所对应的实际的文件名。在这个函数里,你可以通过传递的第二个参数pFriend来得知想获得该文件的好友的ID,校验对方是否有权限来获取该文件,如果有权限获得,则将实际的文件名填充在第三个参数sFileN中;否则设定sFileN=""。它在CPPSerDlg.h文件中的定义如下:
    static void WINAPI SGetFileNameByFileID(int nFileID,PFriend* pFriend,CString &sFileN);
   关于这个函数体的实现,将在后面介绍。


  第二步:创建好友对象PFriend
   每一个PFriend对象都表示一个能够建立连接的好友,在这个对象里你必需填写好友的ID号、IP地址和监听端口号,然后将这个PFriend对象放入一个List或Map中保存起来。
   在CPPSerDlg::SGetFriendFromID(LPCTSTR sID)方法中,你需要在保存PFriend对象的List或Map中检索到该对象,然后返回PFriend*;如果没有查到,则返回NULL。
   在监听端口接收到一个连接请求后,会与请求连接一方进行握手,以确认双方的身份。在这个握手的过程中,会调用SGetFriendFromID()这个回调函数,以确认对方是否够进行连接,如果返回NULL,则认为对方不具备建立连接的权限,则会主动断开连接。
   如果你希望响应任何一个连接请求,你需要在检索时如果没有查找到PFriend对象,则动态地创建一个PFriend对象,然后加入列表中。
    
     第三步:创建一个接收文件的结构PRecvInfo,适用于连接类型为RECV_TYPE(如果你建立的连接类型为SEND_TYPE,请跳过这步,如果你想建立的连接类型为CHAT_TYPE,请直接跳到第7步)
    PRecvInfo* prf=new PRecvInfo();
    prf->szBlockMap=NULL;     //固定赋值为NULL,DLL将填充具体的值。
    prf->nFileLen=110273244;    //想下载的文件的总的字节数。
    prf->nSplitNo=30;      //文件将被分割的块数。
    prf->sSaveName="e:\\test\\cs1004";  //下载文件被保存的文件名,包含路径,但不含扩展名。
    prf->sExpendName=".exe";    //下载文件的扩展名。

    nFileLen表示想下载的文件的大小,这个字节数你需要通过其它方式来获得,在创建一个PRecvInfo结构时,你必需填写一个正确的值。
    nSplitNo表示文件被动态分割成多个块来下载。你可以将一个大的文件动态地分割成若干个小的块来下载,然后使用一个合成函数将分割下载完的文件合并成一个文件。使用这种方法是为了响应以后的扩展,在后面将提供对分割块进行下载后校验的方法,如果某一个下载块出现问题,可以只传送这个下载块。不使用一个大文件的另外一个原因是为了避免在建立一个大文件时产生的一个延时问题,在以后的扩展中,可以把一个大的文件的分割块保存在不同的硬盘上。
    sExpendName表示想下载的文件的扩展名,这个扩展名应该是从传送方得到的,它和传送方传送的文件的扩展名是相同的,接收方不能去修改这个值。

  第四步:创建一个PThreadParm对象,启动接收文件。
    PThreadParm* ptp=new PThreadParm();
    ptp->SetMaxThreadNo(5);     //设置该次下载任务的所同时开启的最大线程个数为5个。
    ptp->m_dwIdentify=(DWORD)ptp;   //PThreadParm的数字标识,可以是指向该对象的地址。
    ptp->nFileID=150;      //表示接收文件的数字标识。
    ptp->m_pRecvInfo=prf;     //指向接收结构PRecvInfo的指针。
    ptp->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789");
    ptp->m_pMsgInfo=&PDefine::cSMsgInfo; //接收传递消息的PMsgInfo结构。
  
    ptp->StartThread(RECV_TYPE);   //启动一个接收线程。
   PThreadParm中的属性m_dwIdentify表示该对象的数字标识,通过这个数字标识你应该可以查找到该PThreadParm对象,并且能够明确地知道该对象是否存在。因为这个标识的值将作为大部分传递给m_pMsgInfo所指定的窗口的消息的lParam参数。
   你可以建立一个List或Deque来保存每个PThreadParm的值,一旦这个对象被delete,则从List或Deque中取消,当接收到消息时,你可以在List或Deque中去检索这个值,如查存在,表示对象可用;否则,不用响应该消息,简单的返回0即可。

 第五步:在PDefin::cSMsgInfo结构中指定的窗体中响应2个消息。
    CREATE_NEW_OBJ_MSG
    在这个消息的响应函数中直接调用PDefine::SCreateNewObj(wParam,lParam)静态方法,传递消息响应函数的2个参数wParam和lParam即可,其它不必作任何事情。

    CREATE_NEW_THREAD_MSG
    这个消息的处理较为复杂,也比较关键。其中lParam参数中保存的是新创建或已经存在的PThreadParm对象的指针。你可以通过判断PThreadParm->m_nIdentify==0来得知该PThreadParm对象是否已设定了标识。如果值为0,你需要为这个对象设定一个唯一的标识。而且你还需要去判断对象的类型,如果类型为RECV_TYPE,则需要根据pParm->m_nFileID来填充PRecvInfo结构,这时候,你是接收文件的一方,这次的连接,有可能是对方主动向你传送一个文件,或者对方在防火墙内,而你在防火墙外,因此由对方进行一个主动连接,而这个m_nFileID所表示的文件ID正是你请求对方传送的文件的数字标识。想完成一个接收的连接,你必需设定PRecvInfo结构中相应的属性。另外,你还需要去设定m_pMsgInfo或m_pCallBackFun的值,以使对象能产生回调。
    最后在消息的响应函数中调用PDefine::SCreateNewThread()静态方法,传递wParam和lParam参数。
  该消息的响应函数参考方法如下:
    {
     PThreadParm* pParm=(PThreadParm*)lParam;
     if(pParm->m_dwIdentify==0)
     {
        //如果是第一次创建该对象,即该对象还没有得到标识。
        //将PThreadParm对象的指针作为标识传回。
        pParm->m_dwIdentify=lParam;
        pParm->m_pMsgInfo=&PDefine::cSMsgInfo;
        //将该对象放入一个队列中。

        //如果对象类型为接收类型,则需要根据pParm->m_nFileID来填充PRecvInfo结构。
        if(pParm->GetThredType()==RECV_TYPE)
        {
          PRecvInfo* pRecvInfo=new PRecvInfo();
          pParm->m_pRecvInfo=pRecvInfo;
          //在下面填充结构。
          //pRecvInfo->nFileLen=
          //pRecvInfo->nSplitNo=
          //pRecvInfo->sExpendName=
          //pRecvInfo->sSaveName=
        }
      }
      PDefine::SCreateNewThread(wParam,lParam);
      return 0;
    }


 第六步:完成在初始化DLL的第7行中设定的回调函数。
    void WINAPI SGetFileNameByFileID(int nFileID,PFriend* pFriend,CString &sFileN)
    {
      //进行校验,是否是指定的好友,如果不是,则不允许下载。
      if(pFriend->m_sFriendID=="PPQ123456789")
       sFileN="e:\\cs1004.exe";
      else
       sFileN="";
    }
 
  第七步:创建一个聊天的PThreadParm则要简单的多,你只需要指定PFriend对象和PMsgInfo结构的指针。
    PThreadParm* pt=new PThreadParm();
    pt->m_pFriend=CPPSerDlg::SGetFriendFromID("PPQ123456789");
    PMsgInfo* pmi=new PMsgInfo();
    pmi->pWnd=this;
    pmi->bIsAutoDel=true;   //为这个PThreadParm单独创建的PMsgInfo结构,在PThreadParm删除时,也自动删除该PMsgInfo结构。
    pt->m_pMsgInfo=pmi;    //你也可以直接将PDefine::cSMsgInfo的地址赋给该变量。
    pt->StartThread(CHAT_TYPE);

  
  经过以上的步骤,你已经完成了所有必需设定的回调函数,并且对如何使用DLL的流程也有了一定的认识。在后面将继续介绍如何响应DLL的消息,以及如何自定义传输的指令对象。

 

  关于PBaseAct派生类的说明:
   在StdAfx.h文件中增加对PBaseAct派生类的类型的定义:
    enum
    {
      SELF1_OBJ=USER_OBJ,  //定义用户类型标识为SELF1_OBJ
      SELF2_OBJ,
      //在后面可加入更多的类型标识。
      //USER_OBJ是用户自定义派生类可以使用的最小类型标识编号。
      //你可以创建的派生类的最大标识号为255,类型标识是用字节来表示的。
    }
 
   PBaseAct的派生类必需重载以下三个方法:
    virtual void SelfSerialize(CString& src)
    {
      PBaseAct::InitUniteValue(src);  //该方法的第一句必需调用该方法。
      //加入自对该类属性的序列化。
      //重复调用静态方法void SUniteValue(src,LPCTSTR value),
      //将想序列化的属性依次传送,value是想传送的属性的字符串(LPCTSTR)或整型(int)表示。

      PBaseAct::SelfSerialize(src);  //该方法的最后一句必需调用基类的方法。
    }
    virtual void InitObject(LPVOID data);
    {
      PBaseAct::InitObject(data);   //该方法的第一句必需调用基类的方法。
      //按照你在SelfSerialize()方法中序列化属性时的顺序,
      //依次调用静态方法int SGetValue(data,value)
      //其中的value是属性的变量名称,可以是一个CString或int类型的变量。
   
      //PBaseAct中有2个SGetValue()方法,一个是传递字符串的值,一个是传递int的值,
      //实际上都是调用得到字符串的方法,然后再函数内部进行了转换。
      //SGetValue()方法会返回当前属性的顺序号。顺序号从3开始计数。
      //顺序号的0是传送者ID,1是接收者ID,2是该对象的版本号。在基类的方法中会自动分解得到这些值。
    }
    virtual BYTE GetObjType()
    {
      return SELF1_OBJ;   //在Self1Act派生类中返回。
      //在Self2Act中则返回 return SELF2_OBJ。
    }

   如何在接收过程中处理自定义的PBaseAct派生类对象?
    下面以CPPSerDlg中的静态方法为例:(具体程序请参考PPSer例子程序)

    1 必需建立以下函数或类中的静态方法:

      在CPPSerDlg.h文件中增加public方法:
        static CRuntimeClass* WINAPI UserGetObject(BYTE nType,LPCTSTR szVersion);
        static BOOL WINAPI ParseUserObj(WPARAM wParam,LPARAM lParam);

      在CPPSerDlg.cpp文件中对相应方法定义如下:
        CRuntimeClass* WINAPI CPPSerDlg::UserGetObject(BYTE nType,LPCTSTR szVersion)
        {
          //szVersion变量中保存了该对象的版本号,可以通过检测版本号来判断是否
          //是你所要求的对象。
          if(szVersion=="#MySelfDefineObj"
          {
            switch(nType)
            {
              case SELF1_OBJ:
                return RUNTIME_CLASS( Self1Act ); //Self1Act为自定义的PBaseAct的派生类。
              case SELF2_OBJ:
                return RUNTIME_CLASS( Self2Act );
            }
          }
          else if(szVerion=="#User1DefineObj")
          {
            //加入对其他用户创建的对象的支持。
            switch(nType)
            {
              case SELF1_OBJ:
                return RUNTIME_CLASS( Self1Act ); //Self1Act为自定义的PBaseAct的派生类。
              case SELF2_OBJ:
                return RUNTIME_CLASS( Self2Act );
            }
          }
          return NULL;
        }

        BOOL WINAPI CPPSerDlg::ParseUserObj(WPARAM wParam,LPARAM lParam)
        {
          //其中wParam的低8位表示该对象的类型标识。wParam的高位暂时保留,可能以后会使用。
          //lParam是指向一个类型标识的对象的指针,可通过强制转换符转换成对象的指针。
          //以Self1Act为例:
          BYTE nType=(BYTE)wParam;
          PBaseAct* pa=(PBaseAct*)lParam;
          //如果使用了版本号,还要注意版本号的区别。pa->m_sVersion内保存有对象的版本号。
          switch(nType)
          {
            case SELF1_OBJ:
              Self1Act* sa=(Self1Act*)pa;
              //在下面对该接收到的该指令进行处理。
       
              break;
            case SELF2_OBJ:
              Self2Act* sa=(Self2Act*)pa;
              //在下面对该接收到的该指令进行处理。
       
              break;
          }
         }
   
    2 建立自已的PBaseAct派生类还必需重载下面这个方法:
      virtual GETCALLBACK* GetParseActFunPointer()
      {
        return CPPSerDlg::ParseUserObj;
      }
      你也可以为每一个派生类指定一个不同的处理函数,这时无需比较类型,
      每个派生类的处理函数都是该派生类中的一个静态方法。
   
    3 需要调用一个方法设定回调对象。
      PBaseAct::SetUserGetObject(CPPSerDlg::UserGetObject);
      这个调用应该加在主程序的初始化阶段。
  
    4 也可以设定一个窗口句柄和自定义一个消息,然后由程序将该消息发送到指定窗口。
      在派生类中定义一个静态的PMsgInfo结构,然后重载PBaseAct中的方法GetCallBackMsg()
      PMsgInfo* GetCallBackMsg()
      {
        return <指向PMsgInfo变量的指针>;
      }

         5 不管是发送或者是由接收所产生的指令对象,都可以修改对象中属性的值后,重复发送,这个是和C++的对象流是不一样的。

  6 每一个开发者都可以定义自已的派生类,但是派生类的标识都是从USER_OBJ来开始的,这样如何区分是你自已的派生类还是其他开发者的派生类呢?

  在PBaseAct的基类中,定义了一个字符型变量m_sVersion,在这个变量中保存了用户的版本号(关于如何创建自已的版本号,还没有确定),这个版本号暂时没有长度的限制,为了不使你自己的派生类和其他开发者的派生类相混淆,你可以定义的足够长,例如:由16位字符组成,使用的字符可以是a..z、A..Z、0..9、_和+、- 、#符号,请不要使用其它的符号。
   
      版本号使程序不但可以支持开发者自己创建的对象,而且可以支持其他开发者创建的对象,而不需要修改程序。

      开发者可以将自己定义的用户对象和指令解析程序打包成一个DLL,直接发布出来,隐藏具体的实现细节,提供足够的接口,使其他开发者可以直接调用这些对象来完成指定的功能。在发布自己的派生类对象时,必需提供一个唯一的版本号。


   看完以上的内容,你应该已经很清楚如何创建自己的派生类啦,每一个派生类都可以表示一个或一组指令,这个指令对象不象VC的序列化对象,它可以被重复使用,如果你修改了对象的值,被修改过的值会被发送出去。当然,这个对象流还不如VC的序列化对象的功能强大,对象中暂时还只能包含一些简单的类型,如CString、LPCTSTR、int这三种类型,而且对象的最大长度不能超过16850个字节。我将在下一版本中,改进对象流,使其能够支持无限大数据,并且会将语言传输与接收集成进来。希望大家有什么意见,可以随时给我发信。

   
DLL的消息

  以下是用户必需响应的消息,响应这些消息的窗口被定义在PDefine::cSMsgInfo结构中。
 
 CREATE_NEW_OBJ_MSG=29600,
 
    在消息函数执行体中直接调用PDefine::SCreateNewObj()静态方法,传递wParam和lParam参数。其它不必作任何事情。

 CREATE_NEW_THREAD_MSG,

   lParam参数中保存的是新创建或已经存在的PThreadParm对象的指针。你可以通过判断PThreadParm->m_nIdentify==0来得知该PThreadParm对象是否已设定了标识。
    如果值为0,你需要为这个对象设定一个唯一的标识。而且你还需要去判断对象的类型,如果类型为RECV_TYPE,则需要根据pParm->m_nFileID来填充PRecvInfo结构。
    另外,你还需要去设定m_pMsgInfo或m_pCallBackFun的值,以使对象能产生回调。
    具体方法请参看实例。
    最后在消息的响应函数中调用PDefine::SCreateNewThread()静态方法,传递wParam和lParam参数。


  你接收到一个PThreadParm对象工作结束的消息时,你可以决定是否要继续使用该对象,如果想继续使用该对象,则先调用InitValue()方法,InitValue()不会改变原有的赋值,如果你想继续工作,则不用改动已经赋过值的属性,而直接调用StartThread()方法。
  如果你想完成其它的工作,你必需要设定相应的属性的值,就象你新创建了一个该PThreadParm对象一样,然后再调用StartThread()方法。
  如果不想继续使用该对象,你需要delete这个对象。
  如果你在delete这个对象这前,不希望对象去删除PRecvInfo对象的指针,你可以保存该对象的指针,然后设定m_pRecvInfo=NULL。


 ALL_CONNECT_END_MSG,
    一个PThreadParm的所有的连接请求都已终止,询问该如何处理。
    lParam保存的是指向PThreadParm的指针。

 MISSION_END_MSG,
    指示一个PThreadParm结束了工作,但它的任务还没有完成。
    lParam保存的是指向PThreadParm的指针。
    终止工作的原因可能是用户发出的中断,或在传送过程中连接出现了问题。

 MISSION_COMPLETED_MSG,
    指示一个PThreadParm任务完成。
    lParam保存的是指向PThreadParm的指针。
 


 //以下是用户可以响应的消息
 
 CHAT_CONNECT_CREATED_MSG,
    一个聊天Socket被建立,并且已经连接到了好友对象上。
    lParam参数指向拥有该聊天连接的PFriend对象的指针。不要删除该指针。

 CHAT_CLOSED_MSG,
    一个聊天Socket被关闭。
    lParam参数指向拥有该聊天连接的PFriend对象的指针。不要删除该指针。
 
 ONE_RECV_THREAD_END_MSG,
    一个接收线程结束。
    lParam参数保存的线程所在的PThreadParm中的m_dwIdentify属性的值。
    wParam参数的低8位表示该线程在PThreadParm线程组中的标识号。

 ONE_SEND_THREAD_END_MSG,
    一个传送线程结束。
    lParam参数保存的线程所在的PThreadParm中的m_dwIdentify属性的值。
    wParam参数的低8位表示该线程在PThreadParm线程组中的标识号。

 THREAD_MAX_NO_CREATED_MSG,
    已经创建了最大的线程个数。

 RECV_NEW_BLOCK_MSG,
    接收线程准备接收一个新分配的块号。
    lParam参数保存的线程所在的PThreadParm中的m_dwIdentify属性的值。
    wParam参数的低8位表示该线程在PThreadParm线程组中的标识号。
    你可以调用GetThreadIDInArray((BYTE)wParam)来获得该线程所在的PThreadID对象的指针。

 SEND_NEW_BLOCK_MSG,
    传送线程准备传送一个新的部分。
    lParam参数保存的线程所在的PThreadParm中的m_dwIdentify属性的值。
    wParam参数的低8位表示该线程在PThreadParm线程组中的标识号。
    你可以调用GetThreadIDInArray((BYTE)wParam)来获得该线程所在的PThreadID对象的指针。

 RECV_DATA_MSG,
    接收到了新的数据信息。在消息的响应函数中,你需要设定PThreadParm中的属性 m_bIsSendMsg=!m_bIsSendMsg,否则无法接收到新的消息。
    在PThreadParm中的m_dwCompletedLen属性中保存的是总的完成的字节数。
    lParam参数保存的线程所在的PThreadParm中的m_dwIdentify属性的值。
    wParam参数的低8位表示该线程在PThreadParm线程组中的标识号。
    你可以调用GetThreadIDInArray((BYTE)wParam)来获得该线程所在的PThreadID对象的指针。

 SEND_DATA_MSG,
    同RECV_DATA_MSG消息。


   由DLL发送的消息还将继续扩充。对于DLL的使用就暂时介绍到这里啦,在随包发送的头文件中都有相应的说明,并且随包发送了一个测试的例子,这个例子在PPSer目录下。PPSer项目中的PChatAct是一个用户自定义对象的实例。
   在CPPSerDlg.cpp文件中的SGetFileNameByFileID()函数体内,将sFileN="e:\\cs1004.exe"这一句改成你想测试下载的已存在的文件名。
   在CPPSerDlg.cpp文件中的OnTest()函数体中,是如何创建一个接收线程和一个聊天线程的例子,文件发送方和接收方都是用户自己的机器,修改其中的PRecvInfo结构中prf->sSaveName="e:\\test\\cs1004"这一句,可以改变下载后文件被保存的位置和文件名,改成自己机器上相应的目录和文件名。修改prf->nFileLen=110273244这一句为你想下载的文件以字节为单位的大小。
   重新编译程序,然后点击Test按钮,就会自动开始下载,点击StartThread按钮,将发送一条聊天消息,发送的对象是PChatAct,点击StopThread按钮,将中止下载线程。重新点击Test按钮,将进行断点续传下载。
   注意:点击Test按钮后,除非点击StopThread按钮,否则不要重新点击Test按钮,否则下载会不正常。

   聊天连接如果在一段时间内没有发送或接收消息,将自动断开连接,以释放资源。

   你可以同创建多个PThreadParm对象,然后调用对象的StartThread()方法进行连接。DLL会将你请求的连接放入一个队列中,循环进行连接测试,如果成功,则启动线程或建立聊天连接;如果不成功,会将连接请求重新放入队列的尾部,然后从队列的头部取出一个连接请求,偿试进行连接。
   DLL充分利用了Windows的异步消息机制,只有连接被建立,并且经过校验成功后,才会创建新的线程,而并不会为每个连接都去创建一个线程。
   当一个线程组启动后,会依次请求创建属于线程组的多个线程,这些连接请求具有较高的优先级,会优先于其它的连接请求而首先处理。

   现在只是DLL的第一个版本,在下一个版本中,我会在DLL中集成语音传输,使开发者可以利用DLL直接开发出具有语音聊天功能的软件。
   
   希望大家将使用DLL开发过程中碰到的任何问题或意见及时的通知我,也希望更多的朋友能加入这个开发行列,共同探讨这个话题。      email:[email protected]

本文地址:http://com.8s8s.com/it/it2421.htm