实现过程上一篇已经写了,这里就是具体的代码实现过程了。先看一下主窗口的界面:
中间的表格就是一个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);
}