C#实现Socket数据接收的三种经典方式
作者:小码编匠
前言
在工业自动化、物联网(IoT)和网络通信等领域,Socket 编程是实现设备间数据交互的核心技术。C# 作为 .NET 平台的主要开发语言,提供强大的网络编程支持。然而,在实际开发中,很多开发在使用 Socket.Receive 方法接收数据时,常常遇到数据丢失、接收延迟、性能瓶颈等问题。
本文将深入探讨 C# 中实现 Socket 数据接收的三种经典方式,结合真实案例分析常见问题,并提供优化后的完整代码示例,帮助大家开发稳定、高效的网络通信系统。
问题背景
在传统的 Socket 数据接收方式中,通常采用如下代码:
int recv;//定义接收数据长度变量
IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
//接收端所监听的接口,ip也可以用IPAddress.Any
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//初始化一个Socket对象
socket.Bind(ipEnd);
//绑定套接字到一个IP地址和一个端口上(bind());
socket.Listen(10);
while (true)
{
byte[] data = new byte[1024];//对data清零
Socket clientSocket = socket.Accept();
//一旦接受连接,创建一个客户端
recv = clientSocket.Receive(data);
if (recv == 0)
//如果收到的数据长度小于0,则退出
break;
string stringData = "0x" + BitConverter.ToString(data).Replace("-", " 0x").ToLower();
this.Invoke((EventHandler)delegate
{
richTextBox1.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss") + stringData + "\n";
});
}
这种写法在简单场景下看似可行,但在处理高频、小包数据(如金属门每10秒发送一次数据)时,会出现严重的数据积压和延迟——原本应60秒接收6组数据,却可能60秒才收到一组,导致数据丢失。
根本原因在于:Receive 方法被放在 Accept 之后的单次调用中,未能持续监听和接收数据流。
三种高效的数据接收方式
为解决上述问题,以下介绍三种经过验证的 C# Socket 数据接收方案。
方式一:基于NetworkStream.Read的持续监听
使用 TcpClient 和 NetworkStream,通过 Stream.Read 方法实现阻塞式持续读取。
private void BGWorker_DoWork()
{
var serverIPEndPoint = new IPEndPoint(IPAddress.Parse("192.168.1.99"), 8234); // 当前服务器使用的ip和端口
TcpListener tcpListener = new TcpListener(serverIPEndPoint);
tcpListener.Start();
Console.WriteLine("服务端已启用......"); // 阻塞线程的执行,直到一个客户端连接
tcpClient = tcpListener.AcceptTcpClient();
Console.WriteLine("已连接.");
stream = tcpClient.GetStream(); // 创建用于发送和接受数据的NetworkStream
var t1 = new Thread(ReceiveMsg);
t1.IsBackground = true;
t1.Start();
}
void ReceiveMsg()
{
byte[] buffer = new byte[1024]; // 预设最大接受1024个字节长度,可修改
int count = 0;
try
{
while ((count = stream.Read(buffer, 0, buffer.Length)) != 0)
{
string stringData = "0x" + BitConverter.ToString(buffer, 0, count).Replace("-", " 0x").ToLower();
Console.WriteLine($"{tcpClient.Client.LocalEndPoint.ToString()}:{DateTime.Now.ToString("yy-MM-dd hh:mm:ss -*- ") + stringData + "\n"}");
this.Invoke((EventHandler)delegate
{
richTextBox1.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss -*- ") + stringData + "\n";
});
}
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
优点:代码简洁,NetworkStream.Read 自动处理流的持续读取。
适用场景:单客户端通信,如设备与上位机一对一连接。
方式二:多线程处理多个客户端连接
为每个客户端连接创建独立线程,确保接收不阻塞主线程。
private void BGWorker_DoWork1()
{
int recv;
IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ipEnd);
socket.Listen(10);
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start(socket);
}
void Listen(object o)
{
try
{
Socket socketWatch = o as Socket;
while (true)
{
socketSend = socketWatch.Accept();
Thread r_thread = new Thread(Received);
r_thread.IsBackground = true;
r_thread.Start(socketSend);
}
}
catch { }
}
void Received(object o)
{
try
{
Socket socketSend = o as Socket;
while (true)
{
byte[] buffer = new byte[1024 * 1024 * 3];
int len = socketSend.Receive(buffer);
if (len == 0)
{
break;
}
string stringData = "0x" + BitConverter.ToString(buffer, 0, len).Replace("-", " 0x").ToLower();
this.Invoke((EventHandler)delegate
{
richTextBox1.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss -*- ") + stringData + "\n";
});
}
}
catch { }
}
优点:支持多客户端并发连接,接收独立,互不干扰。
适用场景:多设备接入的服务器端程序。
方式三:Task + Background Thread 实现异步接收
结合 Task.Run 和后台线程,实现轻量级异步接收,避免线程资源浪费。
private void BGWorker_DoWork2()
{
int recv;
IPEndPoint ipEnd = new IPEndPoint(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(ipEnd);
socket.Listen(10);
new Thread(delegate ()
{
Socket clientSocket = null;
while (true)
{
Stopwatch sw = new Stopwatch();
sw.Start();
clientSocket = socket.Accept();
Task.Run(() =>
{
while (true)
{
byte[] data = new byte[50];
recv = clientSocket.Receive(data, 0, data.Length, SocketFlags.None);
string stringData = "0x" + BitConverter.ToString(data, 0, recv).Replace("-", " 0x").ToLower();
this.Invoke((EventHandler)delegate
{
richTextBox1.Text += DateTime.Now.ToString("yy-MM-dd hh:mm:ss -*- ") + stringData + "\n";
});
sw.Stop();
long times = sw.ElapsedMilliseconds;
this.Invoke((EventHandler)delegate
{
richTextBox1.Text += "执行查询总共使用了" + times + "毫秒" + "\n";
});
}
});
}
})
{ IsBackground = true }.Start();
}
优点:异步非阻塞,资源利用率高,适合高并发场景。
适用场景:需要高性能、低延迟的工业通信系统。
总结
本文通过一个真实的数据接收延迟案例,揭示了传统 Socket.Receive 使用方式的局限性,并提供了三种经过实践验证的优化方案:
1、NetworkStream.Read:适合简单、稳定的单连接场景,代码简洁。
2、多线程 Accept + Receive:适合多客户端并发接入,结构清晰。
3、Task + Thread 异步模式:适合高性能、高并发系统,资源利用更优。
选择哪种方式,取决于具体的应用场景和性能需求。
无论哪种方式,核心原则是:必须将数据接收逻辑置于持续监听的循环中,避免一次性调用导致的数据丢失。
另外,结合 Stopwatch 进行性能监控、使用 Invoke 安全更新 UI、合理设置缓冲区大小,都是构建稳定 Socket 通信系统的关键。
到此这篇关于C#实现Socket数据接收的三种经典方式的文章就介绍到这了,更多相关C# Socket数据接收内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
