MFC基于对话框框架的简易飞鸽系统(二)----获取局域网内运行同样程序的主机名和IP

实现过程上一篇已经写了,这里就是具体的代码实现过程了。先看一下主窗口的界面:

 

中间的表格就是一个List Control控件。要注意所有主机必须得在同一局域网内,而且必须得连着网线。

FeiDlg.h:

// FeiGeDlg.h: 头文件
//

#pragma once
#include "afxcmn.h"
#include <WinSock2.h>  
#include <stdio.h>  
#include <iostream>   
#include "afxwin.h"
#include "SecWin.h"
using namespace std;

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

#define GET_HOST_COMMAND "GetIPAddr"
const int MAX_BUF_LEN = 255;
#define SERVER_PORT 8849  
#define CLIENT_PORT 8859

// CFeiGeDlg 对话框
class CFeiGeDlg : public CDialog
{
// 构造
public:
	CFeiGeDlg(CWnd* pParent = NULL);	// 标准构造函数

// 对话框数据
#ifdef AFX_DESIGN_TIME
	enum { IDD = IDD_FEIGE_DIALOG };
#endif

	protected:
	virtual void DoDataExchange(CDataExchange* pDX);	// DDX/DDV 支持



// 实现
protected:
	HICON m_hIcon;

	// 初始化主窗口
	virtual BOOL OnInitDialog();

	// 接受UDP广播的线程
	static DWORD WINAPI RecvProc(LPVOID lpParameter);
	// 发送UDP广播的线程
	static DWORD WINAPI SendProc(LPVOID lpParameter);

	DECLARE_MESSAGE_MAP()
public:
	
	afx_msg void OnSendText();
	afx_msg void OnBnClickedRefresh();
	
	afx_msg void OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult);
	afx_msg void OnDblclkList1(NMHDR *pNMHDR, LRESULT *pResult);

	afx_msg void OnSendToAll();

	afx_msg void OnExit();
	afx_msg void On32774();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
};

FeiGeDlg.cpp:

// FeiGeDlg.cpp: 实现文件
//

#include "stdafx.h"
#include "FeiGe.h"
#include "FeiGeDlg.h"
#include "afxdialogex.h"
#include "SecWin.h"
#include "GroupChatDlg.h"
#include <vector>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif

// 展示局域网内运行同样程序的主机的ip和主机名
static CListCtrl m_List; 
// 存放获取到的主机ip地址
static std::string ip_List[20]; 
// 获取到的主机ip数量
static int host_num = 0; 
						



// CFeiGeDlg 对话框

CFeiGeDlg::CFeiGeDlg(CWnd* pParent /*=NULL*/)
	: CDialog(IDD_FEIGE_DIALOG, pParent)
{
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CFeiGeDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	DDX_Control(pDX, IDC_LIST1, m_List);
}
// 消息映射
BEGIN_MESSAGE_MAP(CFeiGeDlg, CDialog)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_COMMAND(ID_SEND_TEXT, &CFeiGeDlg::OnSendText)
	ON_BN_CLICKED(IDC_REFRESH, &CFeiGeDlg::OnBnClickedRefresh)

	ON_NOTIFY(LVN_ITEMCHANGED, IDC_LIST1, &CFeiGeDlg::OnLvnItemchangedList1)
	ON_NOTIFY(NM_DBLCLK, IDC_LIST1, &CFeiGeDlg::OnDblclkList1)

	ON_COMMAND(ID_SEND_TO_ALL, &CFeiGeDlg::OnSendToAll)

	ON_COMMAND(ID_EXIT, &CFeiGeDlg::OnExit)
	ON_COMMAND(ID_32774, &CFeiGeDlg::On32774)
	ON_WM_TIMER()
END_MESSAGE_MAP()

// 以字符串为分隔符分割某一个字符串 
// 将结果以一个vector<CString>形式返回
vector<CString> split(CString strSource, CString ch) {
	vector<CString> result;
	int iPos = 0;
	CString strTmp;
	strTmp = strSource.Tokenize(ch, iPos);
	while (strTmp.Trim() != _T(""))
	{
		result.push_back(strTmp);
		strTmp = strSource.Tokenize(ch, iPos);
	}
	return result;
}

