一、NetworkStream
什么是NetworkStream?
NetworkStream
是 .NET Framework 中用于在网络上进行数据传输的流类。它属于System.Net.Sockets
命名空间,并提供了通过网络连接进行读写操作的功能。NetworkStream
主要用于处理从网络套接字(Socket
)获得的输入和输出流数据
主要功能
NetworkStream
允许你通过网络连接进行流式数据传输。它可以用于发送和接收数据,通常与 TcpClient
、TcpListener
、Socket
等类一起使用,如下:
-
数据流:
NetworkStream
实现了Stream
抽象类,提供了基本的流操作方法,如Read
、Write
、Flush
和Close
。 -
实时传输: 它能够进行实时的数据传输,对于需要即时响应的网络通信尤为重要。
-
自动处理缓冲:
NetworkStream
处理了网络数据传输中的缓冲区管理,帮助简化网络编程。 -
同步和异步操作:
NetworkStream
支持同步和异步数据读写操作,可以提高程序的响应能力和性能。
使用方法:
// 链接到服务器
TcpClient _tcpClient = new TcpClient("localhost",8080);
// 获取networkstream 对象
NetworkStream _stream= _tcpClient.GetStream();
// 发送数据
string _message = "hello server";
byte[] data=Encoding.ASCII.GetBytes( _message );
_stream.Write( data, 0, data.Length ); // 向 NetworkStream 中写入数据,通常会指定一个字节数组和要写入的字节数
_stream.WriteAsync(data, 0, data.Length); // 异步写入
_stream.Flush(); // 将缓冲区中的所有数据写入到网络流中
// 接收数据
byte[] _buffer=new byte[1024];
int bytesRead = await _stream.ReadAsync(_buffer, 0, _buffer.Length); // 异步读取
int _byteRead = _stream.Read(_buffer,0,_buffer.Length);
string _response=Encoding.ASCII.GetString( _buffer ,0,_buffer.Length);
Console.WriteLine("Received: " + _response);
_stream.Close();
_stream.Close();
二、ConcurrentDictionary
ConcurrentDictionary
是 .NET 中的一个线程安全的字典实现,属于 System.Collections.Concurrent
命名空间。它设计用于在多线程环境下进行高效的并发操作,提供了一种安全的方法来存储和管理键值对,避免了多线程访问时的数据竞争和同步问题。
主要特点:
-
线程安全:
ConcurrentDictionary
允许多个线程同时读取和写入,而不需要外部同步机制。它内部使用锁和其他并发控制技术,确保在并发操作中数据的一致性和完整性。 -
高效的并发操作: 它通过采用分段锁定、无锁技术和其他优化策略,提供了高效的性能,尤其是在大量并发操作时。
-
支持并发集合操作:
ConcurrentDictionary
支持常见的字典操作,如添加、移除和查找元素,并且提供了原子操作的方法,以保证线程安全
主要方法和属性
TryAdd(K key, V value)
:
尝试将指定的键值对添加到字典中。如果键已经存在,操作失败。
bool added = myDict.TryAdd("key", "value");
TryGetValue(K key, out V value)
:
尝试获取指定键的值。如果键存在,返回 true
并通过 out
参数返回值,否则返回 false
if (myDict.TryGetValue("key", out string value))
{
Console.WriteLine(value);
}
TryRemove(K key, out V value)
:
尝试移除指定的键及其对应的值。如果键存在并成功移除,返回 true
并通过 out
参数返回移除的值。
if (myDict.TryRemove("key", out string value))
{
Console.WriteLine($"Removed: {value}");
}
AddOrUpdate(K key, V addValue, Func<K, V, V> updateValueFactory)
:
如果键不存在,则添加键值对;如果键存在,则使用 updateValueFactory
更新现有值。
myDict.AddOrUpdate("key", "newValue", (key, oldValue) => oldValue + " updated");
GetOrAdd(K key, V value)
:
如果键不存在,则添加键值对并返回新值;如果键已存在,则返回现有值。
string value = myDict.GetOrAdd("key", "defaultValue");
示例:
public void Text()
{
ConcurrentDictionary<string, int> concurrentDict = new ConcurrentDictionary<string, int>();
// 使用多个任务并发添加数据
Parallel.For(0, 10000, i =>
{
concurrentDict.TryAdd(i.ToString(), i);
});
// 使用多个任务并发读取数据
Parallel.For(0, 10000, i =>
{
if (concurrentDict.TryGetValue(i.ToString(), out int value))
{
Console.WriteLine($"Key: {i}, Value: {value}");
}
});
// 使用 AddOrUpdate 方法更新数据
concurrentDict.AddOrUpdate("50", 100, (key, oldValue) => oldValue + 50);
// 输出更新后的值
if (concurrentDict.TryGetValue("50", out int updatedValue))
{
Console.WriteLine($"Updated Key: 50, Value: {updatedValue}");
}
}
三、Socket类
属性
方法:
C#网络编程TCP程序设计(Socket类、TcpClient类和 TcpListener类)_网络_wenchm-GitCode 开源社区
TcpClient类
TcpClient类用于在同步阻止模式下通过网络来连接、发送和接收流数据。为了使TcpClient连接并交换数据,TcpListener实例或Socket实例必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接该侦听器。
- 创建一个TcpClient,并调用Connect方法连接。
- 使用远程主机的主机名和端口号创建TcpClient,此构造函数将自动尝试一个连接。
- TcpListener类用于在阻止同步模式下侦听和接受传入的连接请求。可使用TcpClient类或Socket类来连接TcpListener,并且可以使用IPEndPoint、本地IP地址及端口号或者仅使用端口号来创建TcpListener实例对象
属性、方法
TcpListener
TcpListener
是 .NET 中用于监听和接受传入 TCP 连接的类。它属于 System.Net.Sockets
命名空间,提供了创建服务器应用程序的功能,通过它可以接收来自客户端的连接请求,并与客户端进行数据交换。
主要功能
- 监听端口:
TcpListener
监听特定的 IP 地址和端口号,以接受客户端的 TCP 连接请求。 - 接受连接: 它能够接收来自客户端的连接,并返回一个
TcpClient
对象,该对象可以用于与客户端进行数据交换。 - 异步操作: 支持异步操作,使得服务器能够处理多个连接而不会阻塞主线程。
关键方法和属性
AcceptTcpClient
阻塞当前线程,直到接受到一个 TCP 客户端连接请求,然后返回一个 TcpClient
对象
TcpClient client = listener.AcceptTcpClient();
AcceptTcpClientAsync()
:
异步地接受传入的 TCP 连接请求,返回一个 Task<TcpClient>
对象。
TcpClient client = await listener.AcceptTcpClientAsync();
AcceptSocket()
:
阻塞当前线程,直到接受到一个 TCP 客户端连接请求,然后返回一个 Socket
对象
Socket socket = listener.AcceptSocket();
AcceptSocketAsync()
:
异步地接受传入的 TCP 连接请求,返回一个 Task<Socket>
对象。
Socket socket = await listener.AcceptSocketAsync();
BeginAcceptTcpClient:
用于异步地接受传入的 TCP 客户端连接请求。它允许你的应用程序在等待客户端连接的同时继续执行其他操作,从而提高了应用程序的响应能力和效率。
// 开始异步接受客户端连接
listener.BeginAcceptTcpClient(AcceptTcpClientCallback, listener);
AllowNatTraversal(true);
是一个用于控制网络地址转换(NAT)穿越功能的设置,通常在网络编程和网络应用中使用。这个设置可以用于启用或禁用 NAT 穿越功能,以便客户端和服务器能够更好地在 NAT 环境下进行通信。
this._listener.AllowNatTraversal(true);
AllowNatTraversal
NAT 穿越简介
NAT(Network Address Translation)是一种网络地址转换技术,通常用于私有网络与公共互联网之间的地址转换。NAT 穿越(NAT Traversal)是指在 NAT 环境下使得两个端点能够相互通信的技术或方法。这对于点对点通信(如 VoIP、视频会议、P2P 文件传输等)尤为重要。
WebRTC 和 NAT 穿越
const configuration = {
iceServers: [
{
urls: 'stun:stun.l.google.com:19302' // STUN 服务器地址
},
{
urls: 'turn:turnserver.example.org',
username: 'user',
credential: 'pass'
}
]
};
const peerConnection = new RTCPeerConnection(configuration);
以下是一个简单的使用 TcpListener
的示例,演示了如何创建一个基本的 TCP 服务器,监听客户端连接,并与客户端进行数据交换。
static async Task GodTcplister()
{
// 创建一个 TcpListener 监听器 实例,绑定到本地 IP 地址和端口号
TcpListener _listener = new TcpListener(IPAddress.Any, 5000);
// 启动监听 =======================================
_listener.Start();
Console.WriteLine("Server started, waiting for connections...");
// 接受客户端连接(异步)
TcpClient _client = await _listener.AcceptTcpClientAsync(); // 接受客户端连接:===========================================
Console.WriteLine("Client connected!");
// 获取网络流
NetworkStream _stream = _client.GetStream();
// 读取客户端发送的数据
byte[] _buffer = new byte[1024];
int _bytesRead = await _stream.ReadAsync(_buffer, 0, _buffer.Length);
string _receivedMessage = Encoding.ASCII.GetString(_buffer, 0, _bytesRead);
Console.WriteLine($"Received: {_receivedMessage}");
// 发送响应给客户端
string _responseMessage = "Hello from server!";
byte[] _responseBytes = Encoding.ASCII.GetBytes(_responseMessage);
await _stream.WriteAsync(_responseBytes, 0, _responseBytes.Length);
Console.WriteLine("Response sent to client.");
// 关闭流和客户端
_stream.Close();
_client.Close();
// 停止监听
_listener.Stop();
Console.WriteLine("Server stopped.");
}
服务端-客户端示例:
服务端:
public static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
Console.WriteLine("------------------------------------------------"); //输出消息
int _port = 8888;
TcpClient _tcpClient;
IPAddress[] _serverIP = Dns.GetHostAddresses("127.0.0.1"); //定义IP地址
IPAddress _localAddress = _serverIP[0]; //IP地址
TcpListener _tcpListener = new TcpListener(_localAddress, _port); //监听套接字
_tcpListener.Start(); //开始监听
Console.WriteLine("服务器启动成功,等待用户接入…"); //输出消息
while (true)
{
try
{
_tcpClient = _tcpListener.AcceptTcpClient(); //每接收一个客户端则生成一个TcpClient
NetworkStream _networkStream = _tcpClient.GetStream();//获取网络数据流
BinaryReader _reader = new BinaryReader(_networkStream); //定义流数据读取对象
BinaryWriter _writer = new BinaryWriter (_networkStream); //定义流数据写入对象
while (true)
{
try
{
string strReader = _reader.ReadString(); //接收消息
string[] strReaders = strReader.Split(' ');//截取客户端消息
Console.WriteLine("有客户端接入,客户IP:" + strReaders[0]); //输出接收的客户端IP地址
Console.WriteLine("来自客户端的消息:" + strReaders[1]); //输出接收的消息
string strWriter = "我是服务器,欢迎光临"; //定义服务端要写入的消息
_writer.Write(strWriter); //向对方发送消息
}
catch
{
break;
}
}
}
catch
{
break;
}
}
}
}
客户端:
internal static class Program
{
/// <summary>
/// 应用程序的主入口点。
/// </summary>
[STAThread]
static void Main()
{
TcpClient _tcpClient = new TcpClient(); // 创建一个客户端
Console.WriteLine("========================");
_tcpClient.Connect("127.0.0.1",8888); // IP 端口
if(_tcpClient!=null)
{
Console.WriteLine("连接服务器成功");
NetworkStream _stream = _tcpClient.GetStream(); // 获取数据流
BinaryReader _reader = new BinaryReader(_stream); // 定义二进制数据
BinaryWriter _writer = new BinaryWriter(_stream);
string _localIp = "127.0.0.1";
IPAddress[] _ips= Dns.GetHostAddresses(Dns.GetHostName()); // 获取所有的IP地址
// 遍历
foreach (var ip in _ips)
{
if (!ip.IsIPv6SiteLocal) // 如果不是IPV6 地址
{
_localIp=ip.ToString(); //
}
}
_writer.Write(_localIp + " 你好服务器,我是客户端"); //向服务器发送消息
while(true)
{
try
{
string strReader = _reader.ReadString(); //接收服务器发送的数据
if (strReader != null)
{
Console.WriteLine("来自服务器的消息:" + strReader);//输出接收的服务器消息
}
}
catch
{
break; //接收过程中如果出现异常,退出循环
}
}
}
Console.WriteLine("连接服务器失败");
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
}
}
效果:
四、SerialPort串口
Modbus SerialPort 、TCP协议-CSDN博客
SerialPort
是 .NET 框架中的一个类,用于通过串行端口(也称为 COM 端口)进行数据通信。它提供了与串行通信相关的功能,使得 .NET 应用程序能够通过串行端口与外部设备(如传感器、调制解调器、打印机等)进行数据交换。
串口是什么-CSDN博客
RS-232 D型9针连接器
1、载波检测(CD)
2、接受数据(RXD)
3、发出数据(TXD)
4、数据终端准备好(DTR)
5、信号地线(SG)
6、数据准备好(DSR)
7、请求发送(RTS)
8、清除发送(CTS)
9、振铃指示(RI)
主要功能和特点
-
数据通信:
SerialPort
允许你在应用程序和外部设备之间发送和接收数据。它支持全双工和半双工通信,可以处理 ASCII 或二进制数据。 -
配置串行端口:
你可以配置串行端口的各种参数,包括波特率、数据位、停止位、奇偶校验等,以确保通信的正确性和可靠性。 -
事件处理:
SerialPort
提供了事件处理机制,如数据接收事件 (DataReceived
),可以在接收到数据时触发相应的处理逻辑。 -
同步与异步操作:
SerialPort
支持同步和异步操作,允许在阻塞或非阻塞模式下进行数据读写
全双工和半双工
全双工和半双工是两种不同的通信模式。全双工支持同时双向数据传输,提供了更高的通信效率和实时性;半双工则只允许单向数据传输,通常用于不需要同时双向通信的场景。选择哪种模式取决于具体的应用需求和通信环境
全双工(Full-Duplex)
-
定义: 在全双工模式下,数据可以同时在两个方向上传输。这意味着通信双方可以在同一时间发送和接收数据。
-
特点:
- 双向传输: 数据在两个方向上都可以同时进行,没有冲突。
- 效率高: 由于可以同时进行数据传输,整体的通信效率较高。
- 应用: 常见于电话系统、现代网络通信(如以太网)等需要实时双向数据交流的场景。
-
示例:
- 电话: 在电话通信中,双方可以同时讲话和听对方说话,这就是全双工通信的一个例子。
- 以太网: 现代以太网支持全双工模式,可以同时进行数据发送和接收。
半双工(Half-Duplex)
-
定义: 在半双工模式下,数据只能在一个方向上进行传输,每次只能有一方进行发送或接收。这意味着在同一时间只能进行单向的数据通信。
-
特点:
- 单向传输: 在同一时间点上,数据只能单向传输,通信需要在发送和接收之间切换。
- 效率相对较低: 由于不能同时进行双向传输,可能会造成通信延迟和效率降低。
- 应用: 常见于对讲机、无线电通信等需要单向或切换单向通信的场景。
-
示例:
- 对讲机: 在使用对讲机时,用户必须按下按钮才能说话,放开按钮才能听对方讲话,这就是半双工通信的一个例子。
- 传统的无线电: 传统的无线电通信通常是半双工的,发射和接收需要分开进行。
对比
-
数据传输:
- 全双工: 同时发送和接收数据。
- 半双工: 发送和接收数据需要在不同时间进行。。
关键属性和方法
-
属性:
PortName
: 设置或获取串行端口的名称(例如 "COM1")。BaudRate
: 设置或获取通信的波特率。DataBits
: 设置或获取每个数据字节的位数(通常是 7 或 8)。Parity
: 设置或获取奇偶校验类型(例如 None, Odd, Even)。StopBits
: 设置或获取停止位的数量(例如 None, One, OnePointFive, Two)。Handshake
: 设置或获取握手协议(例如 None, XOnXOff, RequestToSend)。ReadTimeout
: 设置或获取读取操作的超时时间。WriteTimeout
: 设置或获取写入操作的超时时间。
-
方法:
Open()
: 打开串行端口,准备进行数据通信。Close()
: 关闭串行端口,释放相关资源。Read()
: 从串行端口读取数据。Write()
: 向串行端口写入数据。DataReceived
: 数据接收事件,在接收到数据时触发。
奇偶校验
奇偶校验是一种简单的错误检测机制,用于检测数据传输过程中是否出现了错误。其原理基于数据中 1 的数量是否符合预期的奇偶性要求。具体的工作原理如下:
奇偶校验原理
-
数据帧构成:
- 在串行通信中,数据通常以数据帧的形式发送。这些数据帧包括数据位、奇偶校验位和停止位。
- 数据位是实际传输的数据,奇偶校验位用于检测数据传输中的错误,停止位用于表示数据帧的结束。
-
奇偶校验位:
- 奇校验(Odd Parity):在数据帧中,奇偶校验位被设置为 1 或 0,以使数据帧中所有 1 的总数为奇数。如果总数已经是奇数,则校验位设置为 0;如果总数是偶数,则校验位设置为 1。
- 偶校验(Even Parity):在数据帧中,奇偶校验位被设置为 1 或 0,以使数据帧中所有 1 的总数为偶数。如果总数已经是偶数,则校验位设置为 0;如果总数是奇数,则校验位设置为 1。
-
传输过程:
- 当数据被发送时,发送端计算数据位中 1 的数量,并根据所选的奇偶校验类型(奇校验或偶校验)设置校验位。
- 接收端在接收到数据帧后,重新计算数据位中 1 的数量,并检查校验位是否符合预期的奇偶性。如果接收端计算的奇偶性与校验位不符,说明数据可能在传输过程中出现了错误。
示例
假设你要发送数据 1011001
,使用偶校验:
- 数据位(7 位):
1011001
- 1 的总数:4(偶数)
对于偶校验,校验位应该设置为 0
(因为 4 是偶数)。
数据帧的最终格式可能是:
- 数据位:
1011001
- 校验位:
0
传输数据:10110010
接收端在接收到数据帧后:
- 计算接收到的数据位
1011001
的 1 的总数:4(偶数) - 检查校验位
0
是否符合偶校验的要求(1 的总数为偶数)
如果校验通过,接收端可以确认数据传输是可靠的。如果校验失败,接收端将知道数据传输中可能发生了错误。
重要注意点
- 奇偶校验的局限性:奇偶校验只能检测单个比特错误(即单一比特的改变),它不能检测到多个比特错误或数据重排的情况。对于更强的错误检测,通常需要使用更复杂的校验算法,如 CRC(循环冗余检查)。
- 配置:在使用
SerialPort
时,正确配置奇偶校验确保发送端和接收端使用相同的校验设置是非常重要的。如果校验设置不匹配,可能会导致数据接收错误。SerialPort serialPort = new SerialPort("COM1"); serialPort.Parity = Parity.Even; // 设置偶校验 serialPort.BaudRate = 9600; // 设置波特率 serialPort.DataBits = 8; // 数据位 serialPort.StopBits = StopBits.One; // 停止位 serialPort.Open();
停止位
在串行通信中,StopBits
(停止位)是数据帧的一个重要部分,用于标识数据帧的结束。它的作用是帮助接收端确定数据帧的结束位置,并在接收数据时进行适当的同步。停止位确保数据传输的正确性和完整性。StopBits
的配置会影响数据传输的可靠性和时序
停止位的类型
StopBits
属性可以设置为以下几种类型之一:
-
StopBits.None
:- 描述: 没有停止位。这个设置是不常用的,因为它可能导致接收端无法正确确定数据帧的结束位置。
- 使用场景: 不推荐使用,因为它可能会增加数据错误的风险。
-
StopBits.One
:- 描述: 使用一个停止位。最常用的设置,适用于大多数串行通信应用。
- 使用场景: 默认设置,适用于大多数标准串行通信协议。
-
StopBits.OnePointFive
:- 描述: 使用 1.5 个停止位。这是一个较少使用的设置。
- 使用场景: 某些特定的旧设备或协议可能需要此设置。
-
StopBits.Two
:- 描述: 使用两个停止位。提供比一个停止位更长的间隔,有助于处理更高的传输错误容忍度。
- 使用场景: 适用于需要更高可靠性的通信或老旧设备的通信。
停止位的作用
- 数据帧结束标识: 停止位标识数据帧的结束位置,使接收端能够正确地分离和处理每个数据帧。
- 传输同步: 停止位提供了一段间隔,允许接收端进行时钟同步,确保接收数据的准确性。
五、局域网IP 和广域网IP
局域网(LAN)和广域网(WAN)是两种不同的网络类型,它们在 IP 地址的分配和使用上也有所不同。下面分别介绍这两种网络中的 IP 地址概念
局域网 IP 地址
局域网 IP 地址(LAN IP) 是指在局域网内部使用的 IP 地址。局域网通常指的是一个有限区域内的网络,例如家庭网络、办公室网络或学校网络等。局域网内的设备通过路由器或交换机连接在一起,通常使用私有 IP 地址来标识每个设备。
私有 IP 地址 是专门保留给局域网内部使用的 IP 地址范围,不能在公共互联网上直接访问。私有 IP 地址的范围包括:
- A 类: 10.0.0.0 到 10.255.255.255
- B 类: 172.16.0.0 到 172.31.255.255
- C 类: 192.168.0.0 到 192.168.255.255
这些地址可以在局域网内部自由使用,而不需要向互联网服务提供商(ISP)申请公网 IP 地址。局域网内部的设备通过 NAT(网络地址转换)技术与外部的广域网进行通信。
/// <summary>
/// 获取本机的局域网IP
/// </summary>
public static string LANIP
{
get
{
//获取本机的IP列表,IP列表中的第一项是局域网IP,第二项是广域网IP
IPAddress[] addressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
//如果本机IP列表为空,则返回空字符串
if (addressList.Length < 1)
{
return "";
}
//返回本机的局域网IP
return addressList[0].ToString();
}
}
广域网 IP 地址
广域网 IP 地址(WAN IP) 是指在广域网中使用的 IP 地址,广域网通常指的是覆盖较大地理区域的网络,例如互联网。广域网 IP 地址是全球唯一的,用于识别互联网上的每个设备或网络。
公网 IP 地址 是分配给广域网中的设备或网络的 IP 地址。与私有 IP 地址不同,公网 IP 地址是唯一的,并且在全球范围内可见。公网 IP 地址是由互联网服务提供商(ISP)分配给用户的,通常用于访问互联网和提供在线服务。
区别与联系
-
地址范围:
- 局域网使用私有 IP 地址,这些地址在局域网内部有效,不可直接在互联网上使用。
- 广域网使用公网 IP 地址,这些地址在互联网上唯一,允许设备在全球范围内相互通信。
-
NAT(网络地址转换):
- 在局域网内部,设备使用私有 IP 地址进行通信。路由器会使用 NAT 技术将局域网内的私有 IP 地址转换为公网 IP 地址,从而允许局域网内部设备通过一个公网 IP 地址访问互联网。
-
访问方式:
- 局域网 IP 地址通常用于设备之间的内部通信,如家庭网络或企业网络。
- 广域网 IP 地址用于设备与外部网络之间的通信,如访问网站或与其他网络设备进行数据交换。
-
安全性:
- 私有 IP 地址只能在局域网内部访问,因此相对更安全。
- 公网 IP 地址暴露在互联网上,可能会受到更多的安全威胁,因此需要额外的安全措施如防火墙和入侵检测系统。
/// <summary>
/// 获取本机在Internet网络的广域网IP
/// </summary>
public static string WANIP
{
get
{
//获取本机的IP列表,IP列表中的第一项是局域网IP,第二项是广域网IP
IPAddress[] addressList = Dns.GetHostEntry(Dns.GetHostName()).AddressList;
//如果本机IP列表小于2,则返回空字符串
if (addressList.Length < 2)
{
return "";
}
//返回本机的广域网IP
return addressList[1].ToString();
}
}