因为使用IEC61850需要直接访问以太网数据链路层,因此需要做一些访问数据链路层的准备工作。计划使用Linux C构造UDP原始帧在局域网内广播消息,并在另一台电脑上使用QT程序接收和显示这个广播消息。
网上关于使用RAW SOCKET构造原始帧的资料很少,在外国的一个网站上找到了一份源码,经过改造后可以再局域网内广播UDP了。 在此之前遇到了很多问题,在此总结:
(1)使用wireshark可以抓取到UDP数据帧,但应用层的QT程序就是不能读到这个UDP中的数据。经测试,这可能是因为UDP段的校验和错误,wireshark并不会报错,但该UDP无法被应用层的程序接收(经过本人测试)。
(2)以为identification(ID)的值对于发送只有一个数据帧的UDP会产生影响。经测试,如果UDP只有一个数据帧,则identification可以取任意值,不会影响UDP消息的正确接收。
(3)构造UDP原始帧的Linux C程序和接收UDP消息的QT程序若放在同一台计算机下测试,则无法接收到广播的UDP消息;若将两个程序分别放在两台不同的计算机下运行,则UDP广播消息可被正确地接收。(本人分析的原因:本机上发送的UDP广播可能不会广播给自身,从而导致了在同一台计算机上不能正确接收)
(4)为何wireshark抓到的UDP原始帧中最后几位为"00",wireshark显示其属于trailer。在以太网中,规定最小的数据包为64字节(和碰撞检测有关),如果数据包不足64字节,则会由网卡填充,所以显示到后面几位就是"00"。
(5)以太网帧的最小长度到底是多少?不是64字节么?为什么看到了42字节的arp包? 是64字节,你用wireshark抓到的包是把最后4个字节的FCS丢掉的结果,在没有达到64字节时,网卡驱动会自动填充到64字节。楼主看到的42字节,可能是wireshark做了处理,去掉了填充部分。
在这个过程中其实遇到了很多问题,就不一一列举了,再好的文字都不如代码来的实在,下面是一些源码:
【1】接收UDP消息的QT程序
/**
*这个程序功能很简单:监听6666端口的UDP消息,如果接收到消息则显示。
*/
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
socket=new QUdpSocket(this);
connect(ui->startButton,SIGNAL(clicked()),this,SLOT(startDo()));
connect(ui->clearButton,SIGNAL(clicked()),ui->historyTextEdit,SLOT(clear()));
connect(ui->quitButton,SIGNAL(clicked()),this,SLOT(quitDo()));
}
MainWindow::~MainWindow()
{
delete ui;
}
bool MainWindow::validatePort(QString port)
{
bool ok=false;
int tmp_port=port.toInt(&ok,10);
if(ok){
if((tmp_port>1024)&&(tmp_port<65536)){
return true;
}else{
return false;
}
}else{
return false;
}
}
//初始化程序UDP相关的部分
void MainWindow::startDo()
{
QString port=ui->portEdit->text();
bool bo=validatePort(port);
if(bo && (port != "")){
bool ok=socket->bind((quint16)port.toInt());
if(ok){
ui->historyTextEdit->append("bind indicated port!");
}else{
ui->historyTextEdit->append("bind error!");
}
}else{
bool ok=socket->bind(DEFAULT_PORT);
if(ok){
ui->historyTextEdit->append("bind default port!");
}else{
ui->historyTextEdit->append("bind error!");
}
}
connect(socket,SIGNAL(readyRead()),this,SLOT(readData()));
ui->startButton->setEnabled(false);
}
//读取UDP数据报中的信息
void MainWindow::readData()
{
while(socket->hasPendingDatagrams()){
QByteArray arr;
arr.resize(socket->pendingDatagramSize());
socket->readDatagram(arr.data(),arr.size());
ui->historyTextEdit->append(arr);
}
}
void MainWindow::quitDo()
{
exit(0);
}
通过了测试的Linux C UDP原始帧构造程序有两个,便于参考。
【2】Linux C UDP构造参考程序一
/**
*功能:构造UDP 原始帧,并使用Linux原始套接字发送到局域网中。
*UDP数据帧中包含了一个测试用的字符串“123456789”
**/
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <linux/if_packet.h>
#include <linux/if.h>
#include <linux/sockios.h>
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int fd=0;
char buf[256]={0};
struct sockaddr_ll dest;
int destlen=0;
int ret=0;
struct ifreq ifstruct;
fd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL));
if(fd<0){
printf("ERR:socket was failed.\n");
return -1;
}
memset((char *)&dest, 0x00,sizeof(dest));
destlen=sizeof(dest);
strcpy(ifstruct.ifr_name, "eth0");
ioctl(fd, SIOCGIFINDEX, &ifs