Code
1 UDP实现可靠文件传输
2 大家都清楚,如果用TCP传输文件的话,是很简单的,根本都不用操心会丢包,除非是网络坏了,就得重来。用UDP的话,因为UDP是不可靠的,所以用它传输文件,要保证不丢包,就得我们自己写额外的代码来保障了。本文就说说如果保证可靠传输。
3
4 要实现无差错的传输数据,我们可以采用重发请求(ARQ)协议,它又可分为连续ARQ协议、选择重发ARQ协议、滑动窗口协议。本文重点介绍滑动窗口协议,其它的两种有兴趣的可参考相关的网络通信之类的书。
5
6 采用滑动窗口协议,限制已发送出去但未被确认的数据帧的数目。循环重复使用已收到的那些数据帧的序号。具体实现是在发送端和接收端分别设定发送窗口和接收窗口。
7 (1)发送窗口
8 发送窗口用来对发送端进行流量控制。发送窗口的大小Wt代表在还没有收到对方确认的条件下,发送端最多可以发送的数据帧的个数。具体意思请参考下图:
9
10
11 (2)接收窗口
12 接收窗口用来控制接收数据帧。只有当接收到的数据帧的发送序号落在接收窗口内,才允许将该数据帧收下,否则一律丢弃。接收窗口的大小用Wr来表示,在连续ARQ协议中,Wr = 1。接收窗口的意义可参考下图:
13
14
15 在接收窗口和发送窗口间存在着这样的关系:接收窗口发生旋转后,发送窗口才可能向前旋转,接收窗口保持不动时,发送窗口是不会旋转的。这种收发窗口按如此规律顺时钟方向不断旋转的协议就犯法为滑动窗口协议。
16
17 好了,在上面对滑动窗口协议有大致了解后,我们还是进入正题吧:)
18
19 发送端的发送线程:
20 int ret;
21 int nPacketCount = 0;
22 DWORD dwRet;
23 SendBuf sendbuf;
24 DWORD dwRead;
25 DWORD dwReadSize;
26
27 SendBuf* pushbuf;
28
29 //计算一共要读的文件次数,若文件已读完,但客户端没有接收完,
30 //则要发送的内容不再从文件里读取,而从m_bufqueue里提取
31 nPacketCount = m_dwFileSize / sizeof(sendbuf.buf);
32
33 //若不能整除,则应加1
34 if(m_dwFileSize % sizeof(sendbuf.buf) != 0)
35 ++nPacketCount;
36
37 SetEvent(m_hEvent);
38
39 CHtime htime;
40
41 //若已发送大小小于文件大小并且发送窗口前沿等于后沿,则继续发送
42 //否则退出循环
43
44 if(m_dwSend < m_dwFileSize) // 文件没有传输完时才继续传输
45 {
46 while(1)
47 {
48 dwRet = WaitForSingleObject(m_hEvent, 1000);
49 if(dwRet == WAIT_FAILED)
50 {
51 return false;
52 }
53 else if(dwRet == WAIT_TIMEOUT)
54 {
55 //重发
56 ::EnterCriticalSection(&m_csQueue); // 进入m_bufqueue的排斥区
57 ret = m_hsocket.hsendto((char*)m_bufqueue.front(), sizeof(sendbuf));
58 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
59 if(ret == SOCKET_ERROR)
60 {
61 cout << "重发失败,继续重发" << endl;
62 continue;
63 }
64
65 ResetEvent(m_hEvent);
66 continue;
67 }
68
69 //若发送窗口大小 < 预定大小 && 已读文件次数(nReadIndex) < 需要读文件的次数(nReadCount),则继续读取发送
70 //否则,要发送的内容从m_bufqueue里提取
71 if(m_dwSend < m_dwFileSize)
72 {
73 dwReadSize = m_dwFileSize - m_dwSend;
74 dwReadSize = dwReadSize < MAXBUF_SIZE ? dwReadSize : MAXBUF_SIZE;
75
76 memset(sendbuf.buf, 0, sizeof(sendbuf.buf));
77 if(!ReadFile(m_hFile, sendbuf.buf, dwReadSize, &dwRead, NULL))
78 {
79 //AfxMessageBox("读取文件失败,请确认文件存在或有读取权限.");
80 cout << "读取文件失败,请确认文件存在或有读取权限." << endl;
81 return false;
82 }
83
84 m_dwSend += dwRead;
85
86 sendbuf.index = m_nSendIndexHead;
87 m_nSendIndexHead = (m_nSendIndexHead + 1) % Sliding_Window_Size; // 发送窗口前沿向前移一格
88
89 sendbuf.dwLen = dwRead;
90
91 //保存发送过的数据,以便重发
92 ::EnterCriticalSection(&m_csQueue); // 进入m_bufqueue的排斥区
93 pushbuf = GetBufFromLookaside();
94
95 memcpy(pushbuf, &sendbuf, sizeof(sendbuf));
96
97 m_bufqueue.push(pushbuf);
98 if(m_dwSend >= m_dwFileSize) // 文件已读完,在队列中加一File_End标志,以便判断是否需要继续发送
99 {
100 pushbuf = GetBufFromLookaside();
101
102 pushbuf->index = File_End;
103 pushbuf->dwLen = File_End;
104 memset(pushbuf->buf, 0, sizeof(pushbuf->buf));
105
106 m_bufqueue.push(pushbuf);
107 }
108 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
109 }
110
111 ::EnterCriticalSection(&m_csQueue); // 进入m_bufqueue的排斥区
112 if(m_bufqueue.front()->index == File_End) // 所有数据包已发送完毕,退出循环
113 {
114 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
115 break;
116 }
117 else if(m_bufqueue.size() <= Send_Window_Size) // 发送窗口小于指定值,继续发送
118 {
119 ret = m_hsocket.hsendto((char*)m_bufqueue.front(), sizeof(sendbuf));
120 if(ret == SOCKET_ERROR)
121 {
122 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
123 cout << "发送失败,重发" << endl;
124 continue;
125 }
126
127 //延时,防止丢包
128 Sleep(50);
129 }
130 else // 发送窗口大于指定值,等持接收线程接收确认消息
131 {
132 ResetEvent(m_hEvent);
133 }
134 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
135 }
136 }
137
138 发送端的接收线程:
139 int ret;
140 RecvBuf recvbuf;
141
142 while(m_hFile != NULL)
143 {
144 ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));
145 if(ret == SOCKET_ERROR)
146 {
147 //AfxMessageBox("接收确认消息出错");
148 ::EnterCriticalSection(&m_csQueue);
149 if(m_bufqueue.front()->index == File_End) // 文件传输完毕
150 {
151 ::LeaveCriticalSection(&m_csQueue);
152 break;
153 }
154 ::LeaveCriticalSection(&m_csQueue);
155
156 cout << "接收确认消息出错: " << GetLastError() << endl;
157 return false;
158 }
159
160 if(recvbuf.flag == Flag_Ack && recvbuf.index == m_nSendIndexTail)
161 {
162 m_nSendIndexTail = (m_nSendIndexTail + 1) % Sliding_Window_Size;
163
164 //该结点已得到确认,将其加入旁视列表,以备再用
165 ::EnterCriticalSection(&m_csQueue);
166 m_bufLookaside.push(m_bufqueue.front());
167 m_bufqueue.pop();
168 ::LeaveCriticalSection(&m_csQueue);
169
170 SetEvent(m_hEvent);
171 }
172 }
173
174 接收端的接收线程:
175 int ret;
176 DWORD dwWritten;
177 SendBuf recvbuf;
178 RecvBuf sendbuf;
179 int nerror = 0;
180
181 // 设置文件指针位置,指向上次已发送的大小
182 SetFilePointer(m_hFile, 0, NULL, FILE_END);
183
184 //若已接收文件大小小于需要接收的大小,则继续
185 while(m_dwSend < m_dwFileSize)
186 {
187 //接收
188 memset(&recvbuf, 0, sizeof(recvbuf));
189 ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));
190 if(ret == SOCKET_ERROR)
191 {
192 return false;
193 }
194
195 //不是希望接收的,丢弃,继续接收
196 if(recvbuf.index != (m_nRecvIndex) % Sliding_Window_Size)
197 {
198 nerror++;
199 cout << recvbuf.index << "error?" << m_nRecvIndex << endl;
200 continue;
201 }
202
203 if(!WriteFile(m_hFile, recvbuf.buf, recvbuf.dwLen, &dwWritten, NULL))
204 {
205 //AfxMessageBox("写入文件失败");
206 cout << "写入文件失败" << endl;
207 return false;
208 }
209
210 //已接收文件大小
211 m_dwSend += dwWritten;
212
213 //发送确认消息
214 sendbuf.flag = Flag_Ack;
215 sendbuf.index = m_nRecvIndex;
216
217 ret = m_hsocket.hsendto((char*)&sendbuf, sizeof(sendbuf));
218 if(ret == SOCKET_ERROR)
219 {
220 return false;
221 }
222
223 //接收窗口前移一格
224 m_nRecvIndex = (m_nRecvIndex + 1) % Sliding_Window_Size;
225 }
226
1 UDP实现可靠文件传输
2 大家都清楚,如果用TCP传输文件的话,是很简单的,根本都不用操心会丢包,除非是网络坏了,就得重来。用UDP的话,因为UDP是不可靠的,所以用它传输文件,要保证不丢包,就得我们自己写额外的代码来保障了。本文就说说如果保证可靠传输。
3
4 要实现无差错的传输数据,我们可以采用重发请求(ARQ)协议,它又可分为连续ARQ协议、选择重发ARQ协议、滑动窗口协议。本文重点介绍滑动窗口协议,其它的两种有兴趣的可参考相关的网络通信之类的书。
5
6 采用滑动窗口协议,限制已发送出去但未被确认的数据帧的数目。循环重复使用已收到的那些数据帧的序号。具体实现是在发送端和接收端分别设定发送窗口和接收窗口。
7 (1)发送窗口
8 发送窗口用来对发送端进行流量控制。发送窗口的大小Wt代表在还没有收到对方确认的条件下,发送端最多可以发送的数据帧的个数。具体意思请参考下图:
9
10
11 (2)接收窗口
12 接收窗口用来控制接收数据帧。只有当接收到的数据帧的发送序号落在接收窗口内,才允许将该数据帧收下,否则一律丢弃。接收窗口的大小用Wr来表示,在连续ARQ协议中,Wr = 1。接收窗口的意义可参考下图:
13
14
15 在接收窗口和发送窗口间存在着这样的关系:接收窗口发生旋转后,发送窗口才可能向前旋转,接收窗口保持不动时,发送窗口是不会旋转的。这种收发窗口按如此规律顺时钟方向不断旋转的协议就犯法为滑动窗口协议。
16
17 好了,在上面对滑动窗口协议有大致了解后,我们还是进入正题吧:)
18
19 发送端的发送线程:
20 int ret;
21 int nPacketCount = 0;
22 DWORD dwRet;
23 SendBuf sendbuf;
24 DWORD dwRead;
25 DWORD dwReadSize;
26
27 SendBuf* pushbuf;
28
29 //计算一共要读的文件次数,若文件已读完,但客户端没有接收完,
30 //则要发送的内容不再从文件里读取,而从m_bufqueue里提取
31 nPacketCount = m_dwFileSize / sizeof(sendbuf.buf);
32
33 //若不能整除,则应加1
34 if(m_dwFileSize % sizeof(sendbuf.buf) != 0)
35 ++nPacketCount;
36
37 SetEvent(m_hEvent);
38
39 CHtime htime;
40
41 //若已发送大小小于文件大小并且发送窗口前沿等于后沿,则继续发送
42 //否则退出循环
43
44 if(m_dwSend < m_dwFileSize) // 文件没有传输完时才继续传输
45 {
46 while(1)
47 {
48 dwRet = WaitForSingleObject(m_hEvent, 1000);
49 if(dwRet == WAIT_FAILED)
50 {
51 return false;
52 }
53 else if(dwRet == WAIT_TIMEOUT)
54 {
55 //重发
56 ::EnterCriticalSection(&m_csQueue); // 进入m_bufqueue的排斥区
57 ret = m_hsocket.hsendto((char*)m_bufqueue.front(), sizeof(sendbuf));
58 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
59 if(ret == SOCKET_ERROR)
60 {
61 cout << "重发失败,继续重发" << endl;
62 continue;
63 }
64
65 ResetEvent(m_hEvent);
66 continue;
67 }
68
69 //若发送窗口大小 < 预定大小 && 已读文件次数(nReadIndex) < 需要读文件的次数(nReadCount),则继续读取发送
70 //否则,要发送的内容从m_bufqueue里提取
71 if(m_dwSend < m_dwFileSize)
72 {
73 dwReadSize = m_dwFileSize - m_dwSend;
74 dwReadSize = dwReadSize < MAXBUF_SIZE ? dwReadSize : MAXBUF_SIZE;
75
76 memset(sendbuf.buf, 0, sizeof(sendbuf.buf));
77 if(!ReadFile(m_hFile, sendbuf.buf, dwReadSize, &dwRead, NULL))
78 {
79 //AfxMessageBox("读取文件失败,请确认文件存在或有读取权限.");
80 cout << "读取文件失败,请确认文件存在或有读取权限." << endl;
81 return false;
82 }
83
84 m_dwSend += dwRead;
85
86 sendbuf.index = m_nSendIndexHead;
87 m_nSendIndexHead = (m_nSendIndexHead + 1) % Sliding_Window_Size; // 发送窗口前沿向前移一格
88
89 sendbuf.dwLen = dwRead;
90
91 //保存发送过的数据,以便重发
92 ::EnterCriticalSection(&m_csQueue); // 进入m_bufqueue的排斥区
93 pushbuf = GetBufFromLookaside();
94
95 memcpy(pushbuf, &sendbuf, sizeof(sendbuf));
96
97 m_bufqueue.push(pushbuf);
98 if(m_dwSend >= m_dwFileSize) // 文件已读完,在队列中加一File_End标志,以便判断是否需要继续发送
99 {
100 pushbuf = GetBufFromLookaside();
101
102 pushbuf->index = File_End;
103 pushbuf->dwLen = File_End;
104 memset(pushbuf->buf, 0, sizeof(pushbuf->buf));
105
106 m_bufqueue.push(pushbuf);
107 }
108 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
109 }
110
111 ::EnterCriticalSection(&m_csQueue); // 进入m_bufqueue的排斥区
112 if(m_bufqueue.front()->index == File_End) // 所有数据包已发送完毕,退出循环
113 {
114 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
115 break;
116 }
117 else if(m_bufqueue.size() <= Send_Window_Size) // 发送窗口小于指定值,继续发送
118 {
119 ret = m_hsocket.hsendto((char*)m_bufqueue.front(), sizeof(sendbuf));
120 if(ret == SOCKET_ERROR)
121 {
122 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
123 cout << "发送失败,重发" << endl;
124 continue;
125 }
126
127 //延时,防止丢包
128 Sleep(50);
129 }
130 else // 发送窗口大于指定值,等持接收线程接收确认消息
131 {
132 ResetEvent(m_hEvent);
133 }
134 ::LeaveCriticalSection(&m_csQueue); // 退出m_bufqueue的排斥区
135 }
136 }
137
138 发送端的接收线程:
139 int ret;
140 RecvBuf recvbuf;
141
142 while(m_hFile != NULL)
143 {
144 ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));
145 if(ret == SOCKET_ERROR)
146 {
147 //AfxMessageBox("接收确认消息出错");
148 ::EnterCriticalSection(&m_csQueue);
149 if(m_bufqueue.front()->index == File_End) // 文件传输完毕
150 {
151 ::LeaveCriticalSection(&m_csQueue);
152 break;
153 }
154 ::LeaveCriticalSection(&m_csQueue);
155
156 cout << "接收确认消息出错: " << GetLastError() << endl;
157 return false;
158 }
159
160 if(recvbuf.flag == Flag_Ack && recvbuf.index == m_nSendIndexTail)
161 {
162 m_nSendIndexTail = (m_nSendIndexTail + 1) % Sliding_Window_Size;
163
164 //该结点已得到确认,将其加入旁视列表,以备再用
165 ::EnterCriticalSection(&m_csQueue);
166 m_bufLookaside.push(m_bufqueue.front());
167 m_bufqueue.pop();
168 ::LeaveCriticalSection(&m_csQueue);
169
170 SetEvent(m_hEvent);
171 }
172 }
173
174 接收端的接收线程:
175 int ret;
176 DWORD dwWritten;
177 SendBuf recvbuf;
178 RecvBuf sendbuf;
179 int nerror = 0;
180
181 // 设置文件指针位置,指向上次已发送的大小
182 SetFilePointer(m_hFile, 0, NULL, FILE_END);
183
184 //若已接收文件大小小于需要接收的大小,则继续
185 while(m_dwSend < m_dwFileSize)
186 {
187 //接收
188 memset(&recvbuf, 0, sizeof(recvbuf));
189 ret = m_hsocket.hrecvfrom((char*)&recvbuf, sizeof(recvbuf));
190 if(ret == SOCKET_ERROR)
191 {
192 return false;
193 }
194
195 //不是希望接收的,丢弃,继续接收
196 if(recvbuf.index != (m_nRecvIndex) % Sliding_Window_Size)
197 {
198 nerror++;
199 cout << recvbuf.index << "error?" << m_nRecvIndex << endl;
200 continue;
201 }
202
203 if(!WriteFile(m_hFile, recvbuf.buf, recvbuf.dwLen, &dwWritten, NULL))
204 {
205 //AfxMessageBox("写入文件失败");
206 cout << "写入文件失败" << endl;
207 return false;
208 }
209
210 //已接收文件大小
211 m_dwSend += dwWritten;
212
213 //发送确认消息
214 sendbuf.flag = Flag_Ack;
215 sendbuf.index = m_nRecvIndex;
216
217 ret = m_hsocket.hsendto((char*)&sendbuf, sizeof(sendbuf));
218 if(ret == SOCKET_ERROR)
219 {
220 return false;
221 }
222
223 //接收窗口前移一格
224 m_nRecvIndex = (m_nRecvIndex + 1) % Sliding_Window_Size;
225 }
226