视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
p2p即时聊天系统
2025-10-04 22:04:32 责编:小OO
文档
一、课程设计题目

基于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的派生类,分别是CCtrlSocket,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    {

        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;jm_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    {

        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,&BytesPerSector,&NumberOfFreeClusters,&TotalNumberOfClusters);

            DiskFree=(long)SectorsPerCluster*BytesPerSector*NumberOfFreeClusters;//大于一定数目会变成负数,不过只要小于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::shareDenyNone))

    { }

    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

自我评价:优下载本文

显示全文
专题