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