UDP协议开发小结

//局域网内的聊天软件,显示局域网内在线用户
//没有客户端和服务器之分
enum{UPORT = 8880};//固定连接端口
enum{						//命令号
	SEND_HELLO = 0x1234,	//局域网内某用户上线后,会向其他主机发送该命令(打招呼)
	REPL_HELLO,				//收到打招呼信息后,回复该命令
	SEND_BYE,				//当某主机下线后,会向其他主机发送该命令
	SEND_TEXT,				//发送文字的命令
	SEND_FILE				//发送文件的命令
};
struct SInfo 				//存放本机的信息,主机名,用户名,IP地址,用户组
{
	char sHost[16];			
	char sName[16];
	char sIp[16];
	char sGroup[16];
};
struct SPack//作为接受/发送的数据包,命令号+数据,防止丢包
{
	int nCmd;
	char sData[1020];
};
struct SText//文字信息,包含主机名和文字
{
	char sName[16];
	char sText[256];
};

//APP初始化程序中需要完成的事情:
SInfo & info = m_info;					   //定义一个本机信息结构体的引用
gethostname(info.sHost,sizeof(info.sHost));//获取本机名
//int gethostname (char *name, int namelen ); 缓冲区以及其长度,返回0表示获取成功,返回SOCKET_ERROR表示失败,WSAGetLastError(),获取失败代码
hostent* pHost = gethostbyname(info.sHost);//获取与该主机名相对应的主机信息,返回一个结构体
/*
struct hostent {
	char FAR *h_name;		//主机名
	char FAR *FAR *h_aliases;		//域名的别名
	short h_addrtype;		//返回地址的类型IP4 or IP6
	short h_length;		//地址字节数4 or 6
	char FAR * FAR * h_addr_list;	//主机地址列表,该值是网络字节序,一般取第一个
}; 
*/
in_addr* addr = (in_addr*)pHost->h_addr;//调用返回结构体中的IP地址,该值是网络字节序
//这里用了类型转换,h_addr_list[0] 就是h_addr,他指向的in_addr结构体的地址值,他是个指针的指针
strcpy(info.sIp,inet_ntoa(*addr));//网络字节转字符串,并复制到本机信息结构体中
DWORD dw = sizeof(info.sName);
GetUserName(info.sName,&dw);//获取本机用户名
/*
BOOL GetUserName(
  LPTSTR lpBuffer,  // name bufferm用户名缓冲区
  LPDWORD nSize     // size of name buffer 缓冲区大小的地址,DWORD型
);
*/
CString sIP;
SPack pack = {SEND_HELLO};
memcpy(pack.sData,&info,sizeof(SInfo));//将info里的信息放入发送包里
//关于复制内存
//memcpy、strcpy 都是拷贝内存,但前者根据长度,后者拷贝到'\0'while(i<255)
//向局域网内其他253个主机打招呼,该方式运行速度较慢
while(i<255)
{
	sIP.Format("192.168.1.%d",i);//若接收方没有该软件接受则接受程序会返回一个错误码
	m_sock.SendTo(&pack,sizeof(int)+sizeof(SInfo),UPORT,sIP);
	++i;
}

/*
int SendTo( 
const void* lpBuf, 	//发送数据的缓冲区
int nBufLen, 		//缓冲区待发送的字节数
UINT nHostPort, 	//端口号
LPCTSTR lpszHostAddress = NULL, 	//连接到的套接字的网络地址
int nFlags = 0 );	//设置函数调用方式
*/

//该方式运行速度快
sIP = info.sIp;//通过广播地址发送。
int i=sIP.ReverseFind('.');//找到字符串中最后一个.号的位置,从0 开始192.168.1.即9,这样做的前提是不知道IP地址
sIP = sIP.Left(i+1)+"255";//选取该字符串左十位,与"255"合并,构成广播地址。
m_sock.SendTo(&pack,sizeof(int)+sizeof(SInfo),UPORT,sIP);//发送给广播地址,广播地址群发给局域网内所有主机

//对话框初始函数,设置列表控件的风格:
m_list.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);//整行选取+网格线

