/*******************************************************
* FilePoster关键代码
*E-mail: andy.zhshg@163.com
*日期: 2008.12.25
*
*程序描述:
*FilePoster是基于Win32平台的网络文件传输程序。开发平台为
*Visual C++6.0。
*程序采用服务器/客户机模式,服务器用于接收数据,客户机负
责发送数据。利用windows多线程原理,集接收和发送功能于一
体。
*网络传输采用UDP原理,为解决UDP传输的不可靠性,用Windows
*消息对收发的双方进行同步,即发送方先发送消息请求传送文件
*同时发送要传文件的基本信息,收取方收到后回复发送发一个确
*认消息,然后发送方依次发送被分成固定大小块(256字节)的文
*件数据,收取方每收到一个数据块就写入自己的文件并回复发送
*方一个接收完毕消息,以使得发送方发送下一个数据块。发送方
*发送最后一个数据块时添加发送结束标记,接受方接收完毕后关
*闭文件,至此发送过程结束。
*
*问题阐述:
*程序的缺陷在于只能用于具有固定IP的主机之间的文件传输。在
*处于不同的内网中的主机无能为力。因为对于内网中的计算机其
*IP是主机通过映射后分配来的所以如果不借助于服务器很难从外
*网访问内网的计算机。不过对于处于同一局域网的计算机,本程
*序还是能够完全应付的。
*限于篇幅,与文件传输无关的代码省略,以"......"代替。
*********************************************************/
/*********************************************************
*程序的初始化函数
*********************************************************/
BOOL CFilePosterApp::InitInstance()
{
//加载套接字库
if(!AfxSocketInit())
{
AfxMessageBox("加载套接字库失败!");
return FALSE;
}
......
}
/**********************************************************
*主窗口的初始化函数
*struct RECVPARAM 用于传递线程数据
**********************************************************/
struct RECVPARAM
{
HWND hWnd;
SOCKET sock;
};
BOOL CFilePosterDlg::OnInitDialog()
{
......
//设定进度条的步进值
m_progress.SetStep(1);
m_progress_r.SetStep(1);
//创建套接字
m_socket=socket(AF_INET,SOCK_DGRAM,0);
if(INVALID_SOCKET==m_socket)
{
MessageBox("套接字创建失败!");
return FALSE;
}
SOCKADDR_IN addrSock;
addrSock.sin_family=AF_INET;
addrSock.sin_port=htons(6800);
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
//绑定套接字
int retval;
retval=bind(m_socket, (SOCKADDR*)&addrSock,
sizeof(SOCKADDR));
if(SOCKET_ERROR==retval)
{
closesocket(m_socket);
MessageBox("绑定失败!");
return FALSE;
}
//产生一个用于接收数据的线程
struct RECVPARAM *pRecvParam=new RECVPARAM;
pRecvParam->sock=m_socket;
pRecvParam->hWnd=m_hWnd;
HANDLE hThread=CreateThread(NULL, 0, RecvProc,
(LPVOID)pRecvParam, 0, NULL);
CloseHandle(hThread);
......
}
/**************************************************************
*接收线程的回调函数
*发送的数据块结构解析:
*类别 字节号 内容
*H 1 通信头,申请发送
*H 2~257 文件名
*H 258~... 文件大小
*D 1 通信头,拒绝接收
*R 1 通信头,同意接收
*F 1 通信头,发送文件块
*F 2~17 保留
*F 18~... 文件数据块
*E 1 通信头,文件尾发送
*E 2~17 块大小
*E 18~... 文件数据块
**************************************************************/
DWORD WINAPI CFilePosterDlg::RecvProc(LPVOID lpParameter)
{
SOCKET sock=((RECVPARAM*)lpParameter)->sock;
HWND hWnd=((RECVPARAM*)lpParameter)->hWnd;
delete lpParameter;
SOCKADDR_IN addrFrom;
int len=sizeof(SOCKADDR);
char recvBuf[0x112]; //256+17字节的接受缓冲数组
char fileName[0x100]; //256字节的文件名存储区
int retval, i;
FILE* file = NULL;
while(TRUE)
{
//接受UDP数据
retval=recvfrom(sock, recvBuf, 0x112, 0,
(SOCKADDR*)&addrFrom, &len);
if(SOCKET_ERROR == retval)
break;
//收到消息头为'R',即对方同意让你继续发送数据
if (recvBuf[0] == 'R')
{
char wParam = 'R';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, 0);
}
//收到消息头为'D',即对方拒绝让你继续发送数据
else if (recvBuf[0] == 'D')
{
char wParam = 'D';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, 0);
}
//收到消息头为'H',即对方申请给你发送信息,并送来文件的信息
else if (recvBuf[0] == 'H')
{
//从收到的数据中提取文件名信息
for (i = 1; i <= 0x100 && recvBuf[i] != '/0'; i++)
fileName[i-1] = recvBuf[i];
fileName[i-1] = '/0';
//从收到的数据中提取文件大小信息
CString recvMsg;
nFileSize = atoi(&recvBuf[0x101]);
recvMsg.Format("收到来自于(%s)的文件:%s/n文件大小:%i字节/n是否接收?",
inet_ntoa(addrFrom.sin_addr), fileName, nFileSize);
//用消息框提示用户有人要发送文件
if (IDOK == AfxMessageBox(recvMsg, MB_OKCANCEL))
{
//若用户同意接收,提供一个文件保存对话框用于设定保存的路径
CFileDialog saveDlg(false, NULL, fileName);
if (IDOK == saveDlg.DoModal())
{
//创建一个文件用于复制接收的文件数据
if (!(file = fopen(saveDlg.GetPathName(), "wb")))
{
AfxMessageBox("创建本地文件失败!");
continue;
}
char wParam = 'H';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, (LPARAM)&addrFrom);
}
else
{
char wParam = 'C';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, (LPARAM)&addrFrom);
}
}
else //用户拒绝接收
{
char wParam = 'C';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, (LPARAM)&addrFrom);
}
}
//收到的消息头为'F',即对方发来的是文件数据
else if (recvBuf[0] == 'F')
{
//将文件数据写入本地文件中
fwrite(&recvBuf[0x12], 1, 0x100, file);
char wParam = 'F';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, (LPARAM)&addrFrom);
}
//收到的消息头为'E',即对方发来最后一个数据块
else if (recvBuf[0] == 'E')
{
//获取数据块的大小
int bufSize = atoi(&recvBuf[1]);
//将数据块写入本地文件,并关闭文件
fwrite(&recvBuf[0x12], 1, bufSize, file);
fclose(file);
char wParam = 'E';
::PostMessage(hWnd, WM_READY_TO_RECEIVE,
(WPARAM)&wParam, (LPARAM)&addrFrom);
}
//收到未定义的数据头
else
AfxMessageBox("传送数据过程中出现错误!");
}
return (DWORD)NULL;
}
/*************************************************************
*按下发送键,发出文件信息的函数
*************************************************************/
void CFilePosterDlg::OnOK()
{
if (m_posting) //bool m_posting 表示程序是否正在发送文件
{
MessageBox("数据发送中,请稍候再试。");
return;
}
UpdateData();
if (m_filePath == "")
{
MessageBox("请输入要发送的文件路径!");
return;
}
if (m_IPAddr.IsBlank())
{
MessageBox("请添入接收者的IP地址。");
return;
}
WIN32_FIND_DATA FindFileData;
if (INVALID_HANDLE_VALUE == FindFirstFile(m_filePath, &FindFileData))
{
MessageBox("文件路径错误或文件不存在!/n请重新指定文件路径。");
return;
}
DWORD dwIP;
m_IPAddr.GetAddress(dwIP);
SOCKADDR_IN addrTo;
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6800);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
//构建文件信息数据块
char sendBuf[0x112];
int i;
//消息头
sendBuf[0] = 'H';
//文件名
for (i = 1; i <= 0x100 && FindFileData.cFileName[i-1] != '/0'; i++)
sendBuf[i] = FindFileData.cFileName[i-1];
sendBuf[i] = '/0';
//文件大小
_itoa(FindFileData.nFileSizeLow, &sendBuf[0x101], 10);
sendBuf[0x111] = '/0';
//发送数据块
sendto(m_socket, sendBuf, 0x112, 0,
(SOCKADDR*)&addrTo, sizeof(SOCKADDR));
//打开文件,等待读取
if (!(m_file = fopen(m_filePath, "rb")))
{
MessageBox("读取文件失败!");
}
m_nSend = 0; //已发送的块数
m_nFileSize_s = FindFileData.nFileSizeLow; //文件大小
m_progress.SetRange(0, m_nFileSize_s/0x100+1);//设置发送进度条
m_posting = true; //标明发送正进行
}
/************************************************************
*消息响应函数
*响应自定义消息WM_READY_TO_RECEIVE
************************************************************/
void CFilePosterDlg::OnReadyToRecv(WPARAM wParam,LPARAM lParam)
{
char sendBuf[0x112];
DWORD dwIP;
m_IPAddr.GetAddress(dwIP);
SOCKADDR_IN addrTo;
addrTo.sin_family=AF_INET;
addrTo.sin_port=htons(6800);
addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
int nRead;
switch (*(char*)wParam)
{
//对方拒绝接收文件,关闭已打开的文件
case 'D':
MessageBox("对方拒绝接受你发送的文件!");
fclose(m_file);
m_posting = false;
break;
//对方同意接收文件
case 'R':
nRead = fread(&sendBuf[0x12], 1, 0x100, m_file);
//读取的文件小于256字节,则读到文件尾
if (nRead < 0x100)
{
sendBuf[0] = 'E';
_itoa(nRead, &sendBuf[1], 10);
sendto(m_socket, sendBuf, nRead+0x12, 0,
(SOCKADDR*)&addrTo, sizeof(SOCKADDR));
fclose(m_file);
m_progress.SetPos(m_nFileSize_s/0x100+1);
m_posting = false;
m_send = "发送进度:(100%)";
UpdateData(false);
MessageBox("发送完毕!");
m_progress.SetPos(0);
m_send = "发送进度:";
UpdateData(false);
}
//读到文件等于256字节,则文件还未读完
else
{
sendBuf[0] = 'F';
sendto(m_socket, sendBuf, 0x112, 0,
(SOCKADDR*)&addrTo, sizeof(SOCKADDR));
m_progress.StepIt();
m_nSend++;
m_send.Format("发送进度:(%.1f%%)",
(float)m_nSend/(m_nFileSize_s/0x100+1)*100);
UpdateData(false);
}
break;
//同意接收对方文件
case 'H':
m_progress_r.SetRange(0, nFileSize/0x100+1);
m_nRecv = 0;
case 'F':
sendto(m_socket, "R", 2, 0,
(SOCKADDR*)lParam, sizeof(SOCKADDR));
m_progress_r.StepIt();
m_nRecv++;
m_recv.Format("接收进度:(%.1f%%)", (float)m_nRecv/(nFileSize/0x100+1)*100);
UpdateData(false);
break;
//接受完毕,提示用户
case 'E':
m_progress_r.SetPos(nFileSize/0x100+1);
m_recv = "接收进度:(100%)";
UpdateData(false);
MessageBox("接收完毕!");
m_recv = "接收进度:";
m_progress_r.SetPos(0);
UpdateData(false);
break;
//拒绝接收,通知对方
case 'C':
sendto(m_socket, "D", 2, 0,
(SOCKADDR*)lParam, sizeof(SOCKADDR));
break;
}
}