计算机网络滑动窗口协议
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
《计算机通信原理》课程项目
实施报告
题目(3):滑动窗口协议的模拟
组号:
任课教师:
组长:
成员: 20%
成员: 20%
成员: 20%
成员: 20%
联系方式:
目录
一、项目要求 (3)
二、项目成果 (3)
三、程序原理 (3)
1、滑动窗口协议 (3)
A、窗口机制 (3)
B、1比特滑动窗口协议 (5)
C、后退停等协议 (6)
D、选择重传协议 (7)
2、用户数据报协议UDP (7)
四、程序设计说明 (8)
五、主要数据结构说明及主要函数 (8)
六、收获建议 (17)
一、项目要求
本课题通过设计软件,实现对滑动窗口协议的模拟.
设计要求:
1. Windows 环境下运行,程序应在1-2台PC上运行;
2. 演示在两台计算机间传输文件。
允许在同一台机器中用两个独立线程来模拟;
3. 功能:
1)由一台PC(线程)向另一台PC(线程)发送数据包,界面应显示出双方帧个数变化,帧序号,发送和接受速度,暂停或重传提示等,界面中必须动态显示数据帧的发送情况和接受情况,包括在相应窗口详细显示相应的ACK和其他收发数据帧后发出的消息,以表明模拟协议的正确运作过程。
2)接收方及发送方应具有按序收发帧的能力;
3)接受方应有固定大小的滑动窗口,并对收到信息缓存。
当发送方速度过快或帧丢失(超时),接受方应发送消息,要求暂停或重传;
4)发送方发送速度应可以调节,并可以暂停或重发;
5)发送方重传时可仅重传帧(丢失帧);
4. 以上几个功能应可视,要求有简单界面;
二、项目成果
三、程序原理
1、滑动窗口协议
A、窗口机制
滑动窗口协议的基本原理就是在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口;同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。
发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。
不同的滑动窗口协议窗口大小一般不同。
发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧
以发送窗口尺寸为2,接收窗口尺寸为1
分析:
①初始态,发送方没有帧发出,发送窗口前后沿相重合。
接收方0号窗口打开,等待接收0号帧;
②发送方打开0号窗口,表示已发出0帧但尚确认返回信息。
此时接收窗口状态不变;
③发送方打开0、1号窗口,表示0、1号帧均在等待确认之列。
至此,发送方打开的窗口数已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧。
接收窗口此时状态仍未变;
④接收方已收到0号帧,0号窗口关闭,1号窗口打开,表示准备接收1号帧。
此时发送窗口状态不变;
⑤发送方收到接收方发来的0号帧确认返回信息,关闭0号窗口,表示从重发表中删除0号帧。
此时接收窗口状态仍不变;
⑥发送方继续发送2号帧,2号窗口打开,表示2号帧也纳入待确认之列。
至此,发送方打开的窗口又已达规定限度,在未收到新的确认返回帧之前,发送方将暂停发送新的数据帧,此时接收窗口状态仍不变;
⑦接收方已收到1号帧,1号窗口关闭,2号窗口打开,表示准备接收2号帧。
此时发送窗口状态不变;
⑧发送方收到接收方发来的1号帧收毕的确认信息,关闭1号窗口,表示从重发表中删除1号帧。
此时接收窗口状态仍不变。
若从滑动窗口的观点来统一看待1比特滑动窗口、后退n及选择重传三种协议,它们的差别仅在于各自窗口尺寸的大小不同而已。
1比特滑动窗口协议:发送窗口=1,接收窗口=1;后退n协议:发窗口>1,接收窗口>1;选择重传协议:发送窗口>1,接收窗口>1。
B、1比特滑动窗口协议
当发送窗口和接收窗口的大小固定为1时,滑动窗口协议退化为停等协议(stop-and-wait)。
该协议规定发送方每发送一帧后就要停下来,等待接收方已正确接收的确认(acknowledgement)返回后才能继续发送下一帧。
由于接收方需要判断接收到的帧是新发的帧还是重新发送的帧,因此发送方要为每一个帧加一个序号。
由于停等协议规定只有一帧完全发送成功后才能发送新的帧,因而只用一比特来编号就够了
C、后退停等协议
由于停等协议要为每一个帧进行确认后才继续发送下一帧,大大降低了信道利用率,因此又提出了后退n协议。
后退n协议中,发送方在发完一个数据帧后,不停下来等待应答帧,而是连续发送若干个数据帧,即使在连续发送过程中收到了接收方发来的应答帧,也可以继续发送。
且发送方在每发送完一个数据帧时都要设臵超时定时器。
只要在所设臵的超时时间内仍收到确认帧,就要重发相应的数据帧。
如:当发送方发送了N个帧后,若发现该N帧的前一个帧在计时器超时后仍未返回其确认信息,则该帧被判为出错或丢失,此时发送方就不得不重新发送出错帧及其后的N帧。
D、选择重传协议
在后退n协议中,接收方若发现错误帧就不再接收后续的帧,即使是正确到达的帧,这显然是一种浪费。
另一种效率更高的策略是当接收方发现某帧出错后,其后继续送来的正确的帧虽然不能立即递交给接收方的高层,但接收方仍可收下来,存放在一个缓冲区中,同时要求发送方重新传送出错的那一帧。
一旦收到重新传来的帧后,就可以原已存于缓冲区中的其余帧一并按正确的顺序递交高层。
这种方法称为选择重发(SELECTICE REPEAT)。
2、用户数据报协议UDP
用户数据报协议(User Datagram Protocol, UDP)是一个简单的面向数据报的传输层协议,正式规范为RFC 768。
UDP协议的主要作用是将网络数据流量压缩成数据报的形式。
一个典型的数据报就是一个二进制数据的传输单位。
每一个数据报的前8个字节用来包含报头信息,剩余字节则用来包含具体的传输数据。
主要特点:1、UDP是无连接的。
2、UDP使用最大努力交付。
3、UDP 是面向报文的。
4、UDP没有阻塞控制。
5、UDP支持一对一、一对多、多对一、多对多的交互通信。
6、UDP的首部开销小。
UDP报头由4个域组成,其中每个域各占用2个字节,具体如下:
源端口号、目标端口号、数据报长度、校验值。
UDP协议使用报头中的校验值来保证数据的安全。
校验值首先在数据发送方通过特殊的算法计算得出,在传递到接收方之后,还需要再重新计算。
如果某个数据报在传输过程中被第三方篡改或者由于线路噪音等原因受到损坏,发送和接收方的校验计算值将不会相符,由此UDP协议可以检测是否出错。
这与TCP协议是不同的,后者要求必须具有校验值。
四、程序设计说明
程序可分为两个部分:发送端和接收端。
发送端和接收端的主要程序流程大致相同如下:
1、打开程序,程序初始化(初始化套接字,创建套接字,绑定端口,创建
udp接收线程)
2、初始化完成后,发送端开始发送数据同时等待接收端发送等待帧号NAK,
接收端接收数据,并发送所要等待的帧号NAK,如果帧号正确则接收并提取,如果帧号不正确但是数据正确则放臵缓存区。
3、暂停或退出。
五、主要数据结构说明及主要函数
主要数据结构:
发送端:接收端:
Udp报文传输为不可靠传输,滑窗协议达到可靠传输,需要给udp报文的头部加上序号和确认帧,协议格式:
typedef UINT seqNum;
typedef enum { DATA, ACK, NAK } frameKind; //报文类型
typedef enum { NO_ERR, CKSUM_ERR, LOST_ERR } errMode; //错误模式
typedef struct { UCHAR data[MAX_PKT]; } packet; //报文内容
//
typedef struct
{
frameKind kind;
seqNum seq;
seqNum ack;
int size; //数据长度,不包括帧头
errMode err; //该字段用于模拟信道出错
} framframe_hdr;
typedef struct
{
frame_hdr hdr;
packet info;
} frame;
函数说明:
1、程序初始化(初始化套接字,创建套接字,绑定端口,创建udp接收线程)
BOOL CSenderDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
//初始化WinSock
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
AfxMessageBox("Failed to initialize the winsock 2 stack");
return FALSE;
}
//创建UDP Sender Socket
if ((m_UDPSndrSocket = WSASocket(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET)
{
AfxMessageBox("Failed to create UDPSocket");
return FALSE;
}
//填充本地UDP Sender Socket地址结构
SOCKADDR_IN UDPSndrAddr;
memset(&UDPSndrAddr, 0, sizeof(SOCKADDR_IN));
UDPSndrAddr.sin_family = AF_INET;
UDPSndrAddr.sin_port = htons(3073);
UDPSndrAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//绑定Sender UDP端口
if (bind(m_UDPSndrSocket, (sockaddr*)&UDPSndrAddr, sizeof(UDPSndrAddr)) == SOCKET_ERROR )
{
AfxMessageBox("Failed to bind UDPSndrAddr");
return FALSE;
}
//填充Receiver UDP地址
memset(&m_UDPRcvrAddr, 0, sizeof(SOCKADDR_IN));
m_UDPRcvrAddr.sin_family = AF_INET;
m_UDPRcvrAddr.sin_port = htons(3074);
m_UDPRcvrAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
//创建UDP数据包接收线程
DWORD dwThreadId;
CreateThread(NULL, 0, UdpReceiveThread, this, 0, &dwThreadId);
return TRUE; // return TRUE unless you set the focus to a control
}
2、按下开始发送键,的一些列窗口初始化以及错误帧设定,并且开启定时器,设定窗口界面控制:
void CSenderDlg::OnStartSend()
{
// TODO: Add your control notification handler code here
//获取对话框数据
if (!UpdateData(TRUE))
return;
//初始化所有参数
if (m_pOutBuf != NULL)
delete []m_pOutBuf;
m_pOutBuf = new frame[m_SendWndSize];
m_iBuffered = 0; //当前滑动窗口大小
m_ackExpected = 0; //发送窗口左侧
m_nextFrameToSend = 0; //发送窗口右侧+1
for (int i=0; i<MAX_SEQ+1; i++) //错误模式初始均为NO_ERR
m_errArray[i] = NO_ERR;
//设置帧的错误模式,模拟传输错误。
char tmp[256];
char *token;
char seps[] = " ";
int iSeq;
if (!m_strFrameLost.IsEmpty())
{
strcpy_s(tmp, m_strFrameLost);
token = strtok(tmp, seps);
while (token != NULL)
{
iSeq = atoi(token);
token = strtok(NULL, seps);
if (iSeq < 0 || iSeq > MAX_SEQ)
{
AfxMessageBox("Invalid seqno in LostFrame box, ignore it");
continue;
}
m_errArray[iSeq] = LOST_ERR;
}
}
if (!m_strChksumErr.IsEmpty())
{
strcpy_s(tmp, m_strChksumErr);
token = strtok(tmp, seps);
while (token != NULL)
{
iSeq = atoi(token);
token = strtok(NULL, seps);
if (iSeq < 0 || iSeq > MAX_SEQ)
{
AfxMessageBox("Invalid seqno in ChksumErr box, ignore it");
continue;
}
m_errArray[iSeq] = CKSUM_ERR;
}
}
//启动网络层数据发送定时器
SetTimer(ID_SEND_TIMER, m_SendInterval, NULL);
//窗口界面控制
GetDlgItem(IDC_SEND_WND_SIZE)->EnableWindow(FALSE);
GetDlgItem(IDC_SEND_INTERVAL)->EnableWindow(FALSE);
GetDlgItem(IDC_RESEND_TIMER)->EnableWindow(FALSE);
GetDlgItem(IDC_MANUAL_ERR)->EnableWindow(FALSE);
GetDlgItem(IDC_CHKSUM_ERR)->EnableWindow(FALSE);
GetDlgItem(IDC_FRAME_LOST)->EnableWindow(FALSE);
GetDlgItem(IDC_START_SEND)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP_SEND)->EnableWindow();
CString strMsg;
strMsg.Format("%d", m_ackExpected);
GetDlgItem(IDC_BOTTOM)->SetWindowText(strMsg);
strMsg.Format("%d", m_nextFrameToSend);
GetDlgItem(IDC_TOP)->SetWindowText(strMsg);
GetDlgItem(IDC_CUR_FRAME)->SetWindowText("0");
}
3、计时器所要执行的程序:未超时的时候发送数据帧程序和超时之后重新发送数据帧程序void CSenderDlg::OnTimer(UINT nIDEvent)
{
switch(nIDEvent)
{
case ID_SEND_TIMER:
if (m_iBuffered < m_SendWndSize)
PostMessage(NETWORK_LAYTER_READY);
break;
default: //case timeout
int framePos = nIDEvent - ID_TIMER_USER;
ASSERT(framePos>=0 && framePos<m_SendWndSize);
ReSendFrame(framePos);
break;
}
CDialog::OnTimer(nIDEvent);
}
4、重新发送数据帧程序,重新发送的数据帧不带有错误模式
void CSenderDlg::ReSendFrame(int framePos)
{
frame* pFrame = &m_pOutBuf[framePos];
pFrame->hdr.err = NO_ERR; //重发时去掉出错模拟
ToPhysicalLayer((char*)pFrame, sizeof(pFrame->hdr));
SetTimer(framePos + ID_TIMER_USER, m_ResendTime, NULL); //重发定时器
//窗口显示
char buf[32];
_strtime_s(buf);
CString strMsg;
strMsg.Format("%s %5s %8s %7s %3d", buf, "data", "re-sent", "normal", pFrame->hdr.seq);
m_ListOutput.AddString(strMsg);
int iCount = m_ListOutput.GetCount();
if (iCount > 0)
m_ListOutput.SetCurSel(iCount-1);
}
5、UDP接收线程
DWORD CSenderDlg::UdpReceiveThread(LPVOID lpParam)
{
CSenderDlg* pDlg = (CSenderDlg*)lpParam;
pDlg->UdpReceive();
return 0;
}
6、接收udp函数,接收upd应答包,并检测是确认帧ack还是申请发送帧nak,并发出相应的指令,如果是ack正常发送,如果是nak则重发。
void CSenderDlg::UdpReceive()
{
char buf[sizeof(frame)];
int BytesReceived;
while (TRUE)
{
//接收UDP应答包
if ((BytesReceived=recvfrom(m_UDPSndrSocket, buf, sizeof(buf), 0, NULL, NULL)) == SOCKET_ERROR)
{
TRACE("Failed to recvfrom UDPSndrSocket, ErrCode: %d\n", WSAGetLastError()); //10054错误是正常的
//AfxMessageBox("Failed to recvfrom UDPSndrSocket");
continue;
}
ASSERT(BytesReceived>=sizeof(frame_hdr) && BytesReceived<=sizeof(frame));
if (BytesReceived>=sizeof(frame_hdr) && BytesReceived<=sizeof(frame))
{
frame* pFrame = (frame*)buf;
TRACE("Get %s%d, errMode=%d\n", (pFrame->hdr.kind==ACK)?"Ack":"Nak", pFrame->hdr.ack, pFrame->hdr.err);
if (pFrame->hdr.err == NO_ERR)
{
frame* p = new frame;
memcpy(p, buf, BytesReceived);
PostMessage(FRAME_ARRIVAL, (WPARAM)p);
}
}
}
}
7、从网络层准备工作,从网络层取得数据
LRESULT CSenderDlg::OnNetworkLayerReady(WPARAM wParam, LPARAM lParam)
{
m_iBuffered++; //扩展滑动窗口大小
FromNetworkLayer(&m_pOutBuf[m_nextFrameToSend % m_SendWndSize].info); //从网络层取得数据
SendFrame(DATA, m_nextFrameToSend); //发送数据帧
Inc(m_nextFrameToSend); //扩展滑动窗口右沿
//窗口显示
CString strMsg;
strMsg.Format("%d", m_ackExpected);
GetDlgItem(IDC_BOTTOM)->SetWindowText(strMsg);
strMsg.Format("%d", m_nextFrameToSend);
GetDlgItem(IDC_TOP)->SetWindowText(strMsg);
return 0;
}
8、发送帧,并在窗口显示发送情况
void CSenderDlg::SendFrame(frameKind fk, seqNum seq)
{
frame* pFrame = &m_pOutBuf[seq % m_SendWndSize];
pFrame->hdr.kind = fk;
pFrame->hdr.seq = seq;
pFrame->hdr.err = m_errArray[seq];
ToPhysicalLayer((char*)pFrame, sizeof(pFrame->hdr));
SetTimer(seq % m_SendWndSize + ID_TIMER_USER, m_ResendTime, NULL); //重发定时器
//窗口显示格式
char buf[32];
_strtime_s(buf);
CString strErrmode;
if (pFrame->hdr.err == NO_ERR)
strErrmode = "normal";
else if (pFrame->hdr.err == CKSUM_ERR)
strErrmode = "chkerr";
else
strErrmode = "lost";
CString strMsg;
strMsg.Format("%s %5s %8s %7s %3d", buf, "data", "sent", strErrmode, pFrame->hdr.seq);
m_ListOutput.AddString(strMsg);
int iCount = m_ListOutput.GetCount();
if (iCount > 0)
m_ListOutput.SetCurSel(iCount-1);
}
9、从网络层将udp帧发送到物理层并显示发送。
void CSenderDlg::ToPhysicalLayer(char* pBuf, int iSize)
{
sendto(m_UDPSndrSocket, pBuf, iSize, 0, (sockaddr*)&m_UDPRcvrAddr, sizeof(m_UDPRcvrAddr));
//窗口显示
CString strMsg;
strMsg.Format("%d", ((frame_hdr*)pBuf)->seq);
GetDlgItem(IDC_CUR_FRAME)->SetWindowText(strMsg);
}
10、udp帧到达物理层,显示和分析接收到的的udp报文是确认帧还是申请发送帧,并将其显示在界面上,并调整滑动窗口的位置。
LRESULT CSenderDlg::OnFrameArrival(WPARAM wParam, LPARAM lParam)
{
frame ackFrame;
FromPhysicalLayer((char*)&ackFrame, (char*)wParam);
//窗口显示
TRACE("arrived %s%d\n", (ackFrame.hdr.kind==ACK)?"Ack":"Nak", ackFrame.hdr.ack);
char buf[32];
_strtime_s(buf);
CString strMsg;
strMsg.Format("%s %5s %8s %7s %3d", buf, (ackFrame.hdr.kind==ACK)?"ack":"nak", "arrived", "normal", ackFrame.hdr.ack);
m_ListOutput.AddString(strMsg);
int iCount = m_ListOutput.GetCount();
if (iCount > 0)
m_ListOutput.SetCurSel(iCount-1);
if (ackFrame.hdr.kind==NAK && Between(m_ackExpected, ackFrame.hdr.ack, m_nextFrameToSend))
{
ReSendFrame(ackFrame.hdr.ack % m_SendWndSize);
}
seqNum ackedSeq = (ackFrame.hdr.kind==ACK)? ackFrame.hdr.ack : (ackFrame.hdr.ack+MAX_SEQ)%(MAX_SEQ+1);
while (Between(m_ackExpected, ackedSeq, m_nextFrameToSend))
{
m_iBuffered--;
KillTimer(m_ackExpected % m_SendWndSize + ID_TIMER_USER);
Inc(m_ackExpected); //扩展滑动窗口左沿
//窗口显示
CString strMsg;
strMsg.Format("%d", m_ackExpected);
GetDlgItem(IDC_BOTTOM)->SetWindowText(strMsg);
strMsg.Format("%d", m_nextFrameToSend);
GetDlgItem(IDC_TOP)->SetWindowText(strMsg);
}
return 0;
}
10、停止发送:
void CSenderDlg::OnStopSend()
{
// TODO: Add your control notification handler code here
//停止所有定时器
for (int i=1; i<m_SendWndSize+ID_TIMER_USER; i++)
KillTimer(i);
//窗口界面控制
GetDlgItem(IDC_SEND_WND_SIZE)->EnableWindow();
GetDlgItem(IDC_SEND_INTERVAL)->EnableWindow();
GetDlgItem(IDC_RESEND_TIMER)->EnableWindow();
//GetDlgItem(IDC_RANDOM_ERR)->EnableWindow();
GetDlgItem(IDC_MANUAL_ERR)->EnableWindow();
GetDlgItem(IDC_CHKSUM_ERR)->EnableWindow();
GetDlgItem(IDC_FRAME_LOST)->EnableWindow();
GetDlgItem(IDC_START_SEND)->EnableWindow();
GetDlgItem(IDC_STOP_SEND)->EnableWindow(FALSE);
}
六、收获建议
滑动窗口算法为计算机网络中的经典算法,是实现可靠通信较为高效可靠地算法。
本次项目通过MFC来实现滑动窗口协议的模拟,通过socket套接字来实现网络连接传输,通过算法来实现滑动窗口协议。
通过做项目熟悉了mfc编程,和windows编程,对网络套接字socket有了初步的了解,对滑动窗口协议也有的深入的认识。
由于是基于udp,在写滑动窗口的时候也会和tcp滑动窗口进行对比,在对比的过程中也加深了对tcp和udp运输层的认识。
在socket套接字的时候也对ip和端口有了认识,进一步认识和了解了网络层的原理和结构。
此次项目设计面较广,通过项目也熟练了面向对象语言VC++,可谓收获颇丰,不管是理论知识的进一步理解还是实践的锻炼都收获巨大。