//信息接收函数,重载了基类CSocket::OnReceive(nErrorCode)
void CSocketu::OnReceive(int nErrorCode) 
{
	// TODO: Add your specialized code here and/or call the base class
	SPack pack={0};//定义一个pack接受数据
	CString sIP;
	UINT nPort;
	int nRet = ReceiveFrom(&pack,sizeof(pack),sIP,nPort);//第二个参数为缓冲区的长度,有别于发送函数
	if(nRet <= 0)//接受失败,若对方主机没有正确接收的数据包(对方主机没有安装该软件)也会返回也一个错误码:c10054
	{
		int nError = GetLastError();
		return;
	}
	CUdpsocketDlg *pDlg = (CUdpsocketDlg*)AfxGetMainWnd();
	switch(pack.nCmd)
	{
	case SEND_HELLO://如果是有主机上线的消息
		{
			pDlg->InsertHost(pack);//插入列表控件
			SInfo& info = (SInfo&)pack.sData;
 			if(!strcmp(info.sHost,theApp.m_info.sHost))//判断是否是本机发来的消息
				return;
			pack.nCmd = REPL_HELLO;//将发来的消息包中的内容替换成本机的信息
			info = theApp.m_info;
			SendTo(&pack,sizeof(int)+sizeof(SInfo),nPort,sIP);//替换之后发回去
		}
		break;
	case REPL_HELLO:
		pDlg ->InsertHost(pack);//接受到回复后插入列表
		break;
	case SEND_BYE:
		pDlg->RemoveHost(sIP);//有用户退出则移除他在列表控件上的名字
		break;
	case SEND_TEXT:
		pDlg->OnText(pack);
		break;
	}
//	CSocket::OnReceive(nErrorCode);
}

//往列表控件中插入上线主机信息
void CUdpsocketDlg::InsertHost(SPack &pack)
{
	SInfo& info = (SInfo&)pack.sData;
	//将SDate强制转换为SInfo的一个引用,sDate里就存了一个info的结构体大小的内容
	CString sHost = info.sHost;
	int i = -1;
	int nCount = m_list.GetItemCount();//获取控件中信息行数
	while(++i<nCount)//遍历列表,如果当前列表存在该主机名,则退出
	{
		if(m_list.GetItemText(i,0) == sHost)
			break;
	}
	if(i == nCount)//若不存在,则插入
	{ 
		m_list.InsertItem(i,info.sHost);
		m_list.SetItemText(i,1,info.sName);
		m_list.SetItemText(i,2,info.sIp);
	}
}

//双击列表控件中的信息,弹出一个聊天对话框
void CUdpsocketDlg::OnDblclkList(NMHDR* pNMHDR, LRESULT* pResult) //定义一个双击的消息响应
{
	// TODO: Add your control notification handler code here
	NM_LISTVIEW* p = (NM_LISTVIEW*) pNMHDR; //参数pNMHDR中包含了点击项的在列表中的位置信息,转为NM_LISTVIEW*类型
	/*	
		typedef struct tagNMLISTVIEW
		{
			NMHDR   hdr;
			int     iItem;
			int     iSubItem;
			UINT    uNewState;
			UINT    uOldState;
			UINT    uChanged;
			POINT   ptAction;
			LPARAM  lParam;
		} NMLISTVIEW, FAR *LPNMLISTVIEW;
	*/
	int nSel = p->iItem; //获取选中的行数
	if(nSel<0)			//未选中
		return;
	CChatDlg *pDlg = (CChatDlg *)m_list.GetItemData(nSel);//该函数获取列表中该项信息的附加信息,一旦对该项有过操作,该附加信息就不为空
	if(!pDlg)	//若没有点击过该项
	{	
		pDlg = new CChatDlg;		// 创建一个聊天对话框对象
		SInfo& info = pDlg->m_info;	
		m_list.GetItemText(nSel,0,info.sHost,sizeof(info.sHost)); //获取该项的中每一列的内容,放进info中
		m_list.GetItemText(nSel,1,info.sName,sizeof(info.sName));
		m_list.GetItemText(nSel,2,info.sIp,sizeof(info.sIp));
		pDlg->Create(IDD_CHATDLG,GetDesktopWindow());//创建非模式对话框(该对话框未关闭前可以对其他对话框的操作),父窗口为桌面窗口
		//GetDesktopWindow()返回桌面窗口的句柄
		m_list.SetItemData(nSel,(DWORD)pDlg);//将该信息附加到对应项上
	}	
	pDlg->ShowWindow(SW_SHOW);//以当前大小和位置显示对话框
	pDlg->SetForegroundWindow();//将该窗口设置为前台窗口
	*pResult = 0;
}

