⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 grouptalk.cpp

📁 基于IP多播的组讨论会实例,程序运行之后即会自动加入会议组,组中的每个成员都可以接收到你发送的消息,聊天记录窗口下显示组中每个用户发送的消息。
💻 CPP
字号:
////////////////////////////////////////
// GroupTalk.cpp文件

#include "GroupTalk.h"
#include <windows.h>
#include <Ws2tcpip.h>	// IP_MULTICAST_TTL、IP_MULTICAST_IF都定义在Ws2tcpip.h文件



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;
}

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));
}


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);
}


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;
}

// 处理到来的消息,将它们分发到主窗口
void CGroupTalk::DispatchMsg(GT_HDR *pHeader, int nLen)
{
	if(pHeader->gt_type == MT_JION)	// 新用户加入
	{	
		// 向新加入用户发送自己的用户信息
		char buff[sizeof(GT_HDR)] = { 0 };
		GT_HDR *pSend = (GT_HDR*)buff;
		strncpy(pSend->szUser, m_szUser, 15);	
		pSend->gt_type = MT_MINE;
		pSend->nDataLength = 0;
		Send(buff, sizeof(GT_HDR), pHeader->dwAddr);
	}
	else if(pHeader->gt_type == MT_MINE)
	{	
		// 是否来自自己,如果是,则不处理
		if(strcmp(pHeader->szUser, m_szUser) == 0)
			return;
		// 为简单起见,把在线用户当成新加入用户处理
		pHeader->gt_type = MT_JION;
	}

	// 通知主窗口处理
	::SendMessage(m_hNotifyWnd, WM_GROUPTALK, 0, (LPARAM)pHeader);
}



int CGroupTalk::SendText(char *szText, int nLen, DWORD dwRemoteAddr)
{
	// 构建消息封包
	char buf[sizeof(GT_HDR) + 1024] = { 0 };
	GT_HDR *pHeader = (GT_HDR *)buf;
	pHeader->gt_type = MT_MESG;
	pHeader->nDataLength = nLen < 1024 ? nLen : 1024;
	strncpy(pHeader->szUser, m_szUser, 15);
	strncpy(pHeader->data(), szText, pHeader->nDataLength);

	// 发送此封包
	int nSends = Send(buf, pHeader->nDataLength + sizeof(GT_HDR), dwRemoteAddr == 0 ? m_dwMultiAddr : dwRemoteAddr);
	return nSends - sizeof(GT_HDR);
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -