计 算 机 网 络
计 算 机 网 络
实 验 报 告
实 验 报 告
学生姓名
学生姓名
学 号
专业班级
指导教师 王伟平
学 院 信息科学与工程学院
完成时间 2014年5月
实验一 分槽ALOHA协议仿真实验
一、实验目的
掌握VB、VC++、VS或JAVA等集成开发环境编写仿真程序的方法;
理解并掌握分槽ALOHA协议原理。
二、实验内容与实现原理
实验内容:编写仿真程序,对一定网络环境下MAC层的多路访问协议的分槽ALOHA协议进行实现。通过仿真,学习协议采取的介质访问管理,包括介质分配和冲突解决机制,并对协议的性能与理论结果进行比较分析。
实现原理:分槽Aloha的基本思想是把信道时间分成离散的时间槽,槽长为一个帧所需
的发送时间。每个站点只能在时槽开始时才允许发送。其他过程与纯ALOHA协议相同。分槽Aloha的信道效率比纯Aloha要高。分槽Aloha的易受冲突区比纯Aloha小了一半。它的
重发策略是等待一段随机的时间,然后重发;如再次冲突,则再等待一段随机的时间,直到
重发成功为止,但是发送的时间也是在每个时间槽的开始。
三、具体设计实现及结果
仿真思路
设置各站点初始产生包的时间点及产生包的时间间隔(均为随机值),得到所有站点成功发送10000个数据包的总时间以及这段时间内所有数据包的个数(包括各站点每次新产生的包以及由于冲突而重发的包),从而计算出每包时内尝试次数及其对应的吞吐量。针对不同的包产生间隔,得到不同的每包时内尝试次数及其对应的吞吐量,将其画成一条曲线。
具体步骤
初始化各站点产生包的时间点(可采用0到1的随机数),统一归并到时槽开始的时间点。
Mgtime =[ Ttime / log(1-X/Mnum) ]* log(rand(1,Mnum));
% 初始化各站点包产生的时间点,为[0,1]的随机数
mtime = (fix(mgtime/slot)+1) * slot;
% 各站点数据包发送时间点,归并到时槽的开始处
其中:Ttime为发送一个包所需的时间,Mnum为站点的总个数,可通过 改变不同的X值得到不同的包产生时间点。X的取值小于站点总个数
选出最早产生数据包的站点作为初始发送站点,若此时槽只有一个数据包,则发送成功;若有两个以上数据包,则冲突。记录此时槽内所有包的个数
idx = find(mtime==now_time);
% finding of the terminal which transmission start
if length(idx) > 0
State(idx) = TRANSMIT; %State为各站点在此时槽的状态
mtime(idx) = now_time + Mplen(idx) / Srate;
% 发送结束时间,Mplen为数据包的长度,Srate为发送的速率
mtime(idx) = round(mtime(idx)/slot) * slot;
Tplen = Tplen + sum(Mplen(idx));
%此时槽内所有包的总长度
end
?
idx = find(State==TRANSMIT | State==COLLISION);
if length(idx) > 1
State(idx) = COLLISION; % 当有两个以上数据包时,发生冲突
end
若成功,则发送成功的数据包数加1,程序结束点也是成功的个数为10000时。然后根据生成包的随机时间间隔,得出此站点下一次发送包的时间点。
idx = find(mtime==now_time & State==TRANSMIT);
% finding of the terminal which transmission succeeded
if length(idx) > 0
Spnum = Spnum + 1; %发送成功的数据包数加1
Splen = Splen + Mplen(idx);%总共发送成功数据包的长度(计算吞吐量)
State(idx) = STANDBY;%设置此站点发送状态为等待
mgtime(idx) = now_time + [ Ttime / log(1-X/Mnum) ] * log(1-rand);
% 算出此站点下次产生的时间点
mtime(idx) = (fix(mgtime(idx)/slot)+1) * slot;
% 归并到时槽的开始点
end
若发生冲突,根据随机后退时间,得出此站点下一次发送包的时间点。
idx = find(mtime==now_time & State==COLLISION);
% finding of the terminal which transmission failed
if length(idx) > 0
State(idx) = STANDBY;
mtime(idx) = now_time + [ Ttime / log(1-X/Mnum) ] * log(rand(1,length(idx)));
% 站点等待的时间,下次重发此数据包的时间点
mtime(idx) = (fix(mtime(idx)/slot)+1) * slot; % 归并到时槽的开始处
end
依次循环上述过程,直至10000个数据包成功发送。计算出每包时内传输次数及其吞吐量
traffic = Tplen / Srate / now_time; %每包时内传输次数
%总共传输的包(包括站点新产生的包及由于冲突重传的包)的总长度除以发 送速率再除以发送完最后一个包的时间
thoughput=Splen/Srate/now_time;%吞吐量
%成功传输的包的总长度除以发送速率再除以发送完最后一个包的时间
实验运行结果
四、编程语言和环境
编程语言:Matlab。
编程环境:Windows(MS Visual系列,VC/VB/VS.Net;)。
实验总结
本次实验对我来说难度较大,但我认真分析,查阅资料之后,总算做了出来。实验过程中,由于对代码语言Matlab的不熟悉,出现了一系列问题,但最后都圆满解决。本次实验不但让我对分槽Aloha有了更加深刻的了解,而且让我学会了新语言Matlab,锻炼了我的动手能力。
附:实验代码
for m=2:1:500 %m表示标签数
n=1000; %aloha算法,m表示m个标签,n表示重发次数
A=rand(m,n); %生成一个0-1分布的矩阵
A1=0.5*A; %生成一个0-0.5分布的矩阵,假设随机退避时间服从0-0.5分布
B=cumsum(A1,2); %矩阵B是对A1每列相加得到的,表示随机发送的时间
T=B(1,n); %T为标签均发送时间,即为观察时间
C=1:1:(m*n); %生成一个向量
for i=1:m %将矩阵B转化为向量,赋值到向量C
for j=1:n
C(1,(i-1)*n+j)=B(i,j);
end
end
D=sort(C); %将向量按从小到大的顺序排序,用于计算两数据包之间的时间差
E=diff(D); %向量的微分,求两数据包之间的时间差,用于判断是否产生碰撞
T0=0.001; %每个数据包的宽度
N=0; %初始化N(发送成功的数据包)
M=0; %初始化M,总共的数据包
for i=1:(m*n-1) %此循环用于计算M与N
if D(1,i)<=T %只要小于观察时间T就加1
M=M+1;
if i==1&E(1,1)>=T0 %对于时间轴上的第1个和第m*n个数据包只需判断一个时间差,其他需要判断两个
N=N+1;
elseif i==(m*n-1)&E(1,(m*n-1))>=T0
N=N+1;
elseif i~=1&i~=(m*n-1)&E(1,i)>=T0&E(1,i-1)>=T0
N=N+1;
end
else continue
end
end
G=T0/T*M; %仿真得到的平均交换的数据包量,由于这里假设所有数据包的宽度相同且都为T0, T为观察时间
S=T0/T*N; %仿真得到的吞吐量
Q=S/G; %发送成功率
F=m/500; %归一化标签数,便于观察随标签数的变化其他量的变化情况
plot(G,S,'r.',G,Q,'ko',G,F,'g*'); %绘出G与S,Q,F的图像
hold on; %保留在同一张图上
end
xlabel('平均交换的数据包量G'); %添加X轴的标题
title('aloha算法仿真'); %添加标题
legend('吞吐量S','发送成功率Q','归一化标签数F'); %添加注释
grid on; %添加网格
实验三Socket通信实验
一、实验目的和要求
掌握VB、VC++、VS或JAVA等集成开发环境编写网络程序的方法;
掌握客户/服务器(C/S)应用的工作方式;
学习网络中进程之间通信的原理和实现方法;
理解单播、组播和广播的原理并比较其不同之处;
要求本机既是客户端又是服务器端;
二、实验内容与实现原理
实验内容
具有点对点通信功能,任意客户端之间能够发送消息;
具有群组通信功能,客户端能够向组内成员同时发送消息,其他组成员不能收到;
具有广播功能,客户端能够向所有其他成员广播消息;
实现原理
Socket即为网络上的两个程序通过一个双向的通信连接实现数据的交换,这个双向链路的一段成为一个socket。Socket通常用来实现客户端和服务端的连接。它既能接受请求,也能发送请求。
网络编程是通过使用 套接字来达到 进程间通信目的的编程,Socket编程是网络编程的主流工具,Socket API是实现进程间通信的一种编程设施,也是一种为进程间提供底层抽象的机制,提供了访问下层通信协议的大量系统调用和相应的数据结构。
三、实验具体设计实现及结果
点对点通信功能
实现网络点对点通讯程序的关键步骤就是实现信息在网络中的发送和接收。数据接收使用的是Socket,数据发送使用的是NetworkStream。
1.1利用Socket来接收信息
TcpListener tlListen1 = new TcpListener ( 8889 ) ;
//侦听端口号
tlListen1.Start ( ) ;
Socket skSocket = tlListen1.AcceptSocket ( ) ;
//接受远程计算机的连接请求,并获得用以接收数据的Socket实例
EndPoint tempRemoteEP = skSocket.RemoteEndPoint? ;
//获得远程计算机对应的网络远程终结点
while (? true )
{
? Byte [] byStream = new Byte[80] ;
//定义从远程计算机接收到数据存放的数据缓冲区
? int i = skSocket.ReceiveFrom? ( byStream , ref tempRemoteEP? ) ;
? //接收数据,并存放到定义的缓冲区中
? string sMessage = System.Text.Encoding.UTF8.GetString ( byStream? ) ;
? //以指定的编码,从缓冲区中解析出内容
? MessageBox.Show ( sMessage ) ;
? //显示传送来的数据
? }
1.2利用NetworkStream来传送信息
TcpClient tcpc = new TcpClient (? "10.138.198.213"? , 8888? ) ;
? //对IP地址为“10.138.198.213”的计算机的8888端口提出连接申请
NetworkStream tcpStream = tcpc.GetStream ( ) ;
//如果连接申请建立,则获得用以传送数据的数据流
string sMsg = "您好,见到您很高兴" ;
StreamWriter reqStreamW = new StreamWriter ( tcpStream? ) ;
//以特定的编码往向数据流中写入数据 ,默认为UTF8编码
reqStreamW.Write ( sMsg? ) ;
//将字符串写入数据流中
reqStreamW.Flush ( ) ;
//清理当前编写器的所有缓冲区,并使所有缓冲数据写入基础流
群组通信功能
组播编程需要UDP,有两个类支持组播网络编程Socket和UdpClient.一台计算机要加入某一个组,然后接收发往这个组的信息。Socket类要调用SetSocketOption函数加入和离开某一个组。UdpClient类有直接的加入和离开某个组的成员函数可以调用。而向某个组发信息,则没有什么特殊的,只需把发送数据的目的地址设为组播地址就可以了。
发送端:
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint iep = new IPEndPoint(IPAddress.Parse("224.0.0.1"), 3000);
EndPoint ep = (EndPoint)iep;
byte[] b = Encoding.ASCII.GetBytes("just a test!");
s.SendTo(b, ep);
s.Close();
接收端:
? Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
? IPEndPoint iep = new IPEndPoint(IPAddress.Any, 3000);
? EndPoint ep=(EndPoint)iep;
? s.Bind(iep);
? s.SetSocketOption(SocketOptionLevel.IP,SocketOptionName.AddMembership,new MulticastOption(IPAddress.Parse("224.0.0.1")));
? byte[]b=new byte[1024];
? s.ReceiveFrom(b,ref ep);
? string test;
? test = System.Text.Encoding.ASCII.GetString(b);
Console.WriteLine(test);
? s.Close();
? Console.ReadKey();
广播功能
此功能和组播功能实现类似,只要在发送端获得子网中IP广播地址发送休息即可。
// 广播模式(自动获得子网中的IP广播地址)
broadcastIpEndPoint = new IPEndPoint(IPAddress.Broadcast, 3000);
实验流程图
服务器端
服务器端
客户端
socket
bind
listen
socket
connect
accept
receive
send
receive
close
send
close
阻塞自己等待客户连接
建立连接
请求数据
应答数据
实验结果
四、编程语言和环境
编程语言:C++。
编程环境:Windows(MS Visual系列,VC/VB/VS.Net;)。
五、实验总结
本次实验让我更加深入地了解了套接字,即Socket的用法以及建立一个连接的基本步骤。通过本实验我明白了无论一个socket通信的功能如何,它搭建的基本步骤都是一样的,即:1、创建socket;2、阻塞等待客户端连接;3、客户端连接之后获取输入输出流,按照协议对客户端进行读写;4、关闭socket。
然后客户端连接的基本步骤也是一样的,即:1、创建客户端,连上相应的服务器;2、连上之后获取输入输出流,按照协议进行读写操作;3、关闭客户端。
附:实验代码
服务器代码:
#include "InitSock.h"
#include <stdio.h>
#include <iostream>
using namespace std;
CInitSock initSock; // 初始化Winsock库
int main()
{
// 创建套节字
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//用来指定套接字使用的地址格式,通常使用AF_INET
//指定套接字的类型,若是SOCK_DGRAM,则用的是udp不可靠传输
//配合type参数使用,指定使用的协议类型(当指定套接字类型后,可以设置为0,因为默认为UDP或TCP)
if(sListen == INVALID_SOCKET)
{
printf("Failed socket() \n");
return 0;
}
// 填充sockaddr_in结构 ,是个结构体
/* struct sockaddr_in {
short sin_family; //地址族(指定地址格式) ,设为AF_INET
u_short sin_port; //端口号
struct in_addr sin_addr; //IP地址
char sin_zero[8]; //空子节,设为空
} */
sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(4567); //1024 ~ 49151:普通用户注册的端口号
sin.sin_addr.S_un.S_addr = INADDR_ANY;
// 绑定这个套节字到一个本地地址
if(::bind(sListen, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
{
printf("Failed bind() \n");
return 0;
}
// 进入监听模式
//2指的是,监听队列中允许保持的尚未处理的最大连接数
if(::listen(sListen, 2) == SOCKET_ERROR)
{
printf("Failed listen() \n");
return 0;
}
// 循环接受客户的连接请求
sockaddr_in remoteAddr;
int nAddrLen = sizeof(remoteAddr);
SOCKET sClient = 0;
char szText[] = " TCP Server Demo! \r\n";
while(sClient==0)
{
// 接受一个新连接
//((SOCKADDR*)&remoteAddr)一个指向sockaddr_in结构的指针,用于获取对方地址
sClient = ::accept(sListen, (SOCKADDR*)&remoteAddr, &nAddrLen);
if(sClient == INVALID_SOCKET)
{
printf("Failed accept()");
}
printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
continue ;
}
while(TRUE)
{
// 向客户端发送数据
gets(szText) ;
::send(sClient, szText, strlen(szText), 0);
// 从客户端接收数据
char buff[256] ;
int nRecv = ::recv(sClient, buff, 256, 0);
if(nRecv > 0)
{
buff[nRecv] = '\0';
printf(" 接收到数据:%s\n", buff);
}
}
// 关闭同客户端的连接
::closesocket(sClient);
// 关闭监听套节字
::closesocket(sListen);
return 0;
}
客户端代码:
#include "InitSock.h"
#include <stdio.h>
#include <iostream>
using namespace std;
CInitSock initSock; // 初始化Winsock库
int main()
{
// 创建套节字
SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(s == INVALID_SOCKET)
{
printf(" Failed socket() \n");
return 0;
}
// 也可以在这里调用bind函数绑定一个本地地址
// 否则系统将会自动安排
// 填写远程地址信息
sockaddr_in servAddr;
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(4567);
// 注意,这里要填写服务器程序(TCPServer程序)所在机器的IP地址
// 如果你的计算机没有联网,直接使用127.0.0.1即可
servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1)
{
printf(" Failed connect() \n");
return 0;
}
char buff[256];
char szText[256] ;
printf("连接到服务器,以下为聊天内容:\n");
while(TRUE)
{
//从服务器端接收数据
int nRecv = ::recv(s, buff, 256, 0);
if(nRecv > 0)
{
buff[nRecv] = '\0';
printf("接收到数据:%s\n", buff);
}
// 向服务器端发送数据
gets(szText) ;
szText[255] = '\0';
::send(s, szText, strlen(szText), 0) ;
}
// 关闭套节字
::closesocket(s);
return 0;
}