飞鸽传书源码分析六-文件传输续
来源:互联网 发布:小学生绘画软件下载 编辑:程序博客网 时间:2024/05/01 02:19
在飞鸽传书源码分析五-文件传输这篇文章中分析了,如何将要发送的文件先传至要传送到的机器上。此时接收端的机器已经“知道”要接收文件了。本篇文章详细介绍飞鸽传书文件的传输过程。为了简化,我们选择只传输一个文件进行分析。
1.接收文件的结构
1.1 RecvFileObj结构
struct RecvFileObj { ConnectInfo *conInfo; //网络连接信息 FileInfo *fileInfo; //文件信息 BOOL isDir; //要接收的是不是文件夹 FileInfo curFileInfo; // _int64 offset; //当前已经传输文件的offset _int64 woffset; char *recvBuf; //接收到的信息 HANDLE hFile; //为接收文件创建的handle HANDLE hThread; //为接收文件创建的线程handle int infoLen; int dirCnt; _int64 totalTrans; //已经传文件字节数 DWORD startTick; //开始时间戳 DWORD lastTick; //最后时间戳 int totalFiles; //总文件数 FileStatus status; //文件状态 char saveDir[MAX_PATH]; //接收文件存储的位置, char path[MAX_PATH]; char info[MAX_BUF]; // for dirinfo buffer};
1.2 ConnectInfo结构
struct ConnectInfo : public TListObj { SOCKET sd; //socket ULONG addr; //要发送文件的ip地址 int port; //要发送文件的端口 BOOL server; //是否为服务器 BOOL complete; //连接是否完成 DWORD startTick; DWORD lastTick;};
2.接收端向发送端发起tcp连接
在飞鸽传书源码分析五-文件传输最后点击按钮后会出现一个选择保存文件所在路径的对话框。
case FILE_BUTTON: ... else if (fileObj) { TSaveCommonDlg dlg(shareInfo, cfg, this); if (dlg.Exec()) { memset(fileObj, 0, sizeof(RecvFileObj)); strncpyz(fileObj->saveDir, cfg->lastSaveDir, MAX_PATH); SaveFile(); } } ...
BOOL TRecvDlg::SaveFile(void){ int target; //处理客户端选中的将要接收的文件 for (target=0; target < shareInfo->fileCnt; target++) if (shareInfo->fileInfo[target]->IsSelected()) break; if (target == shareInfo->fileCnt) return FALSE; memset(fileObj, 0, (char *)&fileObj->totalTrans - (char *)fileObj); fileObj->conInfo = new ConnectInfo; //将要接收的文件信息 fileObj->fileInfo = shareInfo->fileInfo[target]; ... if (ConnectRecvFile()) SetDlgItemText(FILE_BUTTON, PREPARETRANS_MSGSTR); ... return TRUE;}
BOOL TRecvDlg::ConnectRecvFile(void){ memset(fileObj->conInfo, 0, sizeof(ConnectInfo)); fileObj->conInfo->addr = msg.hostSub.addr; fileObj->conInfo->port = msg.hostSub.portNo; if (msgMng->Connect(hWnd, fileObj->conInfo) != TRUE) return FALSE; if (fileObj->conInfo->complete) StartRecvFile(); return TRUE;}
BOOL TRecvDlg::ConnectRecvFile(void){ memset(fileObj->conInfo, 0, sizeof(ConnectInfo)); //发送端的ip地址 fileObj->conInfo->addr = msg.hostSub.addr; //发送端的端口 fileObj->conInfo->port = msg.hostSub.portNo; //连接发送端,将待发送文件机器作为server,接收文件端作为client if (msgMng->Connect(hWnd, fileObj->conInfo) != TRUE) return FALSE; if (fileObj->conInfo->complete) StartRecvFile(); return TRUE;}
BOOL TRecvDlg::StartRecvFile(void){ char buf[MAX_PATH], tcpbuf[MAX_BUF]; wsprintf(buf, fileObj->isDir ? "%x:%x:" : "%x:%x:%x:", msg.packetNo, fileObj->fileInfo->Id(), OFFSET); fileObj->conInfo->complete = TRUE; msgMng->MakeMsg(tcpbuf, fileObj->isDir ? IPMSG_GETDIRFILES : IPMSG_GETFILEDATA, buf); ... //向server发送一条IPMSG消息 if (::send(fileObj->conInfo->sd, tcpbuf, strlen(tcpbuf), 0) < (int)strlen(tcpbuf)) return EndRecvFile(), FALSE; ... //设置当前接收文件夹的信息为从server发送过来的文件信息 if (fileObj->isDir == FALSE) fileObj->curFileInfo = *fileObj->fileInfo; ... DWORD id; fileObj->hThread = (HANDLE)~0; // 创建一个线程用于接收文件处理 if ((fileObj->hThread = ::CreateThread(NULL, 0, RecvFileThread, this, 0, &id)) == NULL) { EndRecvFile(); return FALSE; } return TRUE;}
DWORD WINAPI TRecvDlg::RecvFileThread(void *_recvDlg){ TRecvDlg *recvDlg = (TRecvDlg *)_recvDlg; RecvFileObj *fileObj = recvDlg->fileObj; fd_set rfd; timeval tv; int sock_ret; BOOL (TRecvDlg::*RecvFileFunc)(void) = fileObj->isDir ? &TRecvDlg::RecvDirFile : &TRecvDlg::RecvFile; FD_ZERO(&rfd); FD_SET(fileObj->conInfo->sd, &rfd); for (int waitCnt=0; waitCnt < 120 && fileObj->hThread != NULL; waitCnt++) { tv.tv_sec = 1, tv.tv_usec = 0; if ((sock_ret = ::select(fileObj->conInfo->sd + 1, &rfd, NULL, NULL, &tv)) > 0) { waitCnt = 0; if ((recvDlg->*RecvFileFunc)() != TRUE) break; if (fileObj->status == FS_COMPLETE) break; } else if (sock_ret == 0) { FD_ZERO(&rfd); FD_SET(fileObj->conInfo->sd, &rfd); fileObj->conInfo->lastTick = ::GetTickCount(); recvDlg->PostMessage(WM_RECVDLG_FILEBUTTON, 0, 0); } else if (sock_ret == SOCKET_ERROR) { break; } } recvDlg->CloseRecvFile(fileObj->status == FS_COMPLETE ? TRUE : FALSE); if (fileObj->status != FS_COMPLETE) fileObj->status = FS_ERROR; recvDlg->PostMessage(WM_TCPEVENT, fileObj->conInfo->sd, FD_CLOSE); ::ExitThread(0); return 0;}
根据函数的调用关系,总算找到了发送接收文件的处理函数”RecvFileThread”,但是这个函数并不真正处理文件信息,在RecvFileThread函数中,我们看到有一个select函数。上面的select函数作用是如果当前tcp socket中有可读的数据,那么select就会返回>0的一个数。表示发送端已经向接收端发送的数据。当有数据可读时就交给recvDlg->*RecvFileFunc所指向的函数完成数据的处理(这里我们只考虑单个文件的传输,所以RecvFileFunc所指就是TRecvDlg::RecvFile)。
3.文件接收端的处理
飞鸽传书在windows使用的网络是多路复用select模型。
BOOL TRecvDlg::RecvFile(void){ //wresid用于表示recvBuf前面多少字节用于每次接收数据后固定写到文件中。 int wresid = (int)(fileObj->offset - fileObj->woffset); //remain剩下的未接收文件大小 _int64 remain = fileObj->curFileInfo.Size() - fileObj->offset; int size = 0; if (remain > cfg->TransMax - wresid) remain = cfg->TransMax - wresid; //接收到的数据从recvBuf+wresid内存处开始存放 if ((size = ::recv(fileObj->conInfo->sd, fileObj->recvBuf + wresid, (int)remain, 0)) <= 0) return FALSE; //如果接收数据的文件还没创建,则调用OpenRecvFile创建一个文件用于将接收到的数据写到文件中。 if (fileObj->hFile == INVALID_HANDLE_VALUE) if (OpenRecvFile() == FALSE) return FALSE; //固定数据+接收到的数据 wresid += size; //当前接收的数据大小为cfg->TransMax(数据还未传完),或者已经接收数据(fileObj->offset)+当前接收数据(size)大于等文件大小 //将接收到的数据(加上前面固定写入的字符)写入到文件中。 if (fileObj->offset + size >= fileObj->curFileInfo.Size() || cfg->TransMax == wresid) { DWORD wsize; if (::WriteFile(fileObj->hFile, fileObj->recvBuf, wresid, &wsize, 0) != TRUE || wresid != (int)wsize) return MessageBox(WRITEFAIL_MSGSTR), FALSE; fileObj->woffset += wresid;//总写入到文件中数据大小(包括固定字符--如果有) } fileObj->offset += size;//从server已接收到的数据大小 DWORD nowTick = ::GetTickCount(); if (nowTick - fileObj->conInfo->lastTick >= 1000) { fileObj->conInfo->lastTick = nowTick; PostMessage(WM_RECVDLG_FILEBUTTON, 0, 0); } //如果接收到数据大小大于等于服务器原文件大小。表示文件传输完成。 if (fileObj->offset >= fileObj->curFileInfo.Size()) fileObj->status = fileObj->isDir ? FS_ENDFILE : FS_COMPLETE; return TRUE;}
4.文件发送端的处理
飞鸽使用的是一个线程用于文件发送处理。线程回调函数是SendFileThread,这个函数中调用了SendFile函数,SendFile就是处理文件信息读取及发送到网络上的任务
BOOL TMainWin::SendFile(SendFileObj *obj){ if (obj == NULL || obj->hFile == INVALID_HANDLE_VALUE) return FALSE; int size = 0; _int64 remain = obj->fileSize - obj->offset; int transMax = cfg->TransMax - (int)(obj->offset % cfg->TransMax); //将磁盘上已经映射到内存的文件数据发送给接收端 if (remain > 0 && (size = ::send(obj->conInfo->sd, obj->mapAddr + (obj->offset % cfg->ViewMax), (int)(remain > transMax ? transMax : remain), 0)) < 0) return FALSE; //计算已经发送的字节数 obj->offset += size; //文件发送完成 if (obj->offset == obj->fileSize) obj->status = obj->command == IPMSG_GETDIRFILES ? FS_ENDFILE : FS_COMPLETE; else if ((obj->offset % cfg->ViewMax) == 0) { //还有剩余的数据未发送 ::UnmapViewOfFile(obj->mapAddr); remain = obj->fileSize - obj->offset; //将剩余数据部分(或全部映射到内存) obj->mapAddr = (char *)::MapViewOfFile(obj->hMap, FILE_MAP_READ, (int)(obj->offset >> 32), (int)obj->offset, (int)(remain > cfg->ViewMax ? cfg->ViewMax : remain)); } obj->conInfo->lastTick = ::GetTickCount(); return TRUE;}
2 0
- 飞鸽传书源码分析六-文件传输续
- 飞鸽传书源码分析五-文件传输
- 飞鸽传书源码分析三-网络
- 飞鸽传书源码分析三-网络
- Linux下的飞鸽传书源码(ipmsg、聊天、文件传输)
- Linux下的飞鸽传书源码(ipmsg、聊天、文件传输) --ipmsg4Linux_ypxing0519.tar
- 飞鸽传书文件传输实现原理
- 飞鸽传书文件传输实现原理
- 飞鸽传书文件传输实现原理
- IPMsg飞鸽传书--文件传输解析
- 飞鸽传书 文件传输软件
- IPMsg(飞鸽传书)文件发送源码分析
- 飞鸽传书源码分析-程序启动过程
- 飞鸽传书源码分析二消息机制
- 飞鸽传书源码分析四-消息发送
- 飞鸽传书源码分析-程序启动过程
- 飞鸽传书源码分析-程序启动过程
- 飞鸽传书源码分析二消息机制
- USACO 2.1.3 Sorting A Three-Valued Sequence
- hihoCoder 1093 最短路径·三:SPFA算法
- 二分查找算法
- hdoj-1505-City Game【动态规划】1506的加强版
- [基本实验] 远程桌面攻击
- 飞鸽传书源码分析六-文件传输续
- 为什么中断handler中不能休眠
- Kubernetes技术分析之存储
- close 和shutdown 函数
- 着色问题
- C++中的多态
- 《编程思想-笔记》-第三章,第四章
- servlet 获取 post body 体 (用流读取为空的问题)
- Win10C:\Windows\System32\drivers\etc\hosts提示权限不够问题