// 获取本地主机的ip

bool GetLocalIP(char* ip)
{
	//1.初始化wsa  
	WSADATA wsaData;
	int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
	//2.获取主机名  
	char hostname[256];
	 ret = gethostname(hostname, sizeof(hostname));
	if (ret == SOCKET_ERROR)
	{
		return false;
	}
	//3.获取主机ip  
	HOSTENT* host = gethostbyname(hostname);
	if (host == NULL)
	{
		return false;
	}
	//4.转化为char*并拷贝返回  
	strcpy(ip, inet_ntoa(*(in_addr*)*host->h_addr_list));
	::WSACleanup();
	return true;
}

// 获取本地主机的主机名
std::string GetLocalHostName(char* ip)
{
	//1.初始化wsa  
	WSADATA wsaData;
	int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
	//2.获取主机名  
	char hostname[256];
	ret = gethostname(hostname, sizeof(hostname));
	if (ret == SOCKET_ERROR)
	{
		return false;
	}
	::WSACleanup();
	return hostname;
}


// 该线程会在初始化对话框时启动,
// 该线程专门用来接收udp广播消息
// 如果接收到广播就将自己的主机名和ip发回去
DWORD WINAPI CFeiGeDlg::RecvProc(LPVOID lpParameter)
{
	// 初始化Winsock库
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 0);
	::WSAStartup(sockVersion, &wsaData);
	int m_nPort = SERVER_PORT;
	SOCKET sClient;
	sockaddr_in clientAddr, bindAddr;
	//用UDP初始化套接字
	sClient = socket(AF_INET, SOCK_DGRAM, 0);
	BOOL optval = TRUE;
	bindAddr.sin_family = AF_INET;
	bindAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	bindAddr.sin_port = htons(m_nPort);
	// 设置套接字为广播类型
	setsockopt(sClient, SOL_SOCKET, SO_BROADCAST, (char FAR *)&optval, sizeof(optval));
	// 给套接字绑定地址
	bind(sClient, (sockaddr *)&bindAddr, sizeof(sockaddr_in));

	int nAddrLen = sizeof(SOCKADDR);
	char buf[256] = { 0 };
	int fromlength = sizeof(SOCKADDR);
	char ipaddr[30] = { 0 };
	char name[30] = { 0 };
	char buff[MAX_BUF_LEN] = "";
	std::string hostInf;
	if (GetLocalIP(ipaddr))
	{
		// 将本机的主机名和ip放到一个字符串中
		hostInf += GetLocalHostName(name);
		hostInf += "!";
		hostInf += ipaddr;
	}
	else
	{
		AfxMessageBox("获取ip失败", MB_OK, 0);
	}
	while (true)
	{

		int nRet = recvfrom(sClient, buf, 256, 0, (struct sockaddr FAR *)&clientAddr, (int FAR *)&fromlength);
		if (SOCKET_ERROR != nRet)
		{
			char    *pIPAddr = inet_ntoa(clientAddr.sin_addr); // 解析得到发送广播消息的客户端ip
			if (strcmp(buf, GET_HOST_COMMAND) != 0)
			{
				// 如果接收到的广播消息不是 GetIPAddr的话,就不对这个广播消息做处理,结束这一次的循环
				continue;
			}
			else
			{
				// 如果接收的广播消息是 GetIPAddr的话,就将自己的主机名和IP发回去
				// 发送数据   
				int nSendSize = sendto(sClient, hostInf.c_str(), strlen(hostInf.c_str()), 0, (SOCKADDR*)&clientAddr, nAddrLen);
				if (SOCKET_ERROR == nSendSize)
				{
					int err = WSAGetLastError();
					AfxMessageBox("发送ip信息失败", MB_OK, 0);
					return false;
				}
			}
		}
		else
		{
			AfxMessageBox("接收UDP信息失败", MB_OK, 0);
		}
		Sleep(100);
	}
	closesocket(sClient);
	// 释放Winsock库
	::WSACleanup();
	return 0;
}



