UDP文件传输的实现

UDP文件传输的实现_第1张图片

源文件地址:http://download.csdn.net/source/890950

/******************************************************* * FilePoster关键代码 *E-mail: [email protected] *日期: 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; } }



PS(20130219):

我这个程序是四年前上学的时候的一个作业,简单的做了一个例子,很基础,并没有考虑丢包的问题,在网络环境好,文件体积不太大的情况下还可以比较好的工作。UDP协议的不可靠性体现在数据包的到达与否以及到达的顺序都是不能保证的,所以接收端简单的把接到的包顺序拼接不一定会与发送端分割前的文件保持一致。

有网友发信询问丢包的处理问题,下面是我的一些简单思路:

为每个包创建一个序号,接收端根据接到包的序号进行拼接,接收完毕后自己排查一边缺失哪些包再跟发送端主动索取。

我也是好久不接触这方面的东西了,提出的方案可能不是最准确的,希望能对大家有所帮助。

大家有过有问题可以再新浪微博@andyzhshg


你可能感兴趣的:(C++开发)