基于IP多播的组讨论会实例分析一

来源:互联网 发布:php oa系统原理 编辑:程序博客网 时间:2024/06/02 20:51
       第一部分:
封装CGroupTalk类
 CGroupTalkl类在构造函数中创建内部线程,工作线程会自动地加入会议组,处理接受到的消息,在析构函数中对资源进行清理,离开会议组,一切都不需要用户干预。CGroupTalk类仅向用户提供一个成员函数-------SendText,用于向指定的成员或会议组发送单播或多播数据。
   CGroupTalk类创建两个套接字----m_sSend和m_sRead,一个用于接受数据,一个用于发送数据。下面就是CGroupTalk的具体定义。
class CGroupTalk
{
public:
       // 构造函数,创建工作线程,加入会议组
       CGroupTalk(HWND hNotifyWnd, DWORD dwMultiAddr, DWORD dwLocalAddr = INADDR_ANY, int nTTL = 64);
       // 析构函数,清理资源,离开会议组
       ~CGroupTalk();
 
       // 向其它成员发送消息。dwRemoteAddr为目标成员的地址,如果为0则向所有成员发送
       BOOL SendText(char *szText, int nLen, DWORD dwRemoteAddr = 0);
 
protected:
              // 帮助函数
       // 加入一个多播组
       BOOL JoinGroup();
       // 离开一个多播组
       BOOL LeaveGroup();
       // 向指定地址发送UDP封包
       BOOL Send(char *szText, int nLen, DWORD dwRemoteAddr);
 
protected:
              // 具体实现
       // 处理到来的封包
       void DispatchMsg(GT_HDR *pHeader, int nLen);
       // 工作线程
       friend DWORD WINAPI _GroupTalkEntry(LPVOID lpParam);      CInitSock theSock;
 
       HWND m_hNotifyWnd;           // 主窗口句柄
       DWORD m_dwMultiAddr;       // 此组使用的多播地址
       DWORD m_dwLocalAddr;      // 用户要使用的本地接口
       int m_nTTL;                       // 多播封包的TTL
       HANDLE m_hThread;              // 工作线程句柄
       HANDLE m_hEvent;         // 事件句柄,用来使用重叠I/O接收数据
 
       SOCKET m_sRead;                 // 接收数据的套节字,它必须加入多播组
       SOCKET m_sSend;                  // 发送数据的套节字,不必加入多播组
 
       BOOL m_bQuit;                // 用来通知工作线程退出
      
       char m_szUser[256];          // 用户名
};
下面对类中的几个重要函数进行说明:
BOOL CGroupTalk::JoinGroup()
{
       // 加入会议组
       ip_mreq mcast;
       mcast.imr_interface.S_un.S_addr = INADDR_ANY;
       mcast.imr_multiaddr.S_un.S_addr = m_dwMultiAddr;
       int nRet = ::setsockopt(m_sRead, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
      
       if(nRet != SOCKET_ERROR)
       {
              // 向组中所有成员发送MT_JION消息,告诉它们自己的用户信息
              char buf[sizeof(GT_HDR)] = { 0 };
              GT_HDR *pHeader = (GT_HDR *)buf;
              pHeader->gt_type = MT_JION;
              strncpy(pHeader->szUser, m_szUser, 15);
              Send(buf, sizeof(GT_HDR), m_dwMultiAddr);
              return TRUE;
       }
       return FALSE;
}
1. 
加入和离开可以使用setsockop函数,又两个套接字选项控制组的加入和离开:IP_ADD-MEMBERSHIPIP_DROP_MEMBERSHIP,套接字选项的级别是IPPROTO_IP,输入参数是一个ip_mreqj结构;
定义如下:
struct ip_mreq {
       struct in_addr imr_multiaddr;    /* IP multicast address of group */
       struct in_addr imr_interface;      /* local IP address of interface */
};
int setsockopt (
 SOCKET s,                 
  int level,                
  int optname,              
  const char FAR * optval
  int optlen                
);
第一个参数是要改变的套接字,第二个参数是指定此选项(控制功能)定义在哪个级别,如SOL_SOCKT,(应用层)IPPROTO_TCP(传输层),IPPROTO_IP(网络层)
第三个参数就是关于具体选项的名称,也就是要实现的某项的功能,如代码所示levelIPPROTO_IPIP-ADD-MEMERSHIP是说增加一个套接字到IP多播组中。第四个参数缓冲区内所有选项的值都被返回到这里。
2.
 
例如加入一个新成员时,它向组的所有成员发送MT_JOIN消息,告诉他们自己的IP地址和用户名,其他在线成员接受到这个消息之后,纷纷向新成员发送MT_MINE消息,告诉他自己的IP地址和用户名,这样一来,每个成员就都知道了组中的其他成员的信息。
为了实现这一功能,必须首先定义一个简单的协议,也就是定义消息格式。
const enum
{
       MT_JION = 1,    // 一个用户加入
       MT_LEAVE,        // 一个用户离开
       MT_MESG,         // 一个用户发送消息
 
       // 内部使用,告诉新加入的用户自己的用户信息
       MT_MINE
};
只要有成员加入或者离开这个组,MT_JION或者MT_LEAVE封包就会被送到组,以便所有的成员都可以动态跟踪在线成员。
   用户发送的封包因该还有消息类型,自己的IP地址,用户名等信息,所有协议的定义如下;
Typedef struct gt_hdr
{
      Unsigned char gt_type;    //消息类型
      DWORD dwAddr;        //发送消息的用户名和IP地址
      Char szUser[15];         //发送消息的用户的用户名
      Int nDataLength;        //后面数据的长度
     Char * data(){return (char*)(this+1);}
 
}GT_HDR;
下面是:LeaveGroup ()JoinGroup类似Send函数对套接字的API比较好理解
 
BOOL CGroupTalk::LeaveGroup ()
{
       // 离开会议组
       ip_mreq mcast;
       mcast.imr_interface.S_un.S_addr = INADDR_ANY;
       mcast.imr_multiaddr.S_un.S_addr = m_dwMultiAddr;
       int nRet = ::setsockopt(m_sRead, IPPROTO_IP, IP_DROP_MEMBERSHIP, (char*)&mcast, sizeof(mcast));
 
       if(nRet != SOCKET_ERROR)
       {
              // 向组中所有成员发送MT_LEAVE消息,告诉它们自己离开了
              char buf[sizeof(GT_HDR)] = { 0 };
              GT_HDR *pHeader = (GT_HDR *)buf;
              pHeader->gt_type = MT_LEAVE;
              strncpy(pHeader->szUser, m_szUser, 15);
              Send(buf, sizeof(GT_HDR), m_dwMultiAddr);
              return TRUE;
       }
       return FALSE;
}
 
int CGroupTalk::Send(char *szText, int nLen, DWORD dwRemoteAddr)
{
       // 发送UDP封包
       sockaddr_in dest;
       dest.sin_family = AF_INET;
       dest.sin_addr.S_un.S_addr = dwRemoteAddr;
       dest.sin_port = ::ntohs(GROUP_PORT);
       return ::sendto(m_sSend, szText, nLen, 0, (sockaddr*)&dest, sizeof(dest));
}
 
原创粉丝点击