p2p即时聊天系统
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
一、课程设计题目
基于P2P的局域网即时通信系统
二、实验环境及工具
1.计算机:PC机,PC虚拟机,
2.操作系统:Windows2000,WindowsXP
3.程序设计语言:VC 6.0
三、设计要求
1.实现一个图形用户界面局域网内的消息系统。
2.功能:建立一个局域网内的简单的P2P消息系统,程序既是服务器又是客户,服务器端口使用3333。
a)用户注册及对等方列表的获取:对等方A启动后,用户设置自己的
信息(用户名,所在组);扫描网段中在线的对等方(3333端口打
开),向所有在线对等方的服务端口发送消息,接收方接收到消息
后,把对等方A加入到自己的用户列表中,并发应答消息;对等方
A把回应消息的其它对等方加入用户列表。
双方交换的消息格式自
己根据需要定义,至少包括用户名、IP地址。
b)发送消息和文件:用户在列表中选择用户,与用户建立TCP连接,
发送文件或消息。
3.用户界面:界面上包括对等方列表;消息显示列表;消息输入框;文件传输进程显示及操作按钮或菜单。
四、设计内容与步骤
1.学习Socket和TCP的基本原理和通信机制;
2.功能设计和界面设计
3.服务器功能的设计和实现
4.客户功能的设计和实现
5.课程设计任务说明书
五、方案设计
1.消息格式
本系统采用的消息格式是,文件头+消息内容
文件头为‘1’-‘9’,消息格式分配如下:
‘1’+本机名:登陆,发送给所有在线对等方的服务端口
‘2’+本机名:对登陆消息的回馈
‘3’+本机名:退出
‘4’+本机名:对话请求
“51”或”52”:对话请求的回应(是否同意)
‘6’+本机名+”退出对话”:退出对话
‘7’+对话内容:对话
‘8’+文件名长度+文件名+文件长度(转换成CString):请求传送
“91”同意传输
“92”拒绝
“93”磁盘已满
2.该软件分别开了3个监听端口:3333、3334、3335。
之所以分开3个端口是因为各种传送的不同,在设计实验的过程中我发现对于登陆消息,
退出消息,应该用的socket是即用即断,即比如我收到登陆消息,并发
送回馈消息后就断开连接,这样就不用一个用户同时连接很多用户,如
果用完不断,就是全连接了。
而文件传输应该跟对话传输分开,因此应
该再开一个端口。
3.在线用户的扫描:
本软件是通过扫描局域网内的在线用户(不一定打开软件),然后一一
发送登陆信息,如果收到登陆信息就在列表上增加用户并发送回馈,如
果收到回馈就在列表上增加用户,如果收到退出消息就删除用户。
4.文件传输
原本打算使用多线程文件传输,及发送端开多个线程同时读一个文件并
发送,接收端在磁盘开辟一个与接收文件大小一致的一个文件,然后接
收端开多个线程接收并各自负责写进特定文件位置,不过由于Socket匹
配问题,因此还是使用单线程传输比较简单一点。
六、方案实现及主要程序
1.工程中的类
(1).本软件中分别有三个CAsyncSocket的派生类,分别是C CtrlSocket,
CTalkSocket,CFileSocket
a)CCtrlSocket:用于接收及发送控制信息,包括文件头为‘1’(登陆);
‘2’(回馈);‘3’(退出);‘4’(对话请求);’5’(对话请求的回
应)的消息,对应监听端口是CTRLPORT——3333
b)CTalkSocket:用于接收及发送对话信息,及部分文件控制信息。
包括
文件头为‘6’(退出对话);‘7’(对话);‘8’(请求传送);‘9’
(传送回应)的消息,对应监听端口是TALKPORT——3334
c)CFileSocket:用于发送及接收文件,对应监听端口是FILEPORT—
—3335
其它类如CPathDialog,CFileDlg与本设计的主要部分无紧要联系,故不一一说明了
2.类的具体实现
(1).CCtrlSocket类:主要部分有FD_READ及FD_CONNECT触发的事件,
OnConnect在建立连接后发送出相应的消息,而OnReceive在有消息到来的情况下处理消息
void CCtrlSocket::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class char q[50];
char t;
unsigned int j;
CString tempaddr;
CString Ctemp;
UINT tempport;
this->Receive(q,strlen(q)+1,0);
t=q[0];
for(j=0;j<strlen(q);j++)
{
q[j]=q[j+1];
}
CChatApp *pApp=(CChatApp *) AfxGetApp();
CChatDlg *pDlg = (CChatDlg *) pApp->m_pMainWnd;
pDlg->UpdateData(true);
switch(t)//对控制信息的判断
{
case '1'://登陆
pDlg->m_listonline.InsertItem(0,q);
this->GetPeerName(tempaddr,tempport);
pDlg->m_listonline.SetItemText(0,1,tempaddr);
Ctemp="2"+pDlg->m_hostname;
this->Send(Ctemp,strlen(Ctemp)+1,0);
break;
case '2'://回馈
pDlg->m_listonline.InsertItem(0,q);
this->GetPeerName(tempaddr,tempport);
pDlg->m_listonline.SetItemText(0,1,tempaddr);
break;
case '3'://退出
for(j=0;j<pDlg->m_listonline.GetItemCount();j++)
{
if(pDlg->m_listonline.GetItemText(j,0)==q)
{
pDlg->m_listonline.DeleteItem(j);
}
}
break;
case '4'://请求对话
Ctemp.Format("%s",q);
Ctemp="是否接受"+Ctemp+"的对话请求?";
if(AfxMessageBox(Ctemp, MB_YESNO|MB_ICONQUESTION) != IDYES)
{
Ctemp="52";//拒绝
this->Send(Ctemp,strlen(Ctemp)+1,0);
break;
}
else
if(TalkSocket.m_hSocket!=INVALID_SOCKET)
{
Ctemp="6"+pDlg->m_hostname+"退出对话";//断开原来对话TalkSocket.Send(Ctemp,strlen(Ctemp)+1,0);
}
Ctemp="51";//同意
this->Send(Ctemp,strlen(Ctemp)+1,0);
this->GetPeerName(tempaddr,tempport);
pDlg->GetDlgItem(IDC_CUT_OFF)->EnableWindow(true);
pDlg->GetDlgItem(IDC_SEND_MSS)->EnableWindow(true);
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(true);
_tcpSocketClose(TalkSocket);
_tcpSocketConnect(TalkSocket,tempaddr,TALKPORT);
pDlg->m_linkip=tempaddr;
pDlg->m_linkname.Format("%s",q);
break;
case '5'://请求对话的回应
if(q[0]=='1')
{
pDlg->m_editrec+="完成连接\r\n";
pDlg->GetDlgItem(IDC_CUT_OFF)->EnableWindow(true);
pDlg->GetDlgItem(IDC_SEND_MSS)->EnableWindow(true);
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(true);
}
else
if(q[0]=='2')
AfxMessageBox("对方不想与你对话或者对方正忙!");
else AfxMessageBox("Error!");
break;
default:
break;
}
pDlg->UpdateData(false);
CAsyncSocket::OnReceive(nErrorCode);
}
void CCtrlSocket::OnConnect(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class if(nErrorCode==0)
{
this->AsyncSelect(FD_READ);
CChatApp *pApp=(CChatApp *) AfxGetApp();
CChatDlg *pDlg = (CChatDlg *) pApp->m_pMainWnd;
pDlg->UpdateData(true);
CString Ctemp;
switch(SendMssKind)
{
case 1:
Ctemp="1"+pDlg->m_hostname;
this->Send(Ctemp,strlen(Ctemp)+1,0);
break;
case 3:
Ctemp="3"+pDlg->m_hostname;
this->Send(Ctemp,strlen(Ctemp)+1,0);
break;
case 4:
Ctemp="4"+pDlg->m_hostname;
this->Send(Ctemp,strlen(Ctemp)+1,0);
break;
default:
break;
}
}
CAsyncSocket::OnConnect(nErrorCode);
}
(2).CTalkSocket类:主要部分有FD_READ及FD_CLOSE触发的事件,
OnClose对方关掉软件后响应,而OnReceive在有消息到来的情况下处理消息
void CTalkSocket::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class char q[150];
unsigned int j;
CString tempaddr;
CString Ctemp;
CString filename;
CString filelen;
long file_length;
char RootPathName[4]; // root path
DWORD SectorsPerCluster; // sectors per cluster
DWORD BytesPerSector; // bytes per sector
DWORD NumberOfFreeClusters; // free clusters
DWORD TotalNumberOfClusters; // total clusters
long DiskFree;
this->Receive(q,strlen(q)+1,0);
CChatApp *pApp=(CChatApp *) AfxGetApp();
CChatDlg *pDlg = (CChatDlg *) pApp->m_pMainWnd;
pDlg->UpdateData(true);
char t=q[0];
for(j=0;j<strlen(q);j++)
{
q[j]=q[j+1];
}
Ctemp.Format("%s",q);
switch(t)
{
case '6'://结束对话
pDlg->m_editrec=pDlg->m_editrec+Ctemp+"\r\n";
_tcpSocketClose(TalkSocket);
pDlg->GetDlgItem(IDC_CUT_OFF)->EnableWindow(false);
pDlg->GetDlgItem(IDC_SEND_MSS)->EnableWindow(false);
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(false);
break;
case '7'://对话信息
pDlg->m_editrec=pDlg->m_editrec+Ctemp+"\r\n";
break;
case '8'://请求文件传输
//q[0]=q[0]-48;
filename=Ctemp.Mid(1,q[0]);
file_length=atol(Ctemp.Right(Ctemp.GetLength()-q[0]-1));
if(file_length<1024)
filelen.Format("%ld字节",file_length);
else
if(file_length<1048576)
filelen.Format("%.2fK",file_length/(float)1024);
else
if(file_length<1073741824)
filelen.Format("%.2fM",file_length/(float)1048576);
else
filelen.Format("%.2fG",file_length/(float)1073741824);
Ctemp="是否接受对方的文件["+filename+"]?[约"+filelen+"]";
if(AfxMessageBox(Ctemp, MB_YESNO|MB_ICONQUESTION) != IDYES)
{
Ctemp="92";//拒绝
this->Send(Ctemp,strlen(Ctemp)+1,0);
}
else
{
RootPathName[0]=pDlg->m_editdir[0];
RootPathName[1]=pDlg->m_editdir[1];
RootPathName[2]=pDlg->m_editdir[2];
RootPathName[3]=0;
GetDiskFreeSpace(RootPathName,&SectorsPerCluster,&BytesPerSect or,&NumberOfFreeClusters,&TotalNumberOfClusters);
DiskFree=(long)SectorsPerCluster*BytesPerSector*NumberOfFreeClus ters;//大于一定数目会变成负数,不过只要小于2G,即1073741824*2就不会了
if(DiskFree<0||DiskFree>file_length)
{
pDlg->m_editfile=pDlg->m_editdir+"\\"+filename;
pDlg->m_filelen=filelen;
Ctemp="91";//同意
this->Send(Ctemp,strlen(Ctemp)+1,0);
CFile file;
if(!file.Open(pDlg->m_editfile,CFile::modeCreate))
AfxMessageBox("文件建立失败");
file.Close();
pDlg->file_length=file_length;
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(false);
}
else
{
AfxMessageBox("磁盘空间不足,自动放弃接收文件!");
Ctemp="93";//磁盘空间不足
this->Send(Ctemp,strlen(Ctemp)+1,0);
}
}
break;
case '9'://请求文件传输的回应
if(q[0]=='1')
{
pDlg->m_editrec+="准备传输……(请不要使用或移动传输的文件)\r\n";
_tcpSocketClose(FileConn);
_tcpSocketConnect(FileConn,pDlg->m_linkip,FILEPORT);
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(false);
}
else
if(q[0]=='2')
{
AfxMessageBox("对方不想接收你的文件!");
}
else
if(q[0]=='3')
{
AfxMessageBox("对方磁盘已满,不能接收!");
}
else
{
AfxMessageBox("Error!");
}
break;
case 'A'://结束文件传输
break;
default:
break;
}
pDlg->UpdateData(false);
CAsyncSocket::OnReceive(nErrorCode);
}
void CTalkSocket::OnClose(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class CChatApp *pApp=(CChatApp *) AfxGetApp();
CChatDlg *pDlg = (CChatDlg *) pApp->m_pMainWnd;
pDlg->UpdateData(true);
pDlg->m_editrec+="对方下线\r\n";
_tcpSocketClose(TalkSocket);
pDlg->GetDlgItem(IDC_CUT_OFF)->EnableWindow(false);
pDlg->GetDlgItem(IDC_SEND_MSS)->EnableWindow(false);
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(false);
pDlg->UpdateData(false);
CAsyncSocket::OnClose(nErrorCode);
}
(3).CFileSocket类:主要部分有FD_READ及FD_WRITE触发的事件,
OnSend是在Connect建立连接后或缓存为空,可以准备发送,而
OnReceive在有消息到来的情况下处理消息,不过由于其它响应也比较重要,便也附上了
void CFileSocket::OnAccept(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class _tcpSocketClose(FileSocket);
if(!FileListen.Accept(FileSocket))
{
AfxMessageBox("接收连接失败!");
return;
}
TotalRecv=0;
TotalSend=0;
FileSocket.AsyncSelect(FD_READ);
CAsyncSocket::OnAccept(nErrorCode);
}
void CFileSocket::OnConnect(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class TotalRecv=0;
TotalSend=0;
FileConn.AsyncSelect(FD_WRITE);
CAsyncSocket::OnConnect(nErrorCode);
}
void CFileSocket::OnReceive(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
FileSocket.AsyncSelect(FD_CLOSE);
CChatApp *pApp=(CChatApp *) AfxGetApp();
CChatDlg *pDlg = (CChatDlg *) pApp->m_pMainWnd;
pDlg->UpdateData(true);
char recvbuf[4096];
CString Ctemp;
CFile file;
int dwRecv;
int per;//文件进度
if(!file.Open(pDlg->m_editfile,CFile::modeWrite|CFile::shareDenyN one))
{ }
else
{
dwRecv=0;
memset(recvbuf,0,4096);
dwRecv=this->Receive(recvbuf,4096,0);
if(dwRecv!=0)
{
file.SeekToEnd();
file.Write(recvbuf,dwRecv);
TotalRecv+=dwRecv;
per=(int)((float)TotalRecv/(float)pDlg->file_length*100);
pDlg->m_prog.SetPos(per);
pDlg->m_per.Format("%d",per);
}
if(TotalRecv==pDlg->file_length)
{
pDlg->m_editrec+="接收完毕……\r\n";
TotalRecv=0;
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(true);
}
pDlg->UpdateData(false);
file.Close();
FileSocket.AsyncSelect(FD_READ|FD_CLOSE);
}
CAsyncSocket::OnReceive(nErrorCode);
}
void CFileSocket::OnSend(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
this->AsyncSelect(FD_CLOSE);
CChatApp *pApp=(CChatApp *) AfxGetApp();
CChatDlg *pDlg = (CChatDlg *) pApp->m_pMainWnd;
pDlg->UpdateData(true);
CString Ctemp;
CFile file;
char buf[4096];
UINT dwread;
int per;//文件进度
if(!file.Open(pDlg->m_editfile,CFile::modeRead))
{ }
else
{
memset(buf,0,4096);
file.Seek(TotalSend,CFile::begin);
dwread=file.Read(buf,4096);
if(dwread!=0)
{
TotalSend+=(long)dwread;
FileConn.Send(buf,dwread,0);
}
per=(int)((float)TotalSend/(float)pDlg->file_length*100);
pDlg->m_prog.SetPos(per);
pDlg->m_per.Format("%d",per);
if(dwread<4096)
{
pDlg->m_editrec+="发送完毕……\r\n";
file.Close();
TotalSend=0;
pDlg->GetDlgItem(IDC_SEND_FILE)->EnableWindow(true);
}
else
{
file.Close();
this->AsyncSelect(FD_WRITE|FD_CLOSE);
}
pDlg->UpdateData(false);
}
CAsyncSocket::OnSend(nErrorCode);
}
void CFileSocket::OnClose(int nErrorCode)
{
// TODO: Add your specialized code here and/or call the base class
this->Close();
TotalRecv=0;
TotalSend=0;
CAsyncSocket::OnClose(nErrorCode);
}
七、调试
1.调试发现的问题:用虚拟机调试发现,虚拟机内看到宿机的IP跟宿机自己看到的IP是不同的(图表1 及图表2)
图表 1 虚拟机1,TRAVIS3
图表 2 宿机TRAVIS
2.功能调试
a)对话请求:A方选择B方IP,并请求对话,如果请求成功,相应按
钮激活,B方接收到对话请求后可以选择是否对话,同意则相应按钮
激活,对话是的界面如(图表1 及图表2)
b)文件传输:A方建立对话之后,按发送文件按钮,弹出文件选择框,
然后选择文件(如图表3),B方接收请求之后决定是否接收(图表
4),同意后A方发送,B方接收(图表5)
图表 3 文件选择框
图表 4 文件传输请求
- -
图表 5 宿机与虚拟机1传输文件
八、分析与总结
这次课程设计完成了所有的功能,所有功能调试通过,不过感觉外观有待美化。
刚开始由于实验做过C/S模式的聊天室,因此对这个P2P模式十分轻视,但是真正做起来就十分困难,单单扫描在线对等方就困难重重,首先C/S模式的IP是确定的,而P2P的想要连接的IP是未知的,因此我上网搜索了许多才找到了搜索局域网在线用户的方法,然后我用Connect();的方法进行端口扫描,不过由于当时我用的是一个Socket进行Connect及发送登陆信息,发送完了再发送,这样效率十分的低,因此后期我用几个Socket同时connect 并发送登陆信息,这样的效率增大了,也可靠了许多,然后是的难点是FileSocket,原先在OnRecive触发时没有屏蔽FD_READ,导致信息接收不完全,最后发现只要在开始屏蔽FD_READ,在接收完了在打开FD_READ就行了,因此也迎刃而解。
总结:这次课程设计陪伴着我度过了整个寒假。
我学习到了许多,包括文件读写,Socket 使用,窗口操作,字符处理等等,受益匪浅,感觉在学习网络,复习网络的过程中得到了许多乐趣,如挑战困难成功的乐趣,也学习了许多VC的应用,如BreakPoint看参数的变化
参考书目:
[1]《电脑编程技巧与维护》杂志社,《Visual C++编程技巧典型案例解析——网络与通信级计算机安全与维护篇》,中国电力出版社,2005
自我评价:优- -优质-。