CSMA/CD算法C语言编程模拟
CSMA/CD工作过程介绍
- 准备发送:适配器从网络层获得一个分组,加上以太网的首部和尾部,组成以太网帧,组成以太网帧,放入适配器的缓存中。但在发送之前,必须先检测信道。
- 检测信道:若检测到信道忙,则应不停地检测,一直等待信道为空闲。若检测到信道空闲,并在96bit时间内信道保持空闲(保证了帧间最小间隔),就发送这个帧
- 在发送过程中仍不停地检测信道,即网络适配器要边发边监听。这里只有两种可能性:
i:发送成功:在争用期内一直未检测到碰撞。这个帧肯定能够发送成功。发送完毕后,其他什么也不做。然后回到步骤1.
ii:发送失败:在争用期内检测到碰撞。这时立即停止发送数据,并按规定发送人为干扰信号32bit或48bit(我在下面模拟过程中,使用的48bit)。适配器接着就执行指数退避算法,等待r倍512bit时间后,返回到步骤2.继续检测信道。但若重传次数达到一定次数仍不能成功(下面算法过程中,我设置为了10次),则停止重传而向上报错。
注:以太网每发完一帧,一定要把已发送的帧暂时保留一下。如果在争用期内检测出发生碰撞,那么还要在推迟一段时间后再把这个暂时保留的帧重传
CSMA/CD过程流程图
CSMA/CD算法C语言编程模拟代码
#include<stdio.h>
#include<string.h>
#include<pthread.h>
#include<windows.h>
#include<time.h>
#include<stdlib.h>
#include<math.h>
#define ABitSleepTime 100
#define RoadLength 100 //信道长度,100bit
#define ContentionPeriod 512 //争用期为512bit和最短帧长512bit
#define EnhanceConflictSignals 48 //增强冲突信号48bit
char road[RoadLength]; //定义信道
/* 模拟传输一个位需要的时间定义 */
void BitTime(int bit)
{
while(bit-- != 0)
{
Sleep(ABitSleepTime);
}
}
/* 模拟信道监听96bit */
void ChannelMonitor(int tranStart)
{
int idleBit = 96;
while(idleBit-- != 0)
{
printf("tranStart:%d在信道监听 idleBit:%d\n", tranStart, idleBit);
if(road[tranStart] != '-')
{
/* 使用字符'-'代表信道空闲,若信道不空闲,信道road的字符串会给修改其它值,继续监听96,直到信道一直空闲96bit为止 */
idleBit = 96;
}
BitTime(1);
}
}
/* 模拟指数回退 */
void IndexReg(int retranCount, int tranStart)
{
int r = 0;
srand(time(NULL) - tranStart * 100000);
/* 使用随机数获取回退的次数 */
r = rand()%4;
printf("tranStart:%d在指数回退 指数回退次数:%d\n", tranStart, r);
BitTime(r * ContentionPeriod); //
}
void* Client(void *arg)
{
/* 通信主机 *arg为1或者-1 data为传输最小帧长 + 封装需要的8字节 8 */
int *flag = (int *)arg, tranStart = -1, data = ContentionPeriod + 8 * 8, j = 0, retranCount = 0; //
/* 确定是哪一个主机,确定信道传输的方向 ,是从左往右传数据,还是从右往左传数据 */
if(*flag == 1)
{
tranStart = 0;
}
else
{
tranStart = RoadLength - 1;
}
while(1)
{
/* 监听到信道空闲持续96bit后,准备进行边监听边传输 */
ChannelMonitor(tranStart);
while((data--) != 0)
{
printf("A==> %s <==B\n", road);
/* 判断信道是否存在冲突,用字符'-'代表信道空闲,若信道不空闲,信道road的字符串会给修改其它值 */
if(road[tranStart + j] == '-')
{
/* 判断数据是否已经传送到另一端的主机端,若没有,则开始将数据进行传送 */
if(abs(j) == RoadLength - 1)
{
road[tranStart + j] = 'B' - *flag;
BitTime(1);
printf("A==> %s <==B\n", road);
if(data != 0)
{
/* 因为没有检测到冲突,则可以认为后面传输不会出现冲突 */
BitTime(data);
data = 0;
j = 0;
while(abs(j) != RoadLength - 1)
{
//信号逐渐传向另一边
road[tranStart + j] = '-' ;
j += (*flag);
BitTime(1);
}
printf("tranStart:%d传送成功,需要重传的次数%d\n", tranStart, retranCount);
memset(road, '-', sizeof(road)); //辅助信道一定清空
retranCount = 0;
}
}
else
{
road[tranStart + j] = 'B' - *flag;
/* 利用创建两个线程时分别传入的1和-1进行加减,便于计算出两边传输数据时的下标,当线程使用的是1,代表数据从左边开始传输,当线程使用参数-1,则代表数据从右边开始传输 */
j += (*flag);
}
}
else
{
//发生冲突,发送48bit的增强冲突,然后进入随机指数回退算法
printf("tranStart:%d发生冲突\n", tranStart);
retranCount += 1;
printf("tranStart:%d发送人为增强冲突信号%d\n", tranStart, EnhanceConflictSignals);
BitTime(EnhanceConflictSignals + RoadLength);
memset(road, '-', sizeof(road));//清空信道,进入指数回退算法
printf("tranStart:%d发送人为增强冲突信号完成\n", tranStart);
if(retranCount > 10)
{
printf("重传次数超过10次,不再继续重传\n");
retranCount = 0;
break;
}
else
{//进入指数回退算法
printf("tranStart:%d进入回退算法\n", tranStart);
IndexReg(retranCount, tranStart);
break;
}
}
BitTime(1);
}
data = ContentionPeriod + 8 * 8;
j = 0;
}
}
int main(void)
{
int tranStart1 = 1, tranStart2 = -1;
pthread_t thid1,thid2;
memset(road, '-', sizeof(road));
/* 创建线程1,使用tranStart1 == 1参数代表是线程2 */
printf("创建主机A\n")
if(pthread_create(&thid1, NULL, Client, &tranStart1))
{
printf("thread1 creation failed\n");
exit(1);
}
/* 创建线程2,使用tranStart2 == -1参数代表是线程2 */
printf("创建主机B\n");
if(pthread_create(&thid2, NULL, Client, &tranStart2))
{
printf("thread2 creation failed\n");
exit(1);
}
while(1)
{
BitTime(10);
}
return 0;
}
注:
对于部分说包含#include<pthread.h>无法编译的问题,这是因为原来由于pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,可以参考以下文章解决:
https://blog.csdn.net/m0_37251750/article/details/126406672
https://blog.csdn.net/freiheit_zz/article/details/118030587