使用NDK进行网络文件传输

 想要通过网络传输文件,搜了不少代码,找到一个何意的真不容易。Marat Bedretdinov给了一个关于聊天程序的代码,里面包含NetLib模块,对网络提供了一个很好的界面,提供了多线程和事件通知。但是很复杂,源文件就有49个,还木有时间细看。有兴趣的可以看看。

Sebastien_Lachance写了一个名为NDK (Network Development Kit 2.0) 的小工具,可以方便的开发客户服务器程序。提供了一个服务器、客户端的框架,基于MFC的CSocket的,不足的是只支持单线程,多线程也不安全。NDK隐藏了基于网络开发的复杂性,只用和3个类进行交互:CNDKServer、CNDKClient、CNDKMessage。先简单看下这些类:

CNDKServer

Attributes:

·         BOOL IsStarted() const;

·         long GetPort() const;

·         long GetNbUsers() const;

·         void GetUserIds(CLongArray& alIds) const;

Operations:

·         BOOL StartListening(long lPort);

·         void Stop();

·         BOOL SendMessageToUser(long lUserId, CNDKMessage& message);

·         BOOL SendMessageToAllUsers(CNDKMessage& message);

·         BOOL SendMessageToSomeUsers(const CLongArray& alUserIds, CNDKMessage& message);

·         BOOL SendMessageToAllUsersExceptFor(long lUserId, CNDKMessage& message);

·         BOOL SendMessageToAllUsersExceptFor(const CLongArray& alUserIds, CNDKMessage& message);

·         BOOL DisconnectUser(long lUserId);

·         void DisconnectAllUsers();

·         BOOL PingUser(long lUserId);

·         void PingAllUsers();

Callbacks:

·         virtual BOOL OnIsConnectionAccepted() = 0;

·         virtual void OnConnect(long lUserId) = 0;

·         virtual void OnMessage(long lUserId, CNDKMessage& message) = 0;

·         virtual void OnDisconnect(long lUserId, NDKServerDisconnection disconnectionType) = 0;

·         virtual void OnPing(long lUserId, long lNbMilliseconds);

CNDKClient

Attributes:

·         BOOL IsConnected() const;

·         BOOL GetIpAndPort(CString& strIp, long& lPort) const;

Operations:

·         BOOL OpenConnection(const CString& strServerIp, long lPort);

·         void CloseConnection();

·         BOOL SendMessageToServer(CNDKMessage& message);

·         BOOL PingServer();

Callbacks:

·         virtual void OnMessage(CNDKMessage& message) = 0;

·         virtual void OnDisconnect(NDKClientDisconnection disconnectionType) = 0;

·         virtual void OnPing(long lNbMilliseconds);

CNDKMessage

Attributes:

·         void SetId(long lId);

·         long GetId() const;

·         int GetNbElements() const;

Operations:

·         void Add(TYPE typeData);

·         void SetAt(long lIndex, TYPE typeData);

·         void GetAt(long lIndex, TYPE& typeData) const;

·         void GetNext(TYPE& typeData);

TYPE 可以是 UCHAR, char, USHORT, short, UINT, int, long, float, double, CString, 或者 LPVOID,这为我们开发各种应用提供了方便。有兴趣的可以读读其源码。通过使用CArchive来实现常用数据类型的序列化和反序列化,然后通过网络进行传输。

CNDKMessage有一个属性ID来代表消息的类型,重载纯虚函数virtual void OnMessage(CNDKMessage& message) = 0时,可以根据消息类型不同而进行不同的处理。比如我们做一个聊天程序可以定义如下消息:

enum ChatMessage
{
	ChatUserJoin,
	ChatUserQuit,
	ChatText,
	ChatBigMessage
};

然后在OnMessage中进行处理:

void CChatServerDlg::OnMessage(long lUserId, CNDKMessage& message)
{
	switch (message.GetId())
	{
	case ChatUserJoin:
		{
		……	
		}
		break;

	case ChatText:
		{
……
		}
		break;

	case ChatBigMessage:
		{	
		……	
		}
		break;
	}
}

如何利用这个框架来实现文件的传输?由于一次传输的数据包不能过大,文件肯定是需要分片的。定义如下的消息类型:

// Defines use for CNDKMessage
#define SERVER_FILES           0	
#define REQUEST_FILE           1
#define START_TRANSFERT        2
#define REQUEST_NEXT_FILE_PART 3
#define NEXT_FILE_PART         4
#define TRANSFERT_COMPLETED    5
// Define use when allocating the buffer to send a file part
#define BUFFER_SIZE 1024

