用程序诠释生命 发表于 2006-6-4 16:06

《文件传输》课程设计(VC)

坚决响应Wool王的号召,写个原创。
不过发现要专门为了发帖去写有点懒,那就把我上学期做的文件传输课程设计发出来吧。
不过,既然是课程设计,严谨度自然跟正式项目没得比,大家权且看看就行了。
还有,我水平有限,有错很正常,欢迎大家指正

实验要求:

   
[*]使用Socket实现点对点传输
[*]可以自定义监听端口
[*]可以对远程文件浏览并选择下载
[*]可以上传本地文件到远程



实验环境

Windows XP + Visual Studio .Net + C++
在Windows XP + Visual C++ 6.0 下亦调试通过


效果图
http://www.lupin.cn/upload/myftp2_2.jpg

序列图
http://www.lupin.cn/upload/myftp2_1.jpg

在上图中:
ServerDialog   是服务端的UI,但实际上,我把服务端跟客户端都合在一起了。
ListenSocket    这是服务端进行监听的Socket
ServerTransfer   这是服务端进行数据和命令传输的Socket
ClientTransfer   这是客户端进行数据和命令舆的Socket
ClientDialog   这是客户端UI。由于前面已经说了,这个各户中,服务端跟客户端合在一起了,所以虽然概念上ServerDialog和ClientDialog是两个不同的东西,但实际上是同一个.

未完待续

iptton 发表于 2006-6-4 17:40

期待……(虽然现在看不懂……)

wool王 发表于 2006-6-4 19:12

楼主真给面子,哈哈。

期待ing。。。

暂时没看出楼主要实现什么,大概是一个类似飞鸽传书的软件吧。等楼主贴完再好好研究研究。

hjack 发表于 2006-6-6 10:10

楼主终于有搞作了。good


PS:论坛的发展有赖师弟师妹的努力啦。我们老了,有空会回来看看的。

iptton 发表于 2006-6-6 13:06

hjack :我们老了……
呵呵

师兄说说在QQ做些什么工作呀……也让我们知道以后要用到什么……有个方向

powerwind 发表于 2006-6-6 16:26

ps:楼主开个头就走掉,快变水了......

用程序诠释生命 发表于 2006-6-6 21:53

在上一部分中,介绍了程序的功能,还有大致的实现思路,现在进入具体实现。

首先,我们需要一个东西来承载我们传输的东西。于是定义了一个CMessage类。以下是定义


        virtual void Serialize( CArchive &ar );//序列化到CARchive中,或从中读取
        CString m_Content;                //包含内容,文件内容,命令内容
        CString m_FilePath;                             //文件目录,仅在传输文件的时候使用
        //0表示发送命令
        //1表示文件列表
        //2表示发送文件
        //3表示取得文件
        int m_Type;


其中的CArchive是一个序列化的载体,它可以用于进行网络传输。也就是说,CMessage在传输的时候会被序列化为CArchive,然后传输。接收的时候也是从CArchive后序列化过来。


OK。有了载体后,需要有些东西来处理这个载体了。
首先当然是服务端的监听Socket了,监听Socket的第一个动作就是开始监听

bool CListenSocket::Start( CMyFtp2Dlg* dlg )
{
        m_Dlg = dlg;
        int port = dlg->GetDlgItemInt( IDC_PORT );
       
        if( !(this->Create( port )) )
                return false;
        if( !(this->Listen() ))
                return false;
        return true;
}

这个方法的作用就是接收来自Dialog的端口,然后监听这个端口。很简单的动作。
监听当然不可能什么事都不做了,监听到了需要有些反应,于是便需要一个反应动作

void CListenSocket::OnAccept( int nErrorCode )
{
        m_Dlg->OnAccept();
        CSocket::OnAccept( nErrorCode );
}

这里又再调用了Dialog里的反应方法,因为某些原因,反应的具体动作放在Dialog会比较好,以下是Dialog里的反应方法:

void CMyFtp2Dlg::OnAccept()
{
        if( !m_Listen.Accept( m_ServerTransfer ) )                //应答连接,转接到m+ServerTransfer
        {
                AfxMessageBox( "Accept Error" );
                return;
        }

        m_ServerTransfer.Init( this );                //服务端TransferSocket初始化

        //取得来访者信息
        CString cIP;
        UINT cPort;
        m_Listen.GetPeerName( cIP, cPort );
        cIP.Format( "%s:%d连接成功", cIP, cPort );
        AfxMessageBox( cIP );

        //传输默认目录文件列表到客户端
        CString files;
        GetFiles( m_DefaultPath, m_MyFiles );            //取得默认目录的文件列表
        ArrayToString( m_MyFiles, files );                     //把文件列表转化为字符串的形式
        CMessage msg;
        msg.m_Content = files;
        msg.m_Type = 1;
        m_ServerTransfer.SendMessage( &msg );      //将文件列表发送出去
}