// 该线程在用户点击刷新按钮时启动
// 该线程专门用来发送udp广播消息
// 该线程接收到的广播消息就是别的主机发过来的主机名和ip地址
DWORD WINAPI CFeiGeDlg::SendProc(LPVOID lpParameter)
{
	// TODO: 在此添加控件通知处理程序代码
	int nPort = SERVER_PORT;
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	// 启动socket api   
	wVersionRequested = MAKEWORD(2, 2);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		AfxMessageBox("不支持2.2版本", MB_OK);
	}
	// 创建socket   

	SOCKET connect_socket  = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (INVALID_SOCKET == connect_socket)
	{
		AfxMessageBox("UDP套接字创建错误", MB_OK);
	}

	// 用来绑定套接字   
	SOCKADDR_IN sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(CLIENT_PORT);
	sin.sin_addr.s_addr = htonl(INADDR_ANY);

	// 用来从网络上的广播地址接收数据   
	SOCKADDR_IN sin_from;
	sin_from.sin_family = AF_INET;
	sin_from.sin_port = htons(SERVER_PORT);
	sin_from.sin_addr.s_addr = INADDR_BROADCAST;

	//设置该套接字为广播类型,   
	BOOL bOpt = TRUE;
	setsockopt(connect_socket, SOL_SOCKET, SO_BROADCAST, (char FAR *)&bOpt, sizeof(bOpt));

	// 绑定套接字   
	err = bind(connect_socket, (SOCKADDR*)&sin, sizeof(SOCKADDR));
	if (SOCKET_ERROR == err)
	{
		err = WSAGetLastError();
		

	}
	int nAddrLen = sizeof(SOCKADDR);
	char buff[MAX_BUF_LEN] = "";
	int nLoop = 0;

	char    szMsg[] = GET_HOST_COMMAND;
	int nLen = sizeof(sin_from);
	
	if (SOCKET_ERROR == sendto(connect_socket, szMsg, strlen(szMsg), 0, (sockaddr*)&sin_from, nLen))
	{

		AfxMessageBox("发送UDP信息错误", MB_OK);
	}
	
	while (true)
	{
		// 接收数据   
		int nRecvSize = recvfrom(connect_socket, buff, MAX_BUF_LEN, 0, (SOCKADDR*)&sin_from, &nAddrLen);
		if (SOCKET_ERROR == nRecvSize)
		{
			AfxMessageBox("接收UDP信息错误", MB_OK);
		}

		buff[nRecvSize] = '\0';
		// 将接收的主机信息插入到列表中显示
		std::string ipList = buff;
		int length = m_List.GetItemCount();
		m_List.InsertItem(length, split(buff, "!")[0]);
		m_List.SetItemText(length,1, split(buff, "!")[1]);
		ip_List[host_num] = split(buff, "!")[1]; // 将获取的ip存放到数组里
		host_num += 1;
		// 默认接受UDP广播消息的端口都是8849
		m_List.SetItemText(length, 2, "8849");
	}
	closesocket(connect_socket);
	::WSACleanup();
	return 0;
}


// CFeiGeDlg 消息处理程序

BOOL CFeiGeDlg::OnInitDialog()
{
	
	CDialog::OnInitDialog();

	// TODO: 在此添加额外的初始化代码
	// 初始化表格
	m_List.ModifyStyle(0, LVS_REPORT);               // 报表模式   
	m_List.SetExtendedStyle(m_List.GetExtendedStyle() | LVS_EX_GRIDLINES | LVS_EX_CHECKBOXES); 	// 间隔线 +复选框
	// 插入表头
	m_List.InsertColumn(0, "主机名");
	m_List.InsertColumn(1, "IP");
	// 所有的端口号都是写死的8849,其实并没有将端口号传输过来
	m_List.InsertColumn(2, "端口号");
	CRect rect;
	m_List.GetClientRect(rect); //获得当前客户区信息   
	m_List.SetColumnWidth(0, rect.Width() / 3); //设置列的宽度。   
	m_List.SetColumnWidth(1, rect.Width() / 3);
	m_List.SetColumnWidth(2, rect.Width() / 3);

	// 启动接收广播信息的线程
	HANDLE hThread = CreateThread(NULL, 0, RecvProc, NULL, 0, NULL);

	return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE  
}

