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