#include "ETUdp.h"
#include "winsock2.h"
#define OBJ_SENDPATHFILE_T L"d:\\WindowsXP
SP2.iso" //我们用来执行文件传输的原始文件名
#pragma comment(lib,"ws2_32.lib") //连接SOCKET库
#ifdef _WIN64
#pragma comment(lib,".\\lib64\\ETUdp.lib")
#else
#pragma comment(lib,".\\lib32\\ETUdp.lib")
#endif
/*
关于本代码
作者:danscort www.phoenixp2p.com
ETUdp UDP可靠传输库
内码: UTF8 [Unicode]
线程: 2
库类型: DLL
支持平台: winxp/2003/vista/7/8 32位和64位
本代码示范了如何使用ETUdp库 执行文件传输 以及进行极限输出测试
本代码可以任意使用,不带任何条件,无论修改还是再发布
ETUdp 是我们为了验证一个UDP
1对1情况下高效传输技术,而重新编写的一个库,是中间附带产物,它的接口风格和我们多对多的phoenix库和文件传输ut库基本相同,
这个库是免费的,但是我们不附带任何保证或者承诺,使用者要自己承担使用这个库可能带来的任何后果,无论好和坏。
ETUdp
只提供了我们认为需要的基本接口,很多复杂的功能,我们认为完全可以通过调用者上层的组合来实现,因此没有放到库接口中,但是不排除以后逐渐增加接口。
如果您认为这个库必须要增加新的接口函数,您可以发邮件告诉我们希望增加的接口函数功能,我们会考虑增加。
UDP 1对1
可靠传输是个很简单的东西,没那么多的技术或者技巧可言,如果您单纯用来实现可靠流传输,其实TCP远比UDP好,具体原因已经另外写文章给出了,这里不再重复。那么对实时性要求很高的应用是否一定要使用UDP呢?
我觉得这完全看应用而定,TCP的慢启动并不象各位想象中的那么慢,它这个慢是针对的延迟恢复,而不是发送,很多朋友其实是被网络上的某些文章给蒙蔽了。作为我个人,实现可靠UDP,并不是用来代替数据流的可靠传输,而是
应用在那种需要同时支持可靠传输和不可靠传输的场合,单纯使用可靠UDP传输代替TCP传输,我个人的理解是完全没必要。
本代码完整包括2部分,分别是 ClientPort1 + ClientPasv1
ClientPasv1, 启动一个监听 等待另一端执行连接 , 主动发送文件 ,
主动发送流量进行极限测试
ClientPort1, 启动一个主动连接 , 去连接指定的远端地址和端口 , 接收文件 ,
接收流量进行极限测试
这两个代码为了简化并说明问题,没有执行后台数据与前台界面分离的原则,这是不安全的,但是作为示范,您可以看清楚整个流程
在实际应用中,请您务必遵守界面与后台数据脱离的原则,也就是,在回调函数中,不进行任何界面操作,而改用PostMessage代替,然后在主界面中响应Message
另外,这个代码只是示范了如何实现文件的发和送, 效率是不行的,因为 读盘-》[投递]发送->
[等待发送完成]-》接收-》写盘
高效的应该是 读盘 -》发送 , 然后立即预读下一块
,我们为了简明,没有进行优化,如果您要实现高效率文件传输,建议对次序进行优化
关于容错,这里我们基本没有进行容错设计,毕竟只是个示范代码
示范代码中所有的文件以及保存路径,请针对您自己的电脑进行修改 , 也就是文件头部分的定义
#define OBJ_SENDPATHFILE_T L"d:\\recvfile.iso"
//我们用来接收的文件
#define OBJ_SENDFILESIZE 621346816i64
//必须提前指定文件长度
另外如何在长时间传输中避免电脑进入休眠之类的处理,具体请参考windows api里的说明吧。
*/
下面的代码是我们在主程序中需要使用到的一些关键变量,其中,pcore1用来指向核心.
public:
void * pcore1;//核心指针
bool mb_listen;
volatile bool mb_transfile;//是否已经开始传输文件
CStdioFile mfileread;//文件读
__int64 ifilesize;
__int64 ifileread;
__int64 ifilewrite;
DWORD dw_start;
DWORD dw_end;
unsigned char u_laststep;
//时间记录使用上面的2个dw
volatile bool mb_transnet;//是否进行网络输出压力测试
__int64 i_send;//已经发送的字节数量
__int64 i_recv;//已经接收的字节数量
char * p_raw;//用来执行传输的内存块
下面这些是回调函数,ETudp采用了事件驱动方式,使用函数指针直接回调.
//通知上层 有新的数据包到达 ibuffsize 给出目前已经成功缓冲的数据大小
const void _stdcall P_ETUDataReady12(const
__int32 ibuffsize, const unsigned __int64 uspecial)
{
#ifdef _DEBUG
char buffs[512];
sprintf(buffs,"\n===>Core 1 , read buff ready ,
buffsize=%u \n\n",ibuffsize);
::OutputDebugStringA(buffs);
#endif
}
//通知上层 投递的数据包完成 注意可能是失败 ibuffsize为实际投递成功的长度
当时由于多线程的原因 这个ibuffsize只作为参考
const void _stdcall P_ETUDataSendDone12(const
__int32 ibuffsize, const unsigned __int64 uspecial)
{
if(pself->mb_transfile)
{
__int64 i641;
unsigned __int32 i321=0,i322=0;
i641=pself->ifilesize-pself->ifileread;
if(i641>=200*1024)
i321=200*1024;
else
i321=(unsigned __int32)i641;
if(i321>0)
{
//执行投递
void * pd1=::ETUdp_GetSendDataPtr(pself->pcore1);
if(pd1!=NULL)
{
//执行投递吧
i322=pself->mfileread.Read(pd1, i321);
CString str; //这里是不安全的写法 注意 界面和后台数据应该分离
str.Format(L"Read file: %u , real read:%u,
Tick:%u , lost packet:%I64d ,
delay=%u",i321,i322,::GetTickCount(
),::ETUdp_GetCurrentLostPackets(pself->pcore1),::ETUdp_GetCurrentDelayTick(pself->pcore1));
pself->m_list.AddString(str);
::ETUdp_SetSendingNewBuffSize(pself->pcore1,i322,NULL);
pself->ifileread+=i322;//实际读到的长度
}
else
{
CString str; //这里是不安全的写法 注意 界面和后台数据应该分离
str.Format(L"Error: failed to get ptr for write
new data");
pself->m_list.AddString(str);
}
}
else
{
//传输完成了
pself->dw_end=::GetTickCount( );
pself->m_list.AddString(L"======== Transfer file
done==========");
pself->mb_transfile=false;
pself->mfileread.Close( );
pself->m_buttonsend.EnableWindow(TRUE);
//统计并显示吧
CString str;
str.Format(L"File size:%I64d , read:%I64d all
done",pself->ifilesize,pself->ifileread);
pself->m_list.AddString(str);
str.Format(L"Transfer
seconds:%.2f",(float)((float)(pself->dw_end-pself->dw_start)/1000));
pself->m_list.AddString(str);
//实际上呢 因该采用消息 发送一个消息给主界面 由主界面根据消息来执行界面输出操作
//但是因为是测试代码 为了避免过分复杂 这里直接操作了界面
//再次说明 正规编写 必须实现后台数据与前台界面分离 本代码这里直接操作界面的写法是不安全的
}
}
else if(pself->mb_transnet)
{
//#ifdef _DEBUG
// CString str1;
// str1.Format(L"Success send data ,
tick=%u\n",::GetTickCount( ));
// ::OutputDebugStringW(str1);
//#endif
//上一个传输结束 由于是压力测试 因此这里采用的是无限制发送
pself->i_send+=500*1024;
//继续下一个传输 注意 由于我们是采用512KB缓冲 因此这里单个投递 500KB
不可以超过缓冲长度
//由于etudp采用了内外缓冲同一指针 这里 进行压力测试 可以跳过重复的内存拷贝
而采用直接修改新缓冲块长度来进行
::ETUdp_SetSendingNewBuffSize(pself->pcore1,500*1024,NULL);
}
}
//调用上层函数 处理不可靠传输数据包 以包的形式进行处理
const void _stdcall P_ETUUdpPacketFunc12(const
void * pdata , const unsigned __int16 isize, const
unsigned __int64 uspecial)
{
//#ifdef _DEBUG
// char buffs[512];
// sprintf(buffs,"\n===>Core 1 udp packet
function , udp packet size=%u \n\n",isize);
// ::OutputDebugStringA(buffs);
//#endif
}
//通知上层 连接完成 注意检测结果 可能是成功或者失败
const void _stdcall P_ETUConnectDone12(const
unsigned __int64 uspecial )
{
//#ifdef _DEBUG
// ::OutputDebugStringW(L"->Core 1 connect done
is called .....\n");
//#endif
}
//通知上层 连接中断 由于多线程运行的原因 这个函数可能会被多次调用 但是只需要处理一次就足够了
const void _stdcall P_ETUDisconnect12(const
unsigned __int64 uspecial )
{
//#ifdef _DEBUG
// ::OutputDebugStringW(L"->Core 1 disconnect
done is called .....\n");
//#endif
}
////上面的回调函数处理完成,下面看功能的实现
CClientPasv1Dlg::CClientPasv1Dlg(CWnd* pParent
/*=NULL*/)
: CDialog(CClientPasv1Dlg::IDD, pParent)
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
pcore1=NULL;//第一个核心指针
mb_listen=false;
u_laststep=0;
::ETUdp_InitAtStart( );
pself=&(*this);
//时间记录使用上面的2个dw
mb_transnet=false;//是否进行网络输出压力测试
i_send=0i64;//已经发送的字节数量
i_recv=0i64;//已经接收的字节数量
p_raw=NULL;//用来执行传输的内存块
this->ifileread=0i64;
this->ifilesize=0i64;
this->dw_end=0;
this->dw_start=0;
this->mb_transfile=false;
}
void CClientPasv1Dlg::OnBnClickedButton1()
{
// TODO: Add your control notification handler
code here
//start listen
if(this->mb_listen) //already listen
{
this->KillTimer(1234);
this->m_list.AddString(L"Now will try to stop
core 1");
::ETUdp_StopCore(pcore1);
::ETUdp_FreeCore(pcore1);
pcore1=NULL;
this->mb_listen=false;
this->u_laststep=0;
this->m_buttonfile.EnableWindow(FALSE);
this->m_buttonsend.EnableWindow(FALSE);
if(this->mb_transfile)
{
this->mfileread.Close( );
}
this->i_recv=0i64;
this->i_send=0i64;
this->mb_transfile=false;
this->mb_transnet=false;
this->m_list.AddString(L"Success stop core 1");
}
else
{
this->pcore1=::ETUdp_CreateCore( );
if(pcore1==NULL)
{
this->m_list.AddString(L"Failed to create
pcore1");
return;
}
else
{
this->m_list.AddString(L"Success create pcore1");
}
//设置参数吧
union
{
unsigned char b[4];
unsigned __int32 u32;
}uip;
uip.b[3]=127;
uip.b[2]=0;
uip.b[1]=0;
uip.b[0]=1;
CString str;
this->m_editremoteip.GetWindowTextW(str);
str.TrimLeft( ).TrimRight( );
if(str.GetLength( )>=7)
{
unsigned int uu[4];
if(::swscanf(str,L"%u.%u.%u.%u",&uu[3],&uu[2],&uu[1],&uu[0])==4)
{
uip.b[3]=(unsigned char)uu[3];
uip.b[2]=(unsigned char)uu[2];
uip.b[1]=(unsigned char)uu[1];
uip.b[0]=(unsigned char)uu[0];
}
}
unsigned __int32 uiport=3500;
this->m_editlocalport.GetWindowTextW(str);
str.TrimLeft( ).TrimRight( );
if(str.GetLength( )>0)
{
if(::swscanf(str,L"%u",&uiport)==1)
{
//success
}
else
{
uiport=3500;
}
}
//注意,我们开启的端口是3500 , 当然你可以更换成其他端口
,但是另一端接收部分也需要更换成相同端口
::ETUdp_SetPasvConnectIpv4Mode(pcore1,uip.u32,0,uiport/*3500*/);
::ETUdp_SetDataBuffSize(pcore1,512*1024,512*1024);//默认采用
512KB缓冲 因此单次最多投递或者接收不能超这个数字 那么500KB是比较理想的投递数字
unsigned __int64 u64arr[4];
u64arr[0]=1i64;
u64arr[1]=2i64;
u64arr[2]=3i64;
u64arr[3]=4i64; //设置握手的保密字 只要双方的保密字相同 就可以连接
否则会被拒绝
::ETUdp_SetConnectKey(pcore1,u64arr);
::ETUdp_SetUpnpMode(pcore1,false);
//下面设置回调函数
::ETUdp_SetCallbackOnBreak(pcore1,P_ETUDisconnect12);
::ETUdp_SetCallbackOnConnectDone(pcore1,P_ETUConnectDone12);
::ETUdp_SetCallbackOnDataReady(pcore1,P_ETUDataReady12);
::ETUdp_SetCallbackOnSendDone(pcore1,P_ETUDataSendDone12);
::ETUdp_SetCallbackOnUdpPacketArrive(pcore1,P_ETUUdpPacketFunc12);
//回调函数设置完成
__int32 iresult;
iresult=::ETUdp_StartCore(pcore1);
str.Format(L"pcore1 , start return
value=%d",iresult);
this->m_list.AddString(str);
if(iresult==0)
{
this->mb_listen=true;
//等待完成 开启定时器
this->SetTimer(1234,1000,NULL);
}
else
{
::ETUdp_StopCore(pcore1);
::ETUdp_FreeCore(pcore1);
pcore1=NULL;
str.Format(L"Error , last step
code=%d",::ETUdp_GetStepNum(pcore1));
this->m_list.AddString(str);
}
}
}
void CClientPasv1Dlg::OnBnClickedButton2()
{
// TODO: Add your control notification handler
code here
//start send file
if(this->mb_transfile==false) //已经在传输了 那么结束
{
//this->mfileread.Close( );
//this->mfilewrite.Close( );
this->m_list.AddString(L"========Start file
transfer==========");
if(this->pcore1==NULL )
{
this->m_list.AddString(L"Found net not ready ,
please connect first...\n");
return;
}
mb_transfile=false;//是否已经开始传输文件
//CStdioFile mfileread,mfilewrite;//分别是读和写
ifilesize=0i64;
ifileread=0i64;
ifilewrite=0i64;
dw_start=::GetTickCount( );
dw_end=0;
if(mfileread.Open(OBJ_SENDPATHFILE_T,CFile::modeRead|CFile::typeBinary,NULL)==FALSE)
{
this->m_list.AddString(L"Failed to open file for
read test.");
return ;
}
this->m_list.AddString(L"====Success open file
for read and write test ....\n");
ifilesize=(__int64)mfileread.GetLength( );
CString str;
str.Format(L"File size:%I64d , Start
tick=%u",ifilesize,dw_start);
this->m_list.AddString(str);
//按 200KB为1单位 执行传输
__int64 i641;
unsigned __int32 i321=0,i322=0;
i641=ifilesize-ifileread;
if(i641>=200*1024)
i321=200*1024;
else
i321=(unsigned __int32)i641;
this->m_buttonsend.EnableWindow(FALSE);//禁止其他按钮
//是的 开始传输了
this->mb_transfile=true;
if(i321>0)
{
//执行投递
void * pd1=::ETUdp_GetSendDataPtr(this->pcore1);
if(pd1!=NULL)
{
//执行投递吧
i322=mfileread.Read(pd1, i321);
str.Format(L"Read file: %u , real
read:%u\n",i321,i322);
::OutputDebugStringW(str);
this->m_list.AddString(str);
::ETUdp_SetSendingNewBuffSize(pcore1,i322,NULL);
ifileread+=i322;//实际读到的长度
}
}
//this->SetTimer(8802,1000,NULL);
}
else
{
//否则 开始结束传输
this->mb_transfile=false;
this->mfileread.Close( );
this->m_list.AddString(L"----------File transfer
done----------------");
}
}
具体我们建议您参考我们提供的2个示范工程代码.