SERVER_FILES 用以获取服务器上可用的文件列表。REQUEST_FILE用于请求某个文件,可以多个。START_TRANSFERT用于获取文件大小,以便分配磁盘空间。REQUEST_NEXT_FILE_PART客户端请求下一个文件块。NEXT_FILE_PART服务器发送下一个文件块。TRANSFERT_COMPLETED 文件传输结束。BUFFER_SIZE表示文件的分片大小。

服务器端的代码如下:

// Called whenever a message is received from a user.
void CNDKFileTransferServerDlg::OnMessage(long lUserId, 
                              CNDKMessage& message)
{
    switch (message.GetId())
    {
    // The client requests a file to download
    case REQUEST_FILE:
        {
            CString strFileName;
            message.GetAt(0, strFileName);

            m_fileUpload.Open(strFileName, CFile::modeRead | 
                              CFile::shareDenyWrite);

            // Send the file length
            message.SetId(START_TRANSFERT);
            message.SetAt(0, (int)m_fileUpload.GetLength());

            SendMessageToUser(lUserId, message);

            CString strActivity;
            strActivity.Format(IDS_UPLOAD_FILE, strFileName);

            AddActivity(strActivity);
        }
        break;

    // The client asks for the next file part to download
    case REQUEST_NEXT_FILE_PART:
        {
            m_unBufferLength = m_fileUpload.Read(m_byteBuffer, BUFFER_SIZE);

            if (m_unBufferLength != 0)
            {
                // Send the file part
                message.SetId(NEXT_FILE_PART);
                message.SetAt(0, m_byteBuffer, m_unBufferLength);
            }
            else
            {
                // When there is no more read bytes,
                // send the acknowledgment that the file is completed
                message.SetId(TRANSFERT_COMPLETED);

                // Close the file if it is opened.
                if (m_fileUpload.m_hFile != INVALID_HANDLE_VALUE)
                    m_fileUpload.Close();

                CString strActivity;
                strActivity.Format(IDS_UPLOAD_FILE_COMPLETED);

                AddActivity(strActivity);
            }

            SendMessageToUser(lUserId, message);
        }
        break;
    }
}

客户端首先请求一个文件:

// Open the file
if (m_fileDownload.Open(strFileNameToCreate, 
    CFile::modeCreate | CFile::modeWrite))
{
    // Ask the server to start the download
    CNDKMessage message(REQUEST_FILE);
    message.Add(strFileName);

    SendMessageToServer(message);

    m_bIsDownloading = TRUE;

    UpdateUI();
}

然后重载CNDKClient中的virtual void OnMessage(CNDKMessage& message) = 0;

// Called when a message is received.
void CNDKFileTransferClientDlg::OnMessage(CNDKMessage& message)
{
    switch (message.GetId())
    {
    case SERVER_FILES:
        {
            // Add the file name in the list
            for (int nFileIndex = 0; 
                 nFileIndex < message.GetNbElements(); nFileIndex++)
            {
                CString strFileName;

                message.GetAt(nFileIndex, strFileName);

                m_listServerFiles.AddString(strFileName);
            }

            UpdateUI();
        }
        break;

    case START_TRANSFERT:
        {
            message.GetAt(0, m_nFileSize);
            
            m_progressDownload.SetRange32(0, m_nFileSize);
            
            // Ask the server for the first file part
            message.SetId(REQUEST_NEXT_FILE_PART);
            SendMessageToServer(message);
        }
        break;

    case NEXT_FILE_PART:
        {
            message.GetAt(0, m_byteBuffer, m_unBufferLength);

            m_fileDownload.Write(m_byteBuffer, m_unBufferLength);

            m_progressDownload.OffsetPos(m_unBufferLength);

            // Ask the server for the first file part
            CNDKMessage requestMessage(REQUEST_NEXT_FILE_PART);
            SendMessageToServer(requestMessage);
        }
        break;

    case TRANSFERT_COMPLETED:
        m_fileDownload.Close();

        AfxMessageBox(IDS_FILE_DOWNLOADED_SUCCESSFULLY);

        UpdateUI();
        break;
    }
}

更多信息和源码参见:

http://www.codeproject.com/KB/IP/peer_to_peer_communicator.aspx

http://www.codeproject.com/KB/IP/ndk.aspx

http://www.codeproject.com/KB/IP/NDKFileTransfer.aspx

 

 

发布了50 篇原创文章 · 获赞 5 · 访问量 35万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章