基于IP多播的组讨论会实例第二部分

来源:互联网 发布:mac ssh 阿里云服务器 编辑:程序博客网 时间:2024/05/01 17:51
基于IP多播的组讨论会实例第二部分
类的构造函数初始化各个成员变量,然后创建内部工作线程_GroupTalkEntry,析构函数通知工作线程退出,释放资源。类的所有工作是在_GroupTalkEntry线程中完成的.下面是实现代
CGroupTalk::CGroupTalk(HWND hNotifyWnd, DWORD dwMultiAddr, DWORD dwLocalAddr, int nTTL)
{
       m_hNotifyWnd = hNotifyWnd;
       m_dwMultiAddr = dwMultiAddr;
       m_dwLocalAddr = dwLocalAddr;
       m_nTTL = nTTL;
       m_bQuit = FALSE;
 
       // 取得本机的用户名作为当前客户用户名
       DWORD dw = 256;
       ::gethostname(m_szUser, dw);
       //创建事件对象和工作线程
       m_hEvent = ::WSACreateEvent();
       m_hThread = ::CreateThread(NULL, 0, _GroupTalkEntry, this, 0, NULL);
}
 
CGroupTalk::~CGroupTalk()
{
       // 通知工作线程退出,等它退出后,释放资源
       m_bQuit = TRUE;
       ::SetEvent(m_hEvent);
       ::WaitForSingleObject(m_hThread, INFINITE);
       ::CloseHandle(m_hThread);
       ::CloseHandle(m_hEvent);
}
说明:
1   CGroupTalk(HWND hNotifyWnd, DWORD dwMultiAddr, DWORD dwLocalAddr, int nTTL)中的四个参数,分别是主窗口的句柄,多播地址,本地地址,多播封包的nTTL值。
2   当终端加入到多播组时,它指定TTL参数,来指明终端的多播应用程序想要经过多少个路由器来发送和接收数据,例如,如果你写一个IP多播程序,它使用TTL为2加入组X,加入命令被发送到本地子网上的所有路由器组。子网上的路由器查看此命令,在内部记下自己以后应该转发寻址那个地址的多播数据,让后对TTL减1,在将这个加入命令传递到它的邻近网络。这些网络上的路由器在此命令上做相同的事情。所以TTL限制了多播数据将被复制的距离。TTL=0 为多播封包被限制在同一个主机,TTL=1,多播封包被限制在一个子网内。TTL=32多播的封包被限制在同一站点。
 
3.
m_hEvent = ::WSACreateEvent();
m_hThread = ::CreateThread(NULL, 0, _GroupTalkEntry, this, 0, NULL);
The WSACreateEvent function is used to create an event object created that is manual reset with an initial state of nonsignaled. Windows Sockets 2 event objects are system objects in Win32 environments. Therefore, if a Win32 application desires auto reset events, it can call the native WSACreateEvent Win32 function directly. The scope of an event object is limited to the process in which it is created.
CreateThread
The CreateThread function creates a thread to execute within the address space of the calling process.
HANDLE CreateThread(
 LPSECURITY_ATTRIBUTES lpThreadAttributes, // pointer to security attributes
 DWORD dwStackSize,                         // initial thread stack size
 LPTHREAD_START_ROUTINE lpStartAddress,     // pointer to thread function
 LPVOID lpParameter,                        // argument for new thread
 DWORD dwCreationFlags,                     // creation flags
 LPDWORD lpThreadId                         // pointer to receive thread ID
);
 lpStartAddress
