本章主要处理粘包分包问题。
一 为什么要处理粘包分包
由于TCP协议本身的机制,客户端与服务器会维持一个连续发送的数据。如果发送的网络数据包太小,TCP会合并较小的数据包再发送,接收端便无法区分那些数据是发送端分开的,因此便产生了粘包问题。如果数据太大,TCP有可能会把数据拆成多分发送,接收端一次只能接收到部分信息,因此便出现分包问题。
二 怎么处理粘包分包
处理粘包分包问题的一种方法,便是在发送数据时,给数据加上长度信息。每次接收到数据后,先读取长度信息,如果缓冲区的数据长度大于要提取的字节数,则取出相应的字节,然后更新缓冲区,否则等待下一次数据接收。
三 业务逻辑的实现
(1) 服务器端
Conn类中添加字段
//处理池
public byte[] lenBytes=new byte[sizeof(Int32)];
/// <summary>
/// 消息长度
/// </summary>
public int msgLength=0;
新建Message.cs处理粘包分包
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using TCP_Sever.Conns;
using TCP_Sever.Timer;
using System.Net;
using System.Net.Sockets;
namespace TCP_Sever.Messages
{
/// <summary>
/// 处理消息的接收与发送
/// </summary>
class Message
{
/// <summary>
/// 接收消息
/// </summary>
/// <param name="conn"></param>
/// <param name="count"></param>
public static void Recv_Message(Conn conn,int count)
{
if(count<=0)
{
Console.WriteLine("收到:" + conn.Get_ClientAddress() + "断开连接");
conn.Client_Close();
return;
}
conn.count += count;
ProcessData(conn);
}
/// <summary>
/// 处理粘包分包
/// </summary>
/// <param name="conn"></param>
private static void ProcessData(Conn conn)
{
if (conn.count < sizeof(Int32))
return;
Array.Copy(conn.ReadBuff, conn.lenBytes, sizeof(Int32));
conn.msgLength = BitConverter.ToInt32(conn.lenBytes, 0);
if (conn.count < conn.msgLength + sizeof(Int32))
{
return;
}
//处理消息
string str = System.Text.Encoding.UTF8.GetString(conn.ReadBuff, sizeof(Int32), conn.msgLength);
Console.WriteLine("收到消息" + str);
if (str == "HeatBeat")
{
conn.last_HeartTime = HeartbeatTime.GetTimeStamp();
Console.WriteLine("更新时间" + conn.last_HeartTime);
}
int count = conn.count - sizeof(Int32) - conn.msgLength;
Array.Copy(conn.ReadBuff, sizeof(Int32) + conn.msgLength, conn.ReadBuff, 0, count);
conn.count = count;
if (count > 0)
{
ProcessData(conn);
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="conn"></param>
public static void Send_Message(string str,Conn conn)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
byte[] length = BitConverter.GetBytes(bytes.Length);
byte[] sendbuff = length.Concat(bytes).ToArray();
try
{
//异步发送
conn.Client_Socket.BeginSend(sendbuff, 0, sendbuff.Length, SocketFlags.None, null, null);
}
catch (Exception e)
{
Console.WriteLine("发送消息失败" + e.Message);
}
}
}
}
Sever类中,修改AsynReceiveCallBack函数
private static void AsynReceiveCallBack(IAsyncResult ar)
{
Conn conn = (Conn)ar.AsyncState;
lock (conn)
{
try
{
int count= conn.Client_Socket.EndReceive(ar);
Message.Recv_Message(conn, count);
for (int i = 0; i < Conn_Helper.Conns.Length; i++)
{
if (!Conn_Helper.Conns[i].isUse)continue;
AsynSend(Conn_Helper.Conns[i]);
}
//继续接收,实现循环
conn.Client_Socket.BeginReceive(conn.ReadBuff,conn.count ,conn.Buff_Remain(), SocketFlags.None, AsynReceiveCallBack, conn);
}
catch (Exception e)
{
Console.WriteLine("客户端断开:" + e.Message);
conn.Client_Close();
}
}
}
Sever类中,修改AsynSend函数
public static void AsynSend(Conn conn)
{
if (sever_socket == null)
{
Console.WriteLine("请先开启服务器");
return;
}
Message.Send_Message("客户端你好",conn);
}
(2) 客户端
新建Message.cs处理粘包分包
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
using System.Net;
using System.Net.Sockets;
namespace YCX_Tool.Client.messages
{
class Message
{
static int buffCount = 0;
static byte[] lenBytes = new byte[sizeof(Int32)];
static Int32 msgLength = 0;
/// <summary>
/// 接收
/// </summary>
/// <param name="count"></param>
/// <param name="readBuff"></param>
public static void Recv_Message(int count,byte[] readBuff)
{
buffCount += count;
ProcessData(readBuff);
}
private static void ProcessData(byte[] readBuff)
{
if (buffCount < sizeof(Int32))
return;
Array.Copy(readBuff, lenBytes, sizeof(Int32));
msgLength = BitConverter.ToInt32(lenBytes, 0);
if (buffCount < msgLength + sizeof(Int32))
return;
string str = System.Text.Encoding.UTF8.GetString(readBuff, sizeof(Int32), (int)msgLength);
Debug.Log("接收到信息:" + str);
//清除已处理的信息
int count = buffCount - msgLength - sizeof(Int32);
Array.Copy(readBuff, msgLength+sizeof(Int32), readBuff, 0, count);
buffCount = count;
if (count > 0)
{
ProcessData(readBuff);
}
}
public static void Send(string str,Socket socket)
{
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(str);
byte[] length = BitConverter.GetBytes(bytes.Length);
byte[] sendbuff = length.Concat(bytes).ToArray();
try
{
//异步发送
socket.BeginSend(sendbuff, 0, sendbuff.Length, SocketFlags.None, null, null);
}
catch (Exception e)
{
Debug.Log("发送消息失败" + e.Message);
}
}
}
}
Client类
修改AsynReceiveCallBack函数
private static void AsynReceiveCallBack(IAsyncResult ar)
{
try
{
//获得客户端套接字
int count = client_socket.EndReceive(ar);
Message.Recv_Message(count, readBuff);
//继续接收,实现循环
client_socket.BeginReceive(readBuff, 0, 1024, SocketFlags.None, AsynReceiveCallBack, null);
}
catch (Exception e)
{
Debug.Log("服务器断开:" + e.Message);
}
}
修改AsynSend函数
public static void AsynSend()
{
//客户端接入
if (client_socket == null)
{
Debug.Log("请先开启服务器");
return;
}
Message.Send("服务端你好",client_socket);
}