// 向一个客户发送文本信息
void CFeiGeDlg::OnSendText()
{
	// TODO: 在此添加命令处理程序代码
	// 先判断有没有选中一个ip
	bool b_selected = false;
	std::string ip; // 选中项的ip
	std::string name; // 选中项的主机名
	for (int i = 0; i < m_List.GetItemCount(); i++)
	{
		if (m_List.GetCheck(i) == 1)
		{
			ip = m_List.GetItemText(i, 1);
			name = m_List.GetItemText(i, 0);
			b_selected = true;
			break;
		}
	}
	if (!b_selected)
	{
		MessageBox("请先选中一个用户");
	}
	else
	{
		// 打开子对话窗口
		SecWin chattingDlg;
                // 将要进行对话的主机的主机名和IP传递给对话窗口
                 chattingDlg.client_ip = ip;
		chattingDlg.client_name = name;
		chattingDlg.DoModal();
		
	}

}

// 刷新局域网内的主机信息列表
void CFeiGeDlg::OnBnClickedRefresh()
{
	m_List.DeleteAllItems();
	// 通过创建子线程来收发udp信息
	HANDLE sendThread = CreateThread(NULL, 0, SendProc, NULL, 0, NULL);
	//0.1s后,线程退出  
	WaitForSingleObject(sendThread, 100);
	
}

// 列表的复选框状态发生改变
// 保证最多只能选中一个ip,因为只能同时与一个接收方通信
void CFeiGeDlg::OnLvnItemchangedList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	POSITION ps;
	int nIndex;
	ps = m_List.GetFirstSelectedItemPosition();
	nIndex = m_List.GetNextSelectedItem(ps);//nIndex为选中的列表项Item值  

	CString ip = m_List.GetItemText(nIndex + 1, 1);
	CString name = m_List.GetItemText(nIndex + 1, 0);
	if (m_List.GetCheck(nIndex + 1))
	{
		for (int i = 1; i <= m_List.GetItemCount(); i++)
		{
			if (i != nIndex + 1)
			{
				m_List.SetCheck(i, false);
			}
			
		}
	}
	else
	{
		m_List.SetCheck(nIndex + 1, false);
	}

	*pResult = 0;
}

// 双击打开子对话窗口
void CFeiGeDlg::OnDblclkList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	LPNMITEMACTIVATE pNMItemActivate = reinterpret_cast<LPNMITEMACTIVATE>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	POSITION ps;
	int nIndex;
	ps = m_List.GetFirstSelectedItemPosition();
	nIndex = m_List.GetNextSelectedItem(ps);//nIndex为选中的列表项Item值  
	CString ip = m_List.GetItemText(nIndex+1, 1);
	CString name = m_List.GetItemText(nIndex + 1, 0);

	SecWin chattingDlg; 
 // 将要进行对话的主机的主机名和IP传递给对话窗口

        chattingDlg.client_ip = ip;
        chattingDlg.client_name = name;
        chattingDlg.DoModal();*pResult = 0;}
// 打开群对话窗口
void CFeiGeDlg::OnSendToAll()
{
    // TODO: 在此添加命令处理程序代码
    GroupChatDlg groupDlg;
    CString ips, names;
    for (int i = 0; i < m_List.GetItemCount(); i++)
    {
        if (i == m_List.GetItemCount() - 1)
        {
        ips += m_List.GetItemText(i, 1);
        names += m_List.GetItemText(i, 0);
        }
        else
        {
        ips += m_List.GetItemText(i, 1);
        ips += "!";
        names += m_List.GetItemText(i, 0);
        names += "!";
        }
    }
// 将获取的所有主机名和IP传递给群对话窗口
        groupDlg.group_names = names;
        groupDlg.group_ips = ips;groupDlg.DoModal();
}
// 点击退出选项
void CFeiGeDlg::OnExit()
{   
    // TODO: 在此添加命令处理程序代码
    // 退出程序
    exit(0);
}
// 点击关于选项
void CFeiGeDlg::On32774()
{
    // TODO: 在此添加命令处理程序代码
    AfxMessageBox("初次使用本程序者请在工作人员帮助下熟悉本程序!", MB_OK);
}
void CFeiGeDlg::OnTimer(UINT_PTR nIDEvent)
{
    // TODO: 在此添加消息处理程序代码和/或调用默认值
    CDialog::OnTimer(nIDEvent);
}

    
      
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值