获取以太网卡的MAC地址
【实验目的】
1、通过
获取以太网卡物理地址的程序,初步掌握在应用层访问底层软硬件接口和属性的
。
2、设计一个程序,使得能够获取本机安装的以太网卡的物理地址。
3、设计一个简单的应用程序,使得该程序只能在装有指定地址网卡的微机上运行。 【实验性质】
综合与设计性实验
【实验条件】
装有以胎网卡并配置网络协议的微机
【实验导读】
1、MAC地址 MAC地址是每一个连接到LAN的端口或设备所需要的
化的数据链路层地址。MAC
地址字长6B(注意也有2B的),由IEEE控制。在数据链路层,数据帧传输的寻址是依照网卡地
址进行的。网卡地址可以采用局部地址或全局地址,以太网使用6B即48位的全局地址。对于共
享型以太网,传输通过广播实现,各个网卡按照自己的物理地址接受属于自己的数据帧。而在交
换式以太网,交换机通过逆向学习方式建立动态的MAC地址--端口映射
,根据该表进行数据帧
的转发。当映射表中没有相应表项时再广播发送到各个端口。这种传输机制当然要求网卡MAC地
址的唯一性。对一些应用程序来说,获取MAC地址有时是必要的。
【实验内容】
1、Linux编程要点
在Linux下编写获取本机网卡地址的程序,比较简单的方法是利用套接口(socket)和IO接口
(ioctl)函数来获取网卡信息,需要引用如下文件:
#include
#include
#include
#include
#include
socket函数的原型是:
int socket(int domain,int type, int protocol);
本函数有以下3个输入参数:
domain参数:表示所使用的协议族;
type参数:表示套接口的类型;
protocol参数:表示所使用的协议族中某个特定的协议。
如果函数调用成功,套接口的描述符(非负整数)就作为函数的返回值,假如返回值为-1,就表明有错误发生。
利用socket函数来获取网卡MAC信息时,domain参数取值AF_INET,表示采用internet协议族;type参数指定为SOCK_DGRAM,表示采用数据报类型套接口,protocol参数在这种组合下只有唯一选择,故用0填充。
I/O控制函数ioctl用于对文件进行底层控制,这里的文件包含网卡、终端、磁带机、套接口等软硬件设施,实际的操作来自各个设备自己提供的ioctl接口。ioctl函数的原型如下: int ioctl(int d,int request,„)
这里,参数d取值套接口的描述符,第一个request参数指定通过socket传输的I/O类型。本实验可以取值SIONGIFHWADDR(0x8927),表示取硬件地址。其他取值及其含义详见/usr/includr/linux/sockios.h。其后的request参数用于为实现I/O控制所必须传入或传出的参数。本实验需要用ifr结构传入网卡设备名,并传出6B的MAC地址。关键的程序段如下: #include
#include
char *device=”eth0”; //teh0是网卡设备名
unsigned char macaddr[ETH_ALEN]; //ETH_ALEN(6)是MAC地址长度
int s=socket(AF_INET,SOCK_DGRAM,0); //建立套接口
struct ifreq req;
int err;
strcpy(req.ifr_name,device); //将设备名作为输入参数传入
s,SIOCGIFHWADDR,&req); //执行取MAC地址操作 err=ioctl(
close(s);
if(err!= -1)
addr,req.ifr_hwaddr.sa_data,ETH_ALEN); //取输出的MAC地址 { memcpy(
for(i=0;i方案。例如,InterNetwork表示需要一个IP版本4的地址,InterNetworkV6表示IP版本6的地址。
SocketType参数指定Socket的类型。例如,Raw支持对基础传输协议的访问,Stream支持可靠、
双向、基于连接的数据流。
ProtocolType指定Socket类支持的协议。例如,IP表示网际协议,TCP表示传输控制协议。 注意:3个参数不是独立的,有些地址族会限制可与其一起使用的协议,并且套接字类型在协议中通常是隐式的。如果地址族、套接字类型和协议类型不匹配将导致无效的Socket。例如,构造一个新的Socket 对象,采用IP版本4的地址,支持可靠、双向、基于连接的数据流,采用TCP协议:
sock=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp)
3)、Socket的配置和连接
为了将Socket和主机关联,必须将主机表示成网络端点的形式。在C#中,采用IPEndPoint类表示网络端点,IPEndPoint函数原型如下:
? public IPEndPoint(IPAddress address,int port)
参数:address表示IP地址,port表示提供服务的端口号。
在服务器端将构造socket对象与表示服务器的网络端点绑定,然后开始进行监听,在收到连接请求后建立连接。主要用语以下3个函数:Bind、Listen和Accept。函数原型如下:? public void Bind(EndPoint localEP) 参数localEP为与socket关联的网络端点。
? public void Listen(int backlog) 参数backlog为挂连接队列的最大长度 ? public Socket Accept() 返回值为socket,用于处理接收的连接请求。 例:构造一个服务器的网络端点,对socket进行绑定,开始监听,接受连接请求。 IPAddress ServerIP=IPAdress.Parse(” 192.18.16.186”); //设定服务器IP地址 IPEndPoint Server=new IPEndPoint(ServerIP,8866); //生成服务器网络端点 Sock=new Socket(AddressFamily.InterNetwork,SocketType.Stream,
ProtocolType.Tcp) // 构造一个socket
Sock.Bind(Server); //将socket和服务器绑定
Sock.Listen(8); //开始监听,允许连接队列的长度为8
Socket connectsock=sock.Accept(); //返回socket,用于同连接请求的socket通信 客户端向服务器端发出连接请求,用到Connect函数,Connect函数原型如下:
? public Connect(EndPoint remoteEP)
参数:remoteEP表示要连接的服务器端点。例如向服务器端发出连接请求,服务器IP为ServerIP,端口为Port。
ServerIP,Port); //定义要连接的服务器端点 IPEndPoint Server=new IPEndPoint(
Sock=new Socket(AddressFamily.InterNetwork,SocketType.Stream,
ProtocolType.Tcp) // 构造一个socket
Server); // 与服务器连接 Sock.Connect(
4)、数据的传送和接收
使用两个用于传送和接收数据的函数:Send、Receive。函数原型如下:
? public int Send(byte[] buffer,int size,SocketFlag socketFlags) 参数:buffer表示要发送的数据;size表示要发送数据的大小;socketFlags提供Socket消息的常数值,具有允许按位组合其成员值的属性。
返回值为发送到socket的字节数。
?public int Receive(byte[] buffer,int size,SocketFlage socketFlags) 参数:buffer表示接收到的数据的存储位置;size表示要接收数据的大小;socketFlags提供Socket消息的常数值,具有允许按位组合其成员值的属性。
返回值为接收到socket的字节数。
例如:接收来自客户端的数据,同时将该数据返回到客户端。Socket是前面例子中定义和设置好的。
public static string data=null; //定义字符串变量存放接收到的信息
bytes=new byte[1024];
int bytesRec=connectsock.Receive(bytes,bytes.Lentgh,0); //接受来自客户端的数据 Console.WriteLine(”Text received:{0}”,bytes); //显示接收到的数据 connectsock.Send(bytes,bytes.Length,0); //发送数据到客户端
5)、socket的关闭
在socket关闭之前,要确保已经发送和接收完所有挂起的数据,因此在关闭socket之前,要先
调用Shutdown,函数原型如下:
?public void Shutdown(SocketShutdown how)
参数:SocketShutdown指定不再允许的操作。成员名称:Both禁止socket发送和接收;Receive禁止socket接收数据;Send禁止socket发送数据。
采用close函数强制关闭Socket连接。函数原型如下:
?public void clsoe()
当该套接字被关闭时,Connected属性将被设置为false。
3、Linux C编程要点
在Linux C下编写Socket通信程序,必须包含下列相关的头文件:
#include”sys/socket.h”
#include”netinet/in.h”
#include”arpa/inet.h”
需要掌握下列编程要点:
1)、创建套接字
函数:int socket(int domain,int type,int protocol)
其中参数domain用于指定协议族,对于TCP/IP可以使用AF_INET。参数type用于指定套接口类型,包括:SOCK_STREAM:字节流套接口,面向连接;SOCK_DGRAM:数据报套接口,无连接;SOCK_RAW:原始套接口,主要用于直接读取IP包。参数protocol:由domain和type决定,当domain采用AF_INET,type采用SOCK_STREAM或SOCK_DGRAM时,通常只有一种选择,故用0填充。 函数socket返回一个文件描述符,可以在其上使用read和write进行数据读写。 2)、套接口地址
AF_INET的地址结构由netinet/in.h中的结构sockaddr_in所描述,至少包含如下成员: struct sockaddr_in {
short int sin_family; //这项填充AF_INET
unsigned short int sin_port; //这项填写端口号
struct in_addr sin_addr; //这项填写IP地址
}
struct in_addr {
unsigned long int s_addr;
};
3) 、给套接口命名
为了使得一个套接口可以为其他进程所使用,作为服务器进程必须给这个套接口命名。对于AF_INET,意味着把一个IP地址绑定到这个套接口上。
int bing(int socket, const struct sockaddr *address, size_t address_len); 如果执行成功,返回0,否则返回-1。
4)、建立监听队列
为了接受来自其他套接口的连接,服务器程序必须创建一个用于接受连接的队列,该过程通过调用listen函数实现。
listen(int socket, int backlog);
参数:socket表示服务器套接口描述符;backlog表示套接口接受连接队列的最大个数。 5)、接受连接
一旦服务器程序创建并命名了套接口,它就可以调用accept函数响应客户的连接请求。如果连接队列为空,则进程睡眠(在此假设套接口为阻塞方式)。
int accept(int socket, struct sockaddr *address, size_t *address_len); 参数address表示返回连接方(客户)进程的协议地址;address_len表示返回地址长度。 6)、建立连接
客户使用带有一个未命名的套接口的connect函数与服务器端的套接口建立连接请求。 int connect(int socket, const struct sockaddr *address, size_t address_len); 参数socket表示未命名的套接口;address表示服务器地址;address_len表示地址长度。 函数执行成功,返回0,失败返回-1。
7)、关闭套接口
使用close函数来关闭套接口。
以下是本机(IP地址127.0.0.1)上建立server和client两个进程进行socket通信的关键程序段。
?server端主要代码:
int server_sockfd;
unsigned int server_len,client_len; socklen_t client_len;
struct sockaddr_in server_address, client_address;
char ch;
server_sockfd=socket(AF_INET,SOCK_STREAM,0); //建立流式套接口 server_address.sin_family=AF_INET; server_address.sin_addr.s_addr=inet_addr(”127.0.0.1”); server_address.sin_port=2000; server_len=sizeof(server_address);
bind(server_sockfd,(struct sockaddr *)&server_address, server_len); //为这个套接口//绑定名字,包含地址、类型等
listen(server_sockfd,5); //创建监听队列,等待用户的连接请求 printf(”server waiting \n”);
client_sockfd=accept(server_sockkfd,(struct sockaddr *)&client_address,&client_len);
//以上接受一个客户的连接请求
read(client_sockfd,&ch,1); //服务器端读客户发送来的一个字符 ch++; write(client_sockfd,&ch,1); //把读取字符做简单处理,回送 close(client_sockfd);
?client端主要代码:
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch=’A’;
sockfd=socket(AF_INET,SOCK_STREAM,0); //建立客户端套接口
address.sin_family=AF_INET;
address.sin_addr.s_addr=inet_addr(”127.0.0.1”);
address.sin_port=2000;
len=sizeof(address); //设置服务器套接口的地址,其中包含套接口的类型、名称 result=connect(sockfd,(struct sockaddr *)&address,len); //向服务器端发出连接请求 if(result= =-1) { perror(”connect fail!”); exit(1);}
//以上试图与服务器套接口建立连接
write(sockfd,&ch,1); read(sockfd,&ch,1);
//如果成功,将向服务器端发送一个字符,然后读取服务器的回答
printf(”char from server=%c\n”,ch);
close(sockfd); exit(0);
4、 VB编程要点
VB5.0以上版本通过Winsock控件提供传输服务原语。两个程序采用TCP/IP通信,其一必须创建socket服务器侦听,另一个必须创建socket客户去连接服务器,建立连接后两者就可以进行通信。
1)、服务器端程序
?创建socket服务器:向窗体form1添加Winsock控件,假设命名为ss,在form1_load事件增加以下代码:
Sub form1_load()
ss.localport-2000 ‘服务器端口号避免与熟知端口冲突
ss.listen ‘开始侦听
End sub
?接受客户连接请求:客户连接到本服务器,产生ConnectionRequest事件,在事件过程加入如
下代码:
Sub ss_ConnectionRequest(Byval requested As Long)
ss.close
ss.accept requested ‘accept方法接受一个连接请求
End Sub
?接收客户发送的数据:客户向服务器发送的数据到达,产生DataArrival事件,在事件过程加
入如下代码:
Sub ss_DataArrival(Byval bytestotal As Long)
Dim s As String
ss.GetData s ‘GetData方法用于接收数据,bytestotal返回到达的字节数 text1.text=s
End Sub
?向客户发送数据:在窗体上单击命令按钮“Send”,产生Click事件,在事件过程加入如下代
码:
Sub Send_click()
ss.SendData text2.text ‘将文本框text2上的文本通过socket发送出去 End Sub
?响应客户关闭连接:客户关闭连接时,服务器会产生close事件,在事件过程加入如下代码:
Sub ss_close
ss.close ‘关闭当前连接
ss.listen ‘重新开始侦听
End Sub
?主动关闭连接
Sub Form1_Unload(Cancel as integer)
ss.close
End Sub
2)、客户端程序
创建客户连接服务器:向窗体添加Winsock控件,假设命名为sc,单击命令按钮“Connect”,
产生Click事件,加入如下代码:
Sub Connect_Click()
sc.RemoteHost=”127.0.0.1” ‘服务器主机名或IP地址 sc.RemotePort=2000 ‘服务器套接字端口号
sc.connect ‘connect方法用于发送连接请求
End Sub
其余发送、接收数据、关闭连接的程序代码与服务器端类似。
【实验作业】
采用你熟悉的编程语言编程实现Socket支持下网上点对点通信的程序。