这个方法做的任务就是把连接者转给传输Socket(ServerTransfer),相当说ListenSocket只是一个接线员,其实没人找它(可怜的家伙),这就是为什么要把实际的反应动作放在Dialog的原因啦,因为ListenSocket根本不知道ServerTransfer的存在。然后ServerTransfer进行初始化。再然后就是把本地文件列表发给客户端了。

      到这里,完成了让客户端得到本地文件列表的功能。


         先这样,我写实验报告先。

未完待续

恶魔大叔 发表于 2006-6-7 13:06

顶一下……

iptton 发表于 2006-6-21 14:04

楼主又匿了……

顶上来……

天命 发表于 2006-6-22 00:30

FTP的设计我们也做了,不过当时用的是命令行界面

wool王 发表于 2006-6-22 01:16

顶!楼主半途而废!!!

用程序诠释生命 发表于 2006-6-22 16:18

哈哈,不用担心, 半途而废不是我的风格.只是最近比较忙,所以没时间顾上而已,今天又回来啦..

OK,在上一部分中,我讲客户端获得了远程的文件列表,接下来就进对远程文件列表的操作吧.

远程文件显示在一个列表控件里面,双击其中的一个项,作出反应.如果是目录,就进入目录,如果是文件就把文件下载到本地.


void CMyFtp2Dlg::OnNMDblclkYourlist(NMHDR *pNMHDR, LRESULT *pResult)
{
        CString name;
        CString size;
        int row = m_YourList.GetNextItem(-1,LVNI_ALL | LVNI_SELECTED);  //取得被双击列表项
        name = m_YourList.GetItemText( row, 0 );    //取得列表项的文件名字段
        size = m_YourList.GetItemText( row, 1 );               //取得列表项的长度字段

        int pos;
        int length = atoi( size );
        CMessage msg;
        msg.m_Type = 0;            //设置传输类型,
        if( length == 0 )                //当长度为0时,判断为目录,单击了远程目录,当然是进入那个目录,浏览下面的文件.
        {
                if( name == "." )
                        m_RemotePath = "";
                else if( name == ".." )
                {
                        m_RemotePath.TrimRight( '/' );
                        pos = m_RemotePath.ReverseFind( '/' );
                        if( pos > -1 )
                                m_RemotePath = m_RemotePath.Left( pos ) + "/";
                        else
                                m_RemotePath = "";
                }
                else
                        m_RemotePath += name + "/";
                msg.m_Content = "cd " + m_RemotePath;         //生成取得远程文件列表的命令
        }
        else
                msg.m_Content = "get " + m_RemotePath + name;               //生成取得远程文件到本地的命令
        if( !m_ClientTransfer.SendMessage( &msg ) )                                 //发送命令
                AfxMessageBox( "命令发送失败" );

        *pResult = 0;
}



应该说,这个文件传输程序的下载功能就到这里了.
下载功能完成了,上传功能也就简单了,只是发送的方向不同而已,看代码:


void CMyFtp2Dlg::OnNMDblclkMylist(NMHDR *pNMHDR, LRESULT *pResult)
{
        CString name;
        CString size;
        int row = m_MyList.GetNextItem(-1,LVNI_ALL | LVNI_SELECTED);
        name = m_MyList.GetItemText( row, 0 );
        size = m_MyList.GetItemText( row, 1 );
       
        int pos;
        int length = atoi( size );
        if( length == 0 )
        {
                if( name == "." )
                        m_CurrentPath = m_DefaultPath;
                else if( name == ".." )
                {
                        m_CurrentPath.TrimRight( '/' );
                        pos = m_CurrentPath.ReverseFind( '/' );
                        m_CurrentPath = m_CurrentPath.Left( pos ) + "/";
                }
                else
                {
                        m_CurrentPath += name + "/";
                }
                if( m_CurrentPath.GetLength() < m_DefaultPath.GetLength() )
                        m_CurrentPath = m_DefaultPath;
                FillMyList( m_CurrentPath );
        }
        else
        {
                if( m_ClientTransfer.SendFile( m_CurrentPath + name, m_RemotePath + name ) )
                        AfxMessageBox( name + " 发送成功" );
                else
                        AfxMessageBox( name + " 发送失败" );
        }
        *pResult = 0;
}



跟下载文件的差不多,就不多解释了.
好滴,整个框架就这么完成了.


问题是,现在还有一些细节没有提到,
1,如何响应命令
2,如何发送文件
3,如何接受文件

这三个问题, 留待下回分解.

天命 发表于 2006-6-23 14:37

这三个好像才是重点啊..........晕

用程序诠释生命 发表于 2006-7-3 00:12

版主在提醒我更新了...
呵呵,最近忙考试,考完再更新,
顺便发多我几个课程设计上来.

wool王 发表于 2006-7-7 23:24

