2. DWORD WINAPI TMainWin::SendFileThread(void *_sendFileObj)
3. {
4. SendFileObj *obj = (SendFileObj *)_sendFileObj;
5. fd_set fds;
6. fd_set *rfds = NULL, *wfds = &fds;
7. timeval tv;
8. int sock_ret;
9. BOOL ret = FALSE, completeWait = FALSE;
10. // 这里SendFileFunc根据command类型自动选择两种函数 : send file or send directory
11. BOOL (TMainWin::*SendFileFunc)(SendFileObj *obj) =
12. obj->command == IPMSG_GETDIRFILES ? TMainWin::SendDirFile : TMainWin::SendFile;
13.
14. FD_ZERO(&fds);
15. FD_SET(obj->conInfo->sd, &fds);
16. // 这里for条件引入了一个简单的超时机制
17. // 正常情况下,只要文件未传送完,循环不会退出
18. for (int waitCnt=0; waitCnt < 180 && obj->hThread != NULL; waitCnt++)
19. {
20. tv.tv_sec = 1, tv.tv_usec = 0;
21. // 这里select有什么用途呢? 对于select功能我还不是完全明白
22. // 根据我的分析,这里主要是利用了select函数的等待功能
23. // 如果sd描述符没有就绪,则在select中最久等待1秒
24. // 如此反复等待最多180次,也就是3分钟,超过三分钟后,for循环结束
25. if ((sock_ret = ::select(obj->conInfo->sd + 1, rfds, wfds, NULL, &tv)) > 0)
26. {
27. // 套接字可用,清除等待
28. waitCnt = 0;
29.
30. //下面的代码是一个有限状态机
31. /*
32. * 控制变量 completeWait, obj->status
33. * 状态机迁移过程(一般状态下):
34. * (1) if ((mainWin->*SendFileFunc)(obj) != TRUE)
35. * (2) if (obj->status == FS_COMPLETE)
36. * (3) if (completeWait)
37. *
38. * 1首先被反复执行,直到文件发送完毕。
39. * 在没有错误的情况下,1总是等价于if(false)
40. * 于是其后的2每次都会被执行,判断是否完成
41. * 一旦完成,completeWait被设置为True,
42. * 在下一次循环里,进入3
43. * 在3的语句体内执行recv函数,等待对方应答
44. *
45. */
46. if (completeWait)
47. {
48. // 本分支在文件发送完后执行
49. if (::recv(obj->conInfo->sd, (char *)&ret, sizeof(ret), 0) >= 0)
50. ret = TRUE;
51. break;
52. }
53. else if ((mainWin->*SendFileFunc)(obj) != TRUE)
54. {
55. //本分支仅在发送出错时进行
56. break;
57. }
58. else if (obj->status == FS_COMPLETE)
59. {
60. // 本分支在发送完成后执行
61. completeWait = TRUE, rfds = &fds, wfds = NULL;
62. if (obj->fileSize == 0) { ret = TRUE; break; }
63. }
64. }
65. else if (sock_ret == 0) {
66. // select超时,重置fds
67. FD_ZERO(&fds);
68. FD_SET(obj->conInfo->sd, &fds);
69. }
70. else if (sock_ret == SOCKET_ERROR) {
71. // select错误,算了,离去吧~
72. break;
73. }
74. }
75.
76. // 如果发送的是文件夹,还需要擦一下屁股
77. if (obj->isDir)
78. {
79. mainWin->CloseSendFile(obj);
80. while (--obj->dirCnt >= 0)
81. ::FindClose(obj->hDir[obj->dirCnt]);
82. }
83.
84. // ret是对方发回的返回值,告知发送方是否完成接收
85. obj->status = ret ? FS_COMPLETE : FS_ERROR;
86. // 发送TCPEVENT消息,关闭句柄
87. // 消息处理流程: EventUser->TcpEvent->EndSendFile
88. mainWin->PostMessage(WM_TCPEVENT, obj->conInfo->sd, FD_CLOSE);
89. // 退出发送线程
90. ::ExitThread(0);
91. return 0;
92. }
93.
上面传送数据最重要的一句是:
else if ((mainWin->*SendFileFunc)(obj) != TRUE)
SendFileFunc的实际内容是什么呢?由函数开始赋值的指针知道:
1.
2. BOOL TMainWin::SendFile(SendFileObj *obj)
3. {
4. if (obj == NULL || obj->hFile == INVALID_HANDLE_VALUE) //判断文件句柄是否合法
5. return FALSE;
6.
7. int size = 0;
8. _int64 remain = obj->fileSize - obj->offset; //取得还需要传递的总字节数
9.
10. //传数据
11. if (remain > 0 && (size = ::send(obj->conInfo->sd, obj->mapAddr + (obj->offset % cfg->ViewMax), remain > cfg->TransMax ? cfg->TransMax : (int)remain, 0)) < 0)
12. return FALSE;
13.
14. // 根据本次成功发送的数据量,调整offset
15. obj->offset += size;
16.
17. // 如果offset等于文件大小了,那么设置obj状态为完成
18. // 由于存在传文件夹模式和传文件模式,所以状态分情况设置
19. if (obj->offset == obj->fileSize)
20. obj->status = obj->command == IPMSG_GETDIRFILES ? FS_ENDFILE : FS_COMPLETE;
21. else if ((obj->offset % cfg->ViewMax) == 0)//没有完成,但是已经传送完成了本部分数据映射,需要调整映射窗口
22. {
23. ::UnmapViewOfFile(obj->mapAddr); // 删除旧映射
24. remain = obj->fileSize - obj->offset; // 计算新的剩余量
25. // 映射下一块,一次8M ,如果只剩下最后一点了,则少于8M (remain)
26. obj->mapAddr = (char *)::MapViewOfFile(obj->hMap, FILE_MAP_READ, (int)(obj->offset >> 32), (int)obj->offset, (int)(remain > cfg->ViewMax ? cfg->ViewMax : remain));
27. }
28. // 更新总消耗时间
29. obj->conInfo->lastTick = ::GetTickCount();
30.
31. return TRUE;
32. }
33.
|