在之前的代码中一个C# .net TcpClient实现的全双工通讯协议_tcpclient协议_青柠檬676的博客-CSDN博客
我们实现了TCP的心跳加上全双工通讯协议的介绍,今天将带大家了解什么是TCP沾包和TCP如何防止沾包
那么我们长话短说
TCP为什么会沾包?
默认情况下, TCP 连接会启⽤延迟传送算法 (Nagle 算法), 在数据发送之前缓存他们. 如果短时间有多个数据发送, 会缓冲到⼀起作⼀次发送 (缓冲⼤⼩⻅ socket.bufferSize ), 这样可以减少 IO 消耗提⾼性能.
我是如何处理TCP的沾包问题的?
为了处理问题,我们需要在TCP每次通话时加上一个包头,包头代表着他的字节数,因为包头是整型的,所以通常是4字节的一个包,这时读取的时候就可以知道我该读多少字节了,那么理论存在,实践开始.
服务端代码
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
class Program
{
static List<TcpClient> clients = new List<TcpClient>();
static void Main(string[] args)
{
TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8080);
listener.Start();
Console.WriteLine("服务器已启动,等待客户端连接...");
Timer timer = new Timer(SendHelloMessage, null, TimeSpan.Zero, TimeSpan.FromSeconds(20));
while (true)
{
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("客户端已连接:{0}", client.Client.RemoteEndPoint);
clients.Add(client);
for (int i = 0; i < 1000; i++)
{
// 向客户端发送数据,包括包头
SendDataWithHeader(client, "柠檬19928" + i);
Console.WriteLine("发送一次");
}
// 启动线程,接收客户端消息
ThreadPool.QueueUserWorkItem(ReceiveMessage, client);
}
}
/// <summary>
/// 发送数据
/// </summary>
/// <param name="client"></param>
/// <param name="message"></param>
static void SendDataWithHeader(TcpClient client, string message)
{
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
byte[] header = BitConverter.GetBytes(messageBytes.Length);
byte[] data = new byte[header.Length + messageBytes.Length];
Array.Copy(header, 0, data, 0, header.Length);
Array.Copy(messageBytes, 0, data, header.Length, messageBytes.Length);
NetworkStream stream = client.GetStream();
stream.Write(data, 0, data.Length);
}
/// <summary>
/// 发送一个Hello的方法
/// </summary>
/// <param name="state"></param>
static void SendHelloMessage(object state)
{
foreach (TcpClient client in clients)
{
try
{
SendDataWithHeader(client, "Hello!");
}
catch (Exception ex)
{
Console.WriteLine("向客户端 {0} 发送消息失败:{1}", client.Client.RemoteEndPoint, ex.Message);
clients.Remove(client);
}
}
}
/// <summary>
/// 读取客户端信息
/// </summary>
/// <param name="state"></param>
static void ReceiveMessage(object state)
{
TcpClient client = (TcpClient)state;
NetworkStream stream = client.GetStream();
while (true)
{
// 读取包头,获取消息的大小
int messageSize = ReadHeader(stream);
// 读取消息内容
byte[] buffer = new byte[messageSize];
int bytesRead = ReadExactly(stream, buffer, 0, messageSize);
if (bytesRead < messageSize)
{
Console.WriteLine("无法读取消息,可能是连接断开:{0}", client.Client.RemoteEndPoint);
clients.Remove(client);
break;
}
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("收到客户端消息:{0}", message);
if (message == "keepAlive")
{
// 客户端发送心跳指令,关闭连接
Console.WriteLine("客户端发送了心跳指令,关闭连接:{0}", client.Client.RemoteEndPoint);
clients.Remove(client);
client.Close();
break;
}
}
}
/// <summary>
/// 解析包头里面有多少字节
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
static int ReadHeader(NetworkStream stream)
{
byte[] header = new byte[4];
int headerBytesRead = stream.Read(header, 0, 4);
if (headerBytesRead < 4)
{
Console.WriteLine("无法读取包头,可能是连接断开");
return -1;
}
return BitConverter.ToInt32(header, 0);
}
/// <summary>
/// 等待流全部获取到,然后丢出去
/// </summary>
/// <param name="stream"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
/// <returns></returns>
static int ReadExactly(NetworkStream stream, byte[] buffer, int offset, int count)
{
int bytesRead = 0;
while (bytesRead < count)
{
int result = stream.Read(buffer, offset + bytesRead, count - bytesRead);
if (result == 0)
{
// 连接断开或遇到流的末尾
return bytesRead;
}
bytesRead += result;
}
return bytesRead;
}
}
客户端代码
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
class Program
{
static void Main(string[] args)
{
TcpClient client = new TcpClient("127.0.0.1", 8080);
byte[] buffer = new byte[1024];
NetworkStream stream = client.GetStream();
Task.Factory.StartNew(() => { Write(stream); });
ThreadPool.QueueUserWorkItem(SendInstruction, client);
while (true)
{
string sendMsg = Console.ReadLine();
SendDataWithHeader(stream, sendMsg);
}
}
/// <summary>
/// 读取服务端信息
/// </summary>
/// <param name="stream"></param>
static void Write(NetworkStream stream)
{
int i = 0;
while (true)
{
// 读取包头,获取消息的大小
int messageSize = ReadHeader(stream);
if (messageSize == -1)
{
Console.WriteLine("与服务端连接断开");
break;
}
// 读取消息内容
byte[] buffer = new byte[messageSize];
int bytesRead = ReadExactly(stream, buffer, 0, messageSize);
if (bytesRead < messageSize)
{
Console.WriteLine("无法读取消息,可能是连接断开");
break;
}
string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
Console.WriteLine("收到服务器消息:{0}", message);
i += 1;
SendDataWithHeader(stream, "你好 服务端,我是客户端小包" + i + "我收到了" + messageSize + "条数据");
}
}
/// <summary>
/// 发送信息给服务端
/// </summary>
/// <param name="stream"></param>
/// <param name="message"></param>
static void SendDataWithHeader(NetworkStream stream, string message)
{
byte[] messageBytes = Encoding.UTF8.GetBytes(message);
byte[] header = BitConverter.GetBytes(messageBytes.Length);
byte[] data = new byte[header.Length + messageBytes.Length];
Array.Copy(header, 0, data, 0, header.Length);
Array.Copy(messageBytes, 0, data, header.Length, messageBytes.Length);
stream.Write(data, 0, data.Length);
}
/// <summary>
/// 发送心跳
/// </summary>
/// <param name="state"></param>
static void SendInstruction(object state)
{
TcpClient client = (TcpClient)state;
NetworkStream stream = client.GetStream();
while (true)
{
// 每隔2分钟向服务端发送一条指令
Thread.Sleep(122000);
SendDataWithHeader(stream, "keepAlive");
Console.WriteLine("发送心跳指令关闭连接");
}
}
/// <summary>
/// 解析包头中有多少字节
/// </summary>
/// <param name="stream"></param>
/// <returns></returns>
static int ReadHeader(NetworkStream stream)
{
byte[] header = new byte[4];
int headerBytesRead = stream.Read(header, 0, 4);
if (headerBytesRead < 4)
{
Console.WriteLine("无法读取包头,可能是连接断开");
return -1;
}
return BitConverter.ToInt32(header, 0);
}
/// <summary>
/// 等待流全部获取到,然后丢出去
/// </summary>
/// <param name="stream"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="count"></param>
/// <returns></returns>
static int ReadExactly(NetworkStream stream, byte[] buffer, int offset, int count)
{
int bytesRead = 0;
while (bytesRead < count)
{
int result = stream.Read(buffer, offset + bytesRead, count - bytesRead);
if (result == 0)
{
// 连接断开或遇到流的末尾
return bytesRead;
}
bytesRead += result;
}
return bytesRead;
}
}