前段时间和朋友一起做了一个类似于电驴、迅雷 + MSN工具的毛坯模型,基本上所有功能都是从socket通信级别向上实现。
整体架构为C/S架构,使用MFC实现。技术上都是很老的东西,此文主要介绍类似于 电驴 的这样一个软件的设计思路和部分代码框架。
我们实现的代码不是很优化,仅为设计思路的佐证。
我最初做这个小软件的想法是,方便一个小型网络中大家的资源共享和交流。
每个人都能共享出自己的一部分文件,当然这个可以通过WINDOWS的共享文件夹或者LINUX的SAMBA实现,不过假如在我并不知道共享源的情况下,就很难获得资源了。有人说,直接在WINDOWS的网上邻居里可以看到局域网上大家共享的资源,但是我发现可能由于各种网络设置的关系,在网上邻居里我经常看不到其他人共享的资源。
于是当时我用WINDOWS的几个API,(忘了具体是什么了,可以探测和遍历他人共享目录),写出来效果也很不好,第一很慢,第二还是收不全,第三不可控,其封装度太高,隐藏N多技术细节。
于是后来我想,还是基于socket,从底层做起。
在不知道源的情况下,我们如何探测、收集到各个源的共享资源呢?—— 答案很快浮出水面,弄一台服务器,大家都连接服务器,告诉它自己这儿的资源情况,然后大家统一从服务器查询网络上的共享资源,然后建立客户端的点对点连接传输文件。
程序的整体架构很简单,下面本文的主旨在于一步步的告诉大家在此软件实现中的一些要点。
1. 内部协议
我们的客户端需要与服务器端“交流”,就得有一套系统内部能被互相识别的语言,这样一套协议完全可以咱们自己设计。
我在实现中采用的UDP通信,每个UDP包 头一个字节为 指令字节,其后按照每个指令的格式跟接参数。例如我的“内部协议”部分代码:
/*********************************************/
// c->s 用户心跳
#define NS_UDP_LIVING 0x03
// c->s 用户更新共享信息
#define NS_UDP_UPDATE 0x04
// c->s 用户搜索
#define NS_UDP_SEARCH 0x05
// c->s 获取在线用户列表
#define NS_UDP_GET_ONLINE_USERS 0x06
// c->s 申请广播消息
#define NS_UDP_CLIENT_BORADCAST 0x07
// c->s 申请下载新版本客户端
#define NS_UDP_CLIENT_GETEXE 0x08
/*********************************************/
// s->c 用户登录成功
#define NS_UDP_LOGIN_SUCCESS 0x71
// s->c 传送搜索结果
#define NS_UDP_PUSHINFO 0x72
// s->c 传送在线用户列表
#define NS_UDP_PUSH_ONLINE_USERS 0x73
// s->c 广播消息
#define NS_UDP_BROADCAST_MESSAGE 0x74
// s->c 服务器重启,踢出所有用户
#define NS_UDP_SERVER_RESTART 0x75
// s->c 重新收集共享资料
#define NS_UDP_RECOLLECT_SHAREFILES 0x76
// s->c 踢出用户
#define NS_UDP_KICK 0x77
// s->c 客户端更新包
#define NS_UDP_CLIENT_UPDATE 0x78
/*********************************************/
// c->c 文件获取
#define NS_UDP_GETFILE 0x80
// c->c 文件获取许可
#define NS_UDP_ALLOW_GETFILE 0x81
// c->c 聊天消息
#define NS_UDP_MESSAGE 0x82
如果直接在网络上发送明文,则很容易被别人使用抓包工具抓取,稍加分析则可理解其中的内容,然后别人可以模拟写客户端或者程序来非法访问、攻击你的服务器端和别的客户端。所以这里应该要使用加密算法,在网路上传输加密数据。在我的“毛坯”程序中没有实现这一点,不过,这在互联网上发布的任何一款商用通信程序都是必须的。
2. TCP文件传输及传输群管理。
在一方申请下载的时候,另一方应该响应该请求,为它建立TCP连接。前者我们称TCP客户端、后者为TCP服务端。基于我们的“共享”的思想,每个客户端程序都可能作为TCP客户(下载资源)或者TCP服务器(提供资源下载),同时服务器端也可以作为TCP服务器(发布客户端自动升级包)。
那么我们总结核心思想就是,不管客户端还是服务器端,都应该有一个tcp listener,不断的监听网络上到来的TCP请求,每当过来一个请求时,与其建立连接,并且单独增开一个线程与其通信进行文件传输——我把这个机制叫做TCP文件传输群管理。
传输群管理应该包括以下几点
1. 侦听请求,建立连接;
2. 为连接增开线程;
3. 控制传输服务;
4. 结束传输,关闭线程。
下面给出我们传输群管理的核心部分代码:
大致说明一下,TCPServerManager为传输群管理,每次有连接单独启动一个TCPTaskManager作为TCP传输的容器类,负责管理一个TCP传输任务。
#ifndef NETSHARE_TCP_SERVER_MANAGER_H_
#define NETSHARE_TCP_SERVER_MANAGER_H_
#include "TCPServer.h"
#include "NetShareUDP.h"
#include <cpplib/MutexLock.h>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
class TCPTaskManager;
class TCPServerManager;
//typedef void (TCPServerManager::*CALLBACK_DELETE)(int);
//发送文件数据结构
struct SendFileStructType
{
string filename;
string ip;
string dir;
DWORD offset;
TCPTaskManager* p_manager;
};
//TCP子任务管理器
class TCPTaskManager
{
public:
TCPTaskManager( SendFileStructType task )
: m_Speed( 0 )
, m_Task( task )
, m_MaxSpeed( 0 )
, m_IsFinish( FALSE )
, m_TaskHandler( 0 )
{
//this->Run();
m_pTCPServerInstance = new TCP_Server();
}
~TCPTaskManager(){}
//
void Speed( DWORD speed ) { m_Speed = speed; }
DWORD Speed(){ return m_Speed; }
void SetMaxSpeed( DWORD speed )
{
m_MaxSpeed = speed;
m_pTCPServerInstance->SetMaxSpeed( speed );
}
//发送完成后调用
//调用该方法并没有回收本类对应对象在TCPServerManager的m_pTasks中存在的实例,
//需要在TCPServerManager中再判断m_IsFinish的状态位加以移除。
void Finish()
{
CloseHandle( m_TaskHandler );
m_IsFinish = TRUE;
}
bool CheckFinish(){ return m_IsFinish; }
TCP_Server* GetTCPServerInstancePtr(){ return m_pTCPServerInstance; }
//
//TCP传送数据线程
static DWORD WINAPI TCPSendThreadProc(LPVOID lpParameter)
{
SendFileStructType* ptype = (SendFileStructType*)(lpParameter);
string ip = ptype->ip;
string filename = ptype->filename;
//TCP_Server ServerInstance;
TCP_Server* pServerInstance = ptype->p_manager->GetTCPServerInstancePtr();
pServerInstance->SetDir( ptype->dir );
pServerInstance->BindManager( ptype->p_manager );
pServerInstance->SendFile( ip,TCP_TRANCESPORT_PORT, filename, ptype->offset );
ptype->p_manager->Finish();
return 0;
}
void Run()
{
m_Task.p_manager = this;
m_TaskHandler = CreateThread( NULL, 0, TCPSendThreadProc, (LPVOID)&m_Task, 0, NULL );
}
protected:
DWORD m_Speed;//当前速度
DWORD m_MaxSpeed; //最大传输速度
SendFileStructType m_Task;
HANDLE m_TaskHandler;
bool m_IsFinish;
TCP_Server* m_pTCPServerInstance;
};
namespace
{
//检测一个任务是否已经完成
bool CompletedTask( TCPTaskManager* task )
{
return ( task->CheckFinish() );
}
}
//TCP文件传送服务器端管理类
class TCPServerManager
{
public:
TCPServerManager(){}
~TCPServerManager(){}
//添加文件传送任务
bool AddTask( SendFileStructType task );
//设置整体最大发送速度
void MaxSpeed( DWORD speed )
{
m_MaxSpeed = speed;
AdjustSpeed();
}
//删除编号为id的下载任务
void Delete( size_t id )
{
TaskMutex.Lock();
vector<TCPTaskManager*>::iterator iter = m_pTasks.begin();
for( size_t i = 0; i < id; i++ )
++iter;
delete *iter;
m_pTasks.erase( iter );
TaskMutex.Unlock();
AdjustSpeed();
}
//移除所有已完成的任务
void ClearCompleteTask()
{
try
{
TaskMutex.Lock();
vector<TCPTaskManager*>::iterator new_end = remove_if( m_pTasks.begin(), m_pTasks.end(), CompletedTask );
for( vector<TCPTaskManager*>::iterator iter = new_end; iter != m_pTasks.end(); ++iter )
delete *iter;
m_pTasks.erase( new_end, m_pTasks.end());
int k = m_pTasks.size();
TaskMutex.Unlock();
}
catch (...)
{
}
}
protected:
//获取当前整体发送速度
DWORD GetCurrTotalSpeed()
{
TaskMutex.Lock();
DWORD sum = 0;
for( vector<TCPTaskManager*>::iterator iter = m_pTasks.begin();
iter != m_pTasks.end();
++iter )
{
sum += (*iter)->Speed();
}
TaskMutex.Unlock();
return sum;
}
//获取当前传送任务数
int GetTaskNum()
{
ClearCompleteTask();
return m_pTasks.size();
}
//调整所有发送任务的发送速度
//暂时为平均分配
void AdjustSpeed()
{
if( m_MaxSpeed == 0 || GetTaskNum() == 0 )
return;
DWORD SingleMaxSpeed = m_MaxSpeed / GetTaskNum();
TaskMutex.Lock();
for( vector<TCPTaskManager*>::iterator iter = m_pTasks.begin();
iter != m_pTasks.end();
++iter )
{
(*iter)->SetMaxSpeed( SingleMaxSpeed );
}
TaskMutex.Unlock();
}
private:
DWORD m_MaxSpeed; //整体最大发送速度
vector<TCPTaskManager*> m_pTasks;
cpplib::resource::Mutex TaskMutex;//访问m_Task的互斥锁
};
#endif
未完待续。。。。