楼上的,新斑竹好看得起你哦。。。

居然置顶了。。。

别半途而废,,,share点自己的经验。。。

iptton 发表于 2006-9-30 21:24

初接触SOCKET。。。

楼主介不介意把源代码所有文件上传上来共享?

pigcanfly 发表于 2007-1-3 09:26

强烈要求师兄放源码,师弟好学习下

powerwind 发表于 2007-1-3 11:02

我去请楼主过来

楼上两位耐心等一下

用程序诠释生命 发表于 2007-1-9 00:18

原帖由 pigcanfly 于 2007-1-3 09:26 发表
强烈要求师兄放源码,师弟好学习下

eggcanfly跟你是什么关系?

用程序诠释生命 发表于 2007-1-9 00:37

半年过去了,我继续。。。。差点忘了。。

截止到上回,我已经把这个软件的大体框架说明白了,接下来就是一点点细节,细节的东西,说白了就是核心代码啦。
所以,这部分大部分是代码了。
  这一部分要解决以下三个问题:
1,如何响应命令
2,如何发送文件
3,如何接受文件
  前面已经说了,CMessage是承载客户端与服务器端消息传送的类,其中包括命令和文件数据都是通过它来传送的。


  响应命令的过程是这样的:Server收到Message(并且这个Message承载的是命令,通过Message里的Type来判断)->解析命令->执行命令

以下的方法是接收Message用的,然后判断是什么类型的函数,然后做出响应,我们后注释为“命令处理”那个方法

// CTransferSocket 成员函数
void CTransferSocket::OnReceive( int nErrorCode )
{
CMessage msg;
msg.Serialize( *m_In );
msg.m_Content.MakeLower();
switch( msg.m_Type )
{
case 0:
PerformCommand( msg.m_Content );//命令处理
break;
case 1:
m_Dlg->FileYourList( msg.m_Content );//填充文件列表
break;
case 2:
case 3:
if( !SaveFile( msg ) )//保存文件
   AfxMessageBox( "文件保存失败" );
else
   if( m_IsClient)
    AfxMessageBox( "文件保存成功" );
break;
}
}


下面的执行命令和发送文件到客户端的具体实现:

void CTransferSocket::PerformCommand( const CString& cmd )
{
CMessage msg;
CString cmdName;
CString cmdPara;
SplitCommand( cmd, cmdName, cmdPara );//分解命令
if( cmdName == "cd" )//进入目录命令
{
CString path = m_Dlg->m_DefaultPath + cmdPara;
if( path.GetLength() < m_Dlg->m_DefaultPath.GetLength())
   path = m_Dlg->m_DefaultPath;
CString files;
CStringArray array;
m_Dlg->GetFiles( path, array );  //读取目录下的文件,把文件信息放到array中
m_Dlg->ArrayToString( array, files );//将array转化为字符串,方便传输
msg.m_Content = files;
msg.m_Type = 1;
SendMessage( &msg ); //发回客户端
}
else if( cmdName == "get" ) //客户端下载文件命令
{
CString filePath = m_Dlg->m_DefaultPath + cmdPara;
SendFile( filePath ); //发回客户端
}
//可以继续增加"else if"来增加新的原语,但原语量大的时候,就不应该用Else If这么简单的策略了。
else
{
return;
}
}



发送文件的具体代码,再次重申,这里只是简单实现,完全没有考虑性能:

bool CTransferSocket::SendFile( CString filePath, CString remotePath )
{
CMessage msg;
CFile file( filePath ,CFile::modeRead|CFile::shareDenyWrite);
byte* buffer = NULL;
long count = file.GetLength();
buffer = new byte;
file.Read( buffer, count );
if( remotePath != "" )
{
msg.m_FilePath = remotePath;
msg.m_Type = 2;
}
else
{
int pos = filePath.ReverseFind( '/' );
if( pos == -1 )
   pos = filePath.GetLength();
else
   pos = filePath.GetLength() - pos - 1;
msg.m_FilePath = filePath.Right( pos );
msg.m_Type = 3;
}
msg.m_Content = buffer;
if( this->SendMessage( &msg ) )
{
file.Close();
return true;
}
else
{
file.Close();
return false;
}
}


再看保存文件的代码:

bool CTransferSocket::SaveFile( const CMessage& msg )
{
CString filePath;
if( msg.m_Type == 2 )
filePath = m_Dlg->m_DefaultPath + msg.m_FilePath;
else
filePath = m_Dlg->m_CurrentPath + msg.m_FilePath;
if( !m_File.Open( filePath ,CFile::modeWrite|CFile::modeCreate) )
{
m_File.Close();
return false;
}
m_File.Write( msg.m_Content, msg.m_Content.GetLength() );
m_File.Close();
return true;
}


细节也讲完了。。。

打完收功。。。。
页: [1] 2
查看完整版本: 《文件传输》课程设计(VC)