窗体与数据帧分析
文章目录
一、wireshark的安装
链接: https://pan.baidu.com/s/18jMDSNe6Za-iMccuTsvm9w
提取码:vbli
下载完成后,我们开始安装;
双击Wireshark-win64-3.2.7文件,进入安装界面,双击Next.
同意协定,点击I agree.
双击Next
一直点击Next,确定路径(可自定义),点击next,然后install
等待安装
安装完成,点击finish
最后,我们在桌面创建一快捷方式,方便使用。
至此,wireshark就已经安装完成了
二、用C编写一个命令行/控制台
本部分内容: C# 编写一个命令行/控制台 hello world 程序,实现如下功能:在屏幕上连续输出 50 行 “ hello cqjtu!重交物联2018级 ” ;同时打开一个网络 UDP 套接字,向室友电脑或树莓派发送这 50 行消息
1、命令行/控制台程序
这里需要用到编译工具visual studio2019
,由于前面已经安装完成,这里不再详细阐述其安装操作。提供下载途径,如果有需要可以参考。
博客: https://blog.csdn.net/QWERTYzxw/article/details/109080487
安装教程: visual studio2019的安装以及使用图文步骤详解
接下来,我们操作如何用C#编写一个命令行/控制台功能程序。
打开Visual Studio2019,点击“创建新项目”,选择“控制台应用(.NET Framework) ”,然后点击“下一步”。
这里我们找不到c#的控制台应用,“找不到完全匹配项”,我们可以选择点击“安装多个工具和功能”。
点击选择“.Net桌面开发”,进行安装。
等待安装
安装完成后,点击启动,进入到下面界面看到我们所需的控制台。
选择控制台应用,点击下一步,创建新项目。在项目名称填入ConsolTest
(可自行定义);自由选择路径,然后点击“创建”。
创建完成后,显示界面如下:
2、功能的实现
在main函数后面输入下面代码。
for(int i = 0; i < 50; i++)
{
Console.WriteLine("第{0}行:hello cqjtu!重交物联2018级", (i + 1));
}
System.Console.ReadKey();
点击运行,结果如下:
至此,我们的控制台创建完成,并且实现了在屏幕上连续输出50行“hello cqjtu!重交物联2018级”的功能。
3、对UDP协议的了解
当前协议分为:UDP与TCP两类
UDP是无连接协议,客户端和服务器通信之前不需要建立握手连接;
UDP没有应答机制,所以也没有重发机制,很大的可能会造成丢包、收到重复包、乱序的情况;
UDP可以实现局域网广播功能,即某个主机可以向所有在同个子网的主机发送数据…
如果相对UDP有更加深入的了解,可以参考以下资料。
资料1: UDP-文库
资料2:UDP与TCP对比
4、UDP通信
客户端发送,服务器接收。
首先在自己电脑上创建一个新项目client,将下列代码复制粘贴进去;
客户端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Client
{
class Program
{
static void Main(string[] args)
{
//提示信息
Console.WriteLine("按下任意按键开始发送...");
Console.ReadKey();
//做好链接准备
UdpClient client = new UdpClient(); //实例一个端口
IPAddress remoteIP = IPAddress.Parse("10.60.202.32"); //假设发送给这个IP
int remotePort = 11000; //设置端口号
IPEndPoint remotePoint = new IPEndPoint(remoteIP, remotePort); //实例化一个远程端点
string sendString = "hello cqjtu!重交物联2018级";//要发送的数据:hello cqjtu!重交物联2018级
for (int i = 0; i < 50; i++)
{
//定义发送的字节数组
//将字符串转化为字节并存储到字节数组中
byte[] sendData = null;
sendData = Encoding.Default.GetBytes(sendString);
client.Send(sendData, sendData.Length, remotePoint);//将数据发送到远程端点
}
client.Close();//关闭连接
//提示信息
Console.WriteLine("");
Console.WriteLine("数据发送成功,按任意键退出...");
System.Console.ReadKey();
}
}
}
然后在室友电脑上创建一个server的项目,将下列代码复制粘贴进去;
服务器:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace Server
{
class Program
{
static void Main(string[] args)
{
int i = 0;
UdpClient client = new UdpClient(11000);
string receiveString = null;
byte[] receiveData = null;
//实例化一个远程端点,IP和端口可以随意指定,等调用client.Receive(ref remotePoint)时会将该端点改成真正发送端端点
IPEndPoint remotePoint = new IPEndPoint(IPAddress.Any, 0);
while (true)
{
i++;
Console.WriteLine("正在准备接收第 {0} 条数据...",i);
receiveData = client.Receive(ref remotePoint);//接收数据
receiveString = Encoding.Default.GetString(receiveData);
Console.WriteLine(receiveString);
Console.WriteLine("第 {0} 条数据接收完毕!",i);
Console.WriteLine("");
}
//client.Close();//关闭连接,如果没有死循环的话,实例client后需要关闭。
}
}
}
5、编译结果
客户端:
服务器:
6、wireshark抓包
双击桌面下载的wireshark
由于网络连接为以太网,所以选择以太网2
我们可以看到wireshark在一直抓包,点击红色按钮停止抓包
重新编译客户端与服务器,不发送
点击File下面的图标,不保存之前抓到的包
然后开始任意键发送,发送完后。点击wireshark红色按钮,停止抓包
在“过滤器”里面输入UDP,会显示下面内容
7、数据帧结构分析
大家都知道我们从应用层发出的消息会经由传输层,网络层,数据链路层,物理层,它们每到一层都会转变形式。
可参考链接: https://zhuanlan.zhihu.com/p/146188551.
下面这张图片,让我们详细了解数据帧结构
EthernetII帧结构
EthernetII帧结构。有几种不同类型的帧结构,尽管它们格式和最大传输单元不同,但却能够共存于相同的物理媒体上。EthernetII 帧(又称DIX帧)是目前使用最广的以太帧。图21显示了Ethernet II帧结构(该帧前后的辅助字段没有显示)。与802.3以太帧结构相比,它较为简单。其中的以太类型字段标识了封装了该帧数据中的较高层协议。例如,以太类型值为0x0800指示了该帧包含了IPv4数据报,0x0806表明指示了该帧包含了ARP帧,0x8100指示了该帧包含了IEEE 802.1Q帧。
目的地址(Destination):数据的接收方,这里是10.60.202.32;
源地址:(Source):数据的发送方,这里是10.60.190.83
数据类型(Type):分为 IP(0800) 包和 ARP(0806) 包,包是 0800 ,所以是 IP 包
数据:后面三排,上层(网络层)传下来的一个 IP 包,数据部分又分为数据部分和填充部分,当这个 IP 包的长度小于 46 个字节,比如数据部分长度为 30 ,那么填充部分(无用信息)就是 16 个字节,保证这个帧的总长度最短为 64 个字节,如果数据部分长度为50,那么填充部分就为 0 个字节,最后,数据(数据部分 + 填充部分)的长度最长为 1500 个字节,不能过大,所以帧的总长度最长为 1518 字节,抓取的所有包,都不会超过这个长度。
校验码:帧的末尾有 4 字节的校验码,但是抓包软件会自动舍去
ip数据报
下面我们任意一个数据进行分析
Version(版本号):分为 IPv4 和 IPv6 现在普遍都用的 IPv4 ,所以值为 4 ,1 个字节
HLen(ip报头长度):32位字的报头长度(HLEN)
TOS(级别):服务类型描述数据报将如何被处理,比如优先发送等,大多数都是默认为 0
Datagram Total Length(总长度):包括报头和数据的数据包长度
identifier(标识):唯一的 IP 数据包值
Flags(标志):说明是否有数据被分段,一条一条的发送多个数据包,每个包的数据很小,没有被分段,所以这里数值为 0
Fragmentation Offset(分段偏移):如果数据包在装人帧时太大,则需要进行分段和重组,这里没有分段,所以偏移量为 0
TTL(存活期):存活期是在数据包产生时建立在其内部的一个设置,如果这个数据包在这个TTL到期时仍没有到达它要去的目的地,那么它将被丢弃,这个设置将防止IP包在寻找目的地的时候在网络中不断循环,每经过一个路由器,它的值就减一,这里它的值为 128 ,也就是 128 个生存期
Protocol(协议):上层协议的端口( TCP 是端口 6;UDP 是端口 17) ,同样也支持网络层协议,如ARP和ICMP,这里值为 17 ,也就是 UDP 协议
Header Checksum(校验码):只针对报头的循环冗余校验(CRC)
Source Address(源地址):消息发送者的 ip 地址,这里是10.60.190.83
Destination Address(目的地址):消息接收者的 ip 地址,这里是10.60.202.32
ip包头的第六行:用于网络检测、调试、安全以及更多的内容
数据包
长度为 34 字节,对应的十六进制就是蓝色的区域了,这就是我们要发送的数据:第n行:hello cqjtu!重交物联2018级
至此,数据帧的结构就分析完了,想要了解更多详情,可以见参考资料。
三、vs2019的C#编写一个简单的Form窗口程序
本部分内容:用 VS2019 的 C# 编写一个简单的 Form 窗口程序,有一个文本框 textEdit 和一个发送按钮 button ,运行程序后,可以在文本框里输入文字,如 “ hello cqjtu!重交物联2018级 ” ,点击 button ,将这些文字发送给室友电脑或树莓派,采用 TCP 套接字
1、创建窗口
创建窗口(创建步骤大致同上)
打开VS2019,选择创建新项目,选择Windows窗体应用,点击下一步
配置新项目,自定义项目名称、路径,点击创建
创建完成
至此,窗口的创建就完成了。
2、设计界面
摆放控件,从工具箱内拖 2 个 TextBox 和 1 个 Button 控件
设置消息输入框属性
左键选中最下面的 TextBox ,并在右下角的属性中找到 Font 属性,并点击 “ … ” ,界面内可以设置字体样式。
在设计属性中的(Name)这里默认的 textBox1 ,也可以更改,但不能重复,唯一标识,这是控件的编号,用于代码编写的时候识别,不能用中文。
在布局这里,Location 是指控件左上角顶点基于窗口所在的位置,Size 是指控件的长和宽,可以自行设置
设置消息显示界面属性
选中一个 TextBox ,并点击黑色的小三角按钮,可以将单行文本框设置为多行文本框;找到 ScrollBars 属性,设置参数为 Vertical
设置边界样式:找到 BorderStyle ,参数设置为 FixedSingle
找到 Enabled 属性,参数设为 False
可以根据自己需求设置文本属性
左键点击button按钮,找到 Text 属性,参数输入为 “ 发送 ” ,则控件上就会显示输入的“发送”字样
设置窗体属性
左击窗体选中它,在右下角的属性中找到 Text 属性,编辑为 “ 客户端 ” ,然后窗体的左上角,就显示为 “ 客户端 ”。
选中AcceptButton 属性,下拉框选中这个 button1 按钮,设置完这个属性后,当我们最后执行这个程序后,按下回车键 相当于点击这个按钮。
至此,控件就基本设置完成了。
3、窗体程序代码
需要创建的项目方法参照上面内容,这里忽略。
双击界面按钮,VS会自动跳到代码区域。
button1_Click 函数内复制粘贴如下代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApp1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void button1_Click(object sender, EventArgs e)
{
try
{
/*
* 显示当前时间
*/
string str = "The current time: ";
str += DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
textBox2.AppendText(str + Environment.NewLine);
/*
* 做好连接准备
*/
int port = 2000;
string host = "10.60.202.32";//我室友的IP地址
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);//把ip和端口转化为IPEndPoint实例
Socket c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个Socket
/*
* 开始连接
*/
str = "Connect to server...";
textBox2.AppendText(str + Environment.NewLine);
c.Connect(ipe);//连接到服务器
/*
*发送消息
*/
string sendStr = textBox1.Text;
str = "The message content: " + sendStr;
textBox2.AppendText(str + Environment.NewLine);
byte[] bs = Encoding.UTF8.GetBytes(sendStr);
str = "Send the message to the server...";
textBox2.AppendText(str + Environment.NewLine);
c.Send(bs, bs.Length, 0);//发送信息
/*
* 接收服务器端的反馈信息
*/
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = c.Receive(recvBytes, recvBytes.Length, 0);//从服务器端接受返回信息
recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
str = "The server feedback: " + recvStr;//显示服务器返回信息
textBox2.AppendText(str + Environment.NewLine);
/*
* 关闭socket
*/
c.Close();
}
catch (ArgumentNullException f)
{
string str = "ArgumentNullException: " + f.ToString();
textBox2.AppendText(str + Environment.NewLine);
}
catch (SocketException f)
{
string str = "ArgumentNullException: " + f.ToString();
textBox2.AppendText(str + Environment.NewLine);
}
textBox2.AppendText("" + Environment.NewLine);
textBox1.Text = "";
}
}
}
复制下面 main 函数内的代码,并粘贴进你自己的 main 函数内
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
/*
* 做好连接准备
*/
int i = 0;
int port = 2000;
string host = "10.60.202.32";
IPAddress ip = IPAddress.Parse(host);
IPEndPoint ipe = new IPEndPoint(ip, port);
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建一个Socket类
s.Bind(ipe);//绑定2000端口
/*
* 循环监听并处理消息
*/
while (true)
{
i++;
try
{
Console.Write("Perform operations {0} :",i);
Console.WriteLine("\t-----------------------------------------------");
s.Listen(0);//开始监听
Console.WriteLine("1. Wait for connect...");
/*
* 实例一个新的socket端口
*/
Socket temp = s.Accept();//为新建连接创建新的Socket。
Console.WriteLine("2. Get a connect");
/*
* 接收客户端发的消息并做解码处理
*/
string recvStr = "";
byte[] recvBytes = new byte[1024];
int bytes;
bytes = temp.Receive(recvBytes, recvBytes.Length, 0);//从客户端接受信息
recvStr += Encoding.UTF8.GetString(recvBytes, 0, bytes);
Console.WriteLine("3. Server Get Message:{0}", recvStr);//把客户端传来的信息显示出来
/*
* 返回给客户端连接成功的消息
*/
string sendStr = "Ok!Client send message sucessful!";
byte[] bs = Encoding.UTF8.GetBytes(sendStr);
temp.Send(bs, bs.Length, 0);//返回客户端成功信息
/*
* 关闭端口
*/
temp.Close();
Console.WriteLine("4. Completed...");
Console.WriteLine("-----------------------------------------------------------------------");
Console.WriteLine("");
//s.Close();//关闭socket(由于再死循环中,所以不用写,但如果是单个接收,实例socket并完成任务后需关闭)
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}
}
}
}
}
4、结果演示
客户端:
服务器端:
5、数据帧分析
参考上面抓包方式,进行抓包分析。由于上面已有抓包分析数据帧结构的详细步骤,这里不再阐述。这里主要说明一下UDP与TCP协议的区别。
UDP:
第三行:Internet Protocl Version 4:IPv4协议
第四行:User Datagram Protocol:UDP协议
TCP:
第三行:Internet Protocl Version 4:IPv4协议
第四行:Transmission Control Protocol:TCP协议
由此可见,它们区别在于第四行,第三行都是 IPv4 协议
p 包头也是 45 00 开头,对比一下第二部分对 ip 包的分析,这个 ip 包头也是 45 00 开头,所以一个 ip 包的前两个字节一定是 45 00
生存期也是 128 ,一个包的生存期基本上都是默认的数值 128 ,而这里的协议号是 6 ,这就是 TCP 的协议号,第二部分的协议号是 17 ,是 UDP 的
其他的就同UDP协议雷同了。
四、总结与参考资料
1、总结
能够创建窗体,用窗体进行消息的发送与接收;了解到UDP与TCP协议是怎么进行套字的;对我们日常发送的消息是怎么进行有了深刻了解;对数据帧的认识也更加深刻。
2、参考资料
1: 分析数据链路层帧结构实验.
2: UDP-文库.
3: 数据帧结构.
4: 数据帧.
5: UDP数据的发送与接收.
6: 网络编程之UDP套接字.