Pointer to the application-defined function of type LPTHREAD_START_ROUTINE to be executed by the thread and represents the starting address of the thread. For more information on the thread function, see ThreadProc.
其中_GroupTalkEntry就是线程开始的地址,_GroupTalkEntry为一个函数,其中完成了很多重要的工作,起代码如下:
DWORD WINAPI _GroupTalkEntry(LPVOID lpParam)
{
       CGroupTalk *pTalk = (CGroupTalk *)lpParam;
 
       // 创建发送套节字和接收套节字        
       pTalk->m_sSend = ::socket(AF_INET, SOCK_DGRAM, 0);
       pTalk->m_sRead = ::WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
 
       // 设置允许其它套节字也接收此接收套节字所监听端口的地址
       BOOL bReuse = TRUE;
       ::setsockopt(pTalk->m_sRead, SOL_SOCKET, SO_REUSEADDR, (char*)&bReuse, sizeof(BOOL));
 
       // 设置多播封包的TTL值
       ::setsockopt(pTalk->m_sSend,
                            IPPROTO_IP, IP_MULTICAST_TTL, (char*)&pTalk->m_nTTL, sizeof(pTalk->m_nTTL));
      
       // 设置要使用的发送接口
       setsockopt(pTalk->m_sSend,
                     IPPROTO_IP, IP_MULTICAST_IF, (char*)&pTalk->m_dwLocalAddr, sizeof(pTalk->m_dwLocalAddr));
 
       // 绑定接收套节字到本地端口
       sockaddr_in si;
       si.sin_family = AF_INET;
       si.sin_port = ::ntohs(GROUP_PORT);
       si.sin_addr.S_un.S_addr = pTalk->m_dwLocalAddr;
       int nRet = ::bind(pTalk->m_sRead, (sockaddr*)&si, sizeof(si));
       if(nRet == SOCKET_ERROR)
       {           
              ::closesocket(pTalk->m_sSend);
              ::closesocket(pTalk->m_sRead);
              ::SendMessage(pTalk->m_hNotifyWnd, WM_GROUPTALK, -1, (long)"bind failed! /n");
              return -1;
       }
 
       // 加入多播组
       if(!pTalk->JoinGroup())
       {
              ::closesocket(pTalk->m_sSend);
              ::closesocket(pTalk->m_sRead);
              ::SendMessage(pTalk->m_hNotifyWnd, WM_GROUPTALK, -1, (long)"JoinGroup failed! /n");
              return -1;
       }
 
       // 循环接收到来的封包
       WSAOVERLAPPED ol = { 0 };
       ol.hEvent = pTalk->m_hEvent;
       WSABUF buf;
       buf.buf = new char[BUFFER_SIZE];
       buf.len = BUFFER_SIZE;     
       while(TRUE)
       {
              // 投递接收I/O
              DWORD dwRecv;
              DWORD dwFlags = 0;
              sockaddr_in saFrom;
              int nFromLen = sizeof(saFrom);
              int ret = ::WSARecvFrom(pTalk->m_sRead,
                                          &buf, 1, &dwRecv, &dwFlags, (sockaddr*)&saFrom, &nFromLen, &ol, NULL);
              if(ret == SOCKET_ERROR)
              {
                     if(::WSAGetLastError() != WSA_IO_PENDING)
                     {
                            ::SendMessage(pTalk->m_hNotifyWnd, WM_GROUPTALK, -1, (long)"PostRecv failed! /n");
                            pTalk->LeaveGroup();  
                            ::closesocket(pTalk->m_sSend);
                            ::closesocket(pTalk->m_sRead);
                            break;
                     }
              }
 
              // 等待I/O完成,处理封包
              ::WSAWaitForMultipleEvents(1, &pTalk->m_hEvent, TRUE, WSA_INFINITE, FALSE);
              if(pTalk->m_bQuit)              // 是否退出?
              {
                     pTalk->LeaveGroup();  
                     ::closesocket(pTalk->m_sSend);
                     ::closesocket(pTalk->m_sRead);
                     break;
              }
              BOOL b = ::WSAGetOverlappedResult(pTalk->m_sRead, &ol, &dwRecv, FALSE, &dwFlags);
              if(b && dwRecv >= sizeof(GT_HDR))
              {    
                     GT_HDR *pHeader = (GT_HDR*)buf.buf; 
                     // 填写源地址信息
                     pHeader->dwAddr = saFrom.sin_addr.S_un.S_addr;
                     pTalk->DispatchMsg(pHeader, dwRecv);
              }
       }
 
       delete buf.buf;
       return 0;
}
说明
1  
     pTalk->m_sRead = ::WSASocket(AF_INET, SOCK_DGRAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
WSA_FLAG_OVERLAPPED:是指定套街字的属性,使用的是重叠I/O模型,这个模型基本的设计思想是允许应用程序使用重叠的数据结构一个投递一个或者多个异步I/O请求。提交I/O请求完成后,与之关联的重叠数据结构中的事件对象受信,应用程序便可以使用WSAGetOverLappedResul获取重叠操作的结果。
2
  WSAOVERLAPPED ol={0};
ol.hEvent = pTalk->m_hEvent;
WSAOVERLAPPED
The WSAOVERLAPPED structure provides a communication medium between the initiation of an overlapped I/O operation and its subsequent completion. The WSAOVERLAPPED structure is designed to be compatible with the Win32 OVERLAPPED structure:
typedef struct _WSAOVERLAPPED {
    DWORD        Internal;
    DWORD        InternalHigh;
    DWORD        Offset;
    DWORD        OffsetHigh;
    WSAEVENT     hEvent;
} WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;
 前4个参数由系统使用,应用程序不应该操作他们。hEvent域允许应用程序为这个操作关联一个事件对象句柄。重叠I/O事件通知方法需要将Windows事件对象关联到上面的结构体中,这是重叠I/O的特色。
当使用重叠I/O模型,每个I/O函数都要接受一个WSAOVERLAPPED的结构体类型的参数。
ol.hEvent = pTalk->m_hEvent; 关联一个事件对象句柄。
3.
int ret = ::WSARecvFrom(pTalk->m_sRead,
              &buf, 1, &dwRecv, &dwFlags, (sockaddr*)&saFrom, &nFromLen, &ol, NULL);
 
&ol:这个参数当I/O请求完成立即返回,他们依靠程序传递的WSAOVERLAPPED的结构来管理I/O请求的完成。
 
3.:WSAWaitForMultipleEvents(1, &pTalk->m_hEvent, TRUE, WSA_INFINITE, FALSE);
WSAWaitForMultipleEvents函数
熟悉WSAEventSelect模型的朋友对这个函数肯定不会陌生,不对,其实大家都不应该陌生,这个函数与线程中常用的WaitForMultipleObjects函数有些地方还是比较像的,因为都是在等待某个事件的触发嘛。
因为我们需要事件来通知我们重叠操作的完成,所以自然需要这个等待事件的函数与之配套。
DWORD WSAWaitForMultipleEvents(
        DWORD cEvents,                        // 等候事件的总数量
      const WSAEVENT* lphEvents,           // 事件数组的指针
      BOOL fWaitAll,               // 这个要多说两句:
                                       // 如果设置为 TRUE,则事件数组中所有事件被传信的时候函数才会返回
                                      // FALSE则任何一个事件被传信函数都要返回
                                     // 我们这里肯定是要设置为FALSE的
      DWORD dwTimeout,     // 超时时间,如果超时,函数会返回 WSA_WAIT_TIMEOUT
                                          // 如果设置为0,函数会立即返回
                                     // 如果设置为 WSA_INFINITE只有在某一个事件被传信后才会返回
                                   // 在这里不建议设置为WSA_INFINITE,因为。。。后面再讲吧..-_-b
         BOOL fAlertable       // 在完成例程中会用到这个参数,这里我们先设置为FALSE
                              );
返回值:
   WSA_WAIT_TIMEOUT :最常见的返回值,我们需要做的就是继续Wait
    WSA_WAIT_FAILED : 出现了错误,请检查cEvents和lphEvents两个参数是否有效
如果事件数组中有某一个事件被传信了,函数会返回这个事件的索引值,但是这个索引值需要减去预定义值 WSA_WAIT_EVENT_0才是这个事件在事件数组中的位置
 4
BOOL b = ::WSAGetOverlappedResult(pTalk->m_sRead, &ol, &dwRecv, FALSE, &dwFlags);
  BOOL WSAAPI WSAGetOverlappedResult( SOCKET s, LPWSAOVERLAPPED lpOverlapped, LPDWORD
                                                                                                     lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags );
s:标识套接口。这就是调用重叠操作(WSARecv()、 WSARecvFrom()、WSASend()、WSASendTo() 或 WSAIoctl())时指定的那个套接口。
lpOverlapped:指向调用重叠操作时指定的WSAOVERLAPPED结构。
lpcbTransfer:指向一个32位变量,该变量用于存放一个发送或接收操作实际传送的字节数,或WSAIoctl()传送的字节数。
fWait:指定函数是否等待挂起的重叠操作结束。若为真TRUE则函数在操作完成后才返回。若为假FALSE且函数挂起,则函数返回FALSE,WSAGetLastError()函数返回                    WSA_IO_INCOMPLETE。
lpdwFlags:指向一个32位变量,该变量存放完成状态的附加标志位。如果重叠操作为                    WSARecv()或WSARecvFrom(),则本参数包含lpFlags参数所需的结果。返回值:
  如果函数成功,则返回值为真TRUE。它意味着重叠操作已经完成,lpcbTransfer所指向的值已经被刷新。应用程序可调用WSAGetLastError()来获取重叠操作的错误信息。
  如果函数失败,则返回值为假FALSE。它意味着要么重叠操作未完成,要么由于一个或多个参数的错误导致无法决定完成状态。失败时,lpcbTransfer指向的值不会被刷新。应用程序可用WSAGetLastError()来获取失败的原因
                                                     
工作线程创建发送和接收的套接字,设置他们的属性,将接收到的套接字加入到会议组,之后进入无限循环再m_sRead套接字上读取收到的UDP包,然后到用DisptachMsg函数处理这个封包。在第三部分,将介绍DisptachMsg 函数。
 
原创粉丝点击