//发送消息消息响应
void CChatDlg::OnOK() 
{
	// TODO: Add extra validation here
	SPack pack={SEND_TEXT};
	SText& text = (SText &)pack.sData;//强制转为text引用,因为发送文字只需SText结构体大小
	int nSel = GetDlgItemText(IDC_SEND,text.sText,sizeof(text.sText));//返回复制到缓冲区的文字个数
	if(nSel<=0)//获取发送控件里的文字
	{
		AfxMessageBox("不能发送空文字!");
		return;
	}
	strcpy(text.sName,theApp.m_info.sHost);
	theApp.m_sock.SendTo(&pack,sizeof(int)+sizeof(SText),UPORT,m_info.sIp);//发送本机名和文字
	COleDateTime time = COleDateTime::GetCurrentTime();
	CString str;//将自己发的文字显示界面
	str.Format("你对 %s 说: (%02d:%02d:%02d) \r\n%s\r\n",m_info.sHost,
		time.GetHour(),time.GetMinute(),time.GetSecond(),text.sText);
	CEdit *pEdit = (CEdit*)GetDlgItem(IDC_LIST);//获取列表控件的句柄
	pEdit->SetSel(pEdit->GetWindowTextLength(),-1);
	pEdit->ReplaceSel(str);
	SetDlgItemText(IDC_SEND,"");
	GetDlgItem(IDC_SEND)->SetFocus();
	//CDialog::OnOK();
}

//接受到文字时的处理函数
void CUdpsocketDlg::OnText(SPack &pack)
{
	SText &text = (SText&)pack.sData;//强制引用
	int i = -1;
	int nCount = m_list.GetItemCount();
	while(++i<nCount)
	{
		if(m_list.GetItemText(i,0) == text.sName)//如果列表中存在该主机
		{
			CChatDlg * pDlg = (CChatDlg*)m_list.GetItemData(i);//获取该项的附件信息
			if(!pDlg)//如果消息为空,即没有打开过该聊天窗口
			{
				pDlg = new CChatDlg;//创建新的对话框
				SInfo& info = pDlg->m_info;
				m_list.GetItemText(i,0,info.sHost,sizeof(info.sHost));
				m_list.GetItemText(i,1,info.sName,sizeof(info.sName));
				m_list.GetItemText(i,2,info.sIp,sizeof(info.sIp));
				pDlg->Create(IDD_CHATDLG,GetDesktopWindow());
				m_list.SetItemData(i,(DWORD)pDlg);
			}
			pDlg->ShowWindow(SW_SHOW);
			pDlg->SetForegroundWindow();
			pDlg->FlashWindow(true);//使通知栏窗口闪烁
			CString str;
			COleDateTime time = COleDateTime::GetCurrentTime();
			str.Format("%s 对你说 %d:%d:%d \r\n %s \r\n",text.sName,time.GetHour(),time.GetMinute(),time.GetSecond(),text.sText);
			CEdit* pEdit = (CEdit *)pDlg->GetDlgItem(IDC_LIST1);
			pEdit->SetSel(pEdit->GetWindowTextLength(),-1);
			pEdit->ReplaceSel(str);
			return;
		}
	}
}

//App退出函数
int CUdpsocketApp::ExitInstance() //退出进程函数
{
	SPack pack = {SEND_BYE};
	CString sIP = m_info.sIp;
	int i = sIP.ReverseFind('.');
	sIP = sIP.Left(i+1)+"255";
	m_sock.SendTo(&pack,sizeof(int),UPORT,sIP);//发送BYE消息到局域网内其他主机
	return CWinApp::ExitInstance();
}

//删除该IP所占的一行
void CUdpsocketDlg::RemoveHost(CString szIP)
{
	int i = 0;
	int nCount = m_list.GetItemCount();
	while(i++<nCount)
	{
		CString s = m_list.GetItemText(i,2);
		if(m_list.GetItemText(i,2) == szIP)
		{
			m_list.DeleteItem(i);
		}
	}
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值