2、要求
(1)在一台计算机上模拟总线型以太网数据帧发送过程,总线上连接的计算机个数为2个,支持CSMA\CD协议(二进制指数退避算法)
(2)用两个线程a和b模拟以太网上的两台主机。用一个双字类型变量Bus模拟总线。
(3)两个子线程向总线发送自己的数据。数据用该线程的线程号进行模拟,发送数据用线程号的Bus的“或”进行模拟。每台主机须向总线上成功发送5次数据,如果其中某次数据发送失败,则该线程结束。
以太网的核心技术是随机争用型介质访问方法,即带有冲突检测的载波侦听多路访问(CSMA/CD)方法。
1.以太网的帧的发送流程
(1)载波侦听过程。以太网中每个节点利用总线发送数据,总线是每个结点共享的公共传输介质。所以结点在发送一个帧前,必须侦听总线是否空闲,由于以太网的数据采用曼彻斯特编码方式,所以可以通过判断总线电平是否跳变来确定总线是否空闲。若总线空闲,就可启动发送,否则继续侦听。
(2)冲突检测。在数据发送过程中,可能会产生冲突(冲突是指总线上同时出现两个或两个以上的发送信号,他们叠加后的信号波形与任何发送结点输出的信号波形不相同。因为可能有多个主机都在侦听总线,当它们侦听到总线空闲时,都会往总线上发送数据)。所以在发送数据的过程中,也应该进行冲突检测,只要发现冲突就应该停止发送数据。
(2)随机延迟后重发。在检测到冲突、停止发送后,结点进行随机延迟重发。若重发16次后还没成功,则宣告发送失败,取消该帧的发送。随机延迟的计算方法一般采用截至二进制指数后退算法。该算法可表示为:t=2k*R*a ,其中t为结点重新发送需要的后退延迟时间,a为冲突窗口值(冲突窗口为总线最大长度和电磁波在介质中的传播速度比值的2倍),R为随机数,k的取值为k=min(n,10),n为该帧已发送的次数。
-
实现原理
原理:
发送方:
某站点需要发送数据帧,首先侦听信道:
- 如果信道空闲,站点立即发送数据帧;
- 在发送数据帧过程中,边发送边冲突检测;
- 如果信道忙,继续侦听直到信道变为空闲,再发送数据帧
如果再发送过程中检测到冲突,则:
- 立即停止发送该数据帧;
- 给总线上发送一串阻塞加强信号,告诉其他站点总线发生冲突;
- 等待一段随机时间(利用二进制指数退避算法),再重新争用总线,重复上面步骤,并重发该数据帧。
接收方:
- 检查是否发生冲突,若发生冲突,则丢弃该帧;若没有冲突,进入下一步。
- 检查该帧的目的地址是否可以接收该帧,若可以接收,则进入下一步。
- 检查CRC校验和LLC数据长度。若都正确,接收该帧,否则丢弃。
5、程序代码
#include <iostream>
#include <thread>
#include <stdlib.h>
#include <windows.h>
#include <math.h>
#include <mutex>
#define T 5 //在这里,以毫秒为单位,故延迟窗口为5ms
using namespace std;
int BUS;
int randnum[128];//随机数库
int randnumi;//随机数库访问变量
mutex myout;
class Host
{
public:
char id; //主机ID
int centcount; //发送次数
int collisionCounter; //发送失败最对次数
int successcount; //成功计数器
int data;
Host(char idIN, int a, int b, int sc)
{
id = idIN;
centcount = a;
collisionCounter = b;
successcount = sc;
data = randnum[randnumi++];
myout.lock();
cout << "主机" << id << "已就绪." << endl;
myout.unlock();
}
int csmacd(int a,int randn)
{
int k = min(a, 10);
int r = randn % k;
return (pow(2, r) - 1) * T;
}
void send()
{
int cCounter = 0; //失败次数计数器
while (true)
{
if (successcount >= centcount)
break;
if (BUS == 0)
{
Sleep((int)(T*(randnum[randnumi++]%5) )); //模拟占用总线的时间.
BUS = BUS | data;
Sleep((int)(T*(randnum[randnumi++]%5) ));
if (BUS == data)
{
successcount++;
myout.lock();
cout << id << " send " << data << " success,总 " << successcount << " 次成功. ///" << endl;
myout.unlock();
cCounter = 0;
data=randnum[randnumi++];
BUS = 0;
}
else
{
cCounter++;
myout.lock();
cout << id << " send " << data << " collision,第" <<cCounter<<" 次失败."<< endl;
myout.unlock();
BUS = 0;
Sleep(csmacd(cCounter,randnum[randnumi++]));
if (cCounter >= collisionCounter)
{
myout.lock();
cout << id << " send" << data << " failure" << endl;
myout.unlock();
data=randnum[randnumi];
cCounter = 0;
BUS = 0;
}
}
}
else
{
Sleep(randnum[randnumi++] % 10);
}
}
}
};
void a()
{
Host host('A', 5, 16, 0); //第二个参数为修改的需要成功的次数
host.send();
}
void b()
{
Host host('B', 5, 16, 0);
host.send();
}
int main()
{ srand(time(0));
for(int i=0;i<128;i++){
randnum[i]=rand();
}
randnumi=0;
thread A(a);
thread B(b);
A.join();
B.join();
return 0;
}
6、运行结果与分析
经过多次测试, 可以观察到,数据的冲突具有随机性, 并且由于在一开始两个发送方都征用信道,造成大概率的信道堵塞, 多次发送失败, 当发送失败次数多了后, 就有一方有较大的随即延迟, 此时另一方即可迅速发完. 最后当两个节点发送数据基本不再冲突.
我的报告中对发送方式的修改: 修改为在一个线程中连续发送五次成功后才退出线程. (基本原理与创建线程发送无异).