使用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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章