如果我们要处理客户端的每一条消息,针对与上面的粘包的问题,我们无法处理消息的,我们不知道消息到底有多长(虽说我们的案例是一样的,但实际开发中可能是不一样的),那么我们就要涉及到分包的问题了。
在上一篇的文章中我们可以看到一篇文章被接受了多次才接受完,加入客户端一下发了多篇文章?那么我们怎么才能把每一篇文章分出来呢?这就涉及到了分包。
我们在客户端每次发消息的时候我们都要将发送信息的长度加到消息的前面。由于TCP消息都是按顺序到达的,那么我们在读消息的时候,先读出消息长度(len),然后从消息长度后开始读取len个字节,那么这len个字节就是我们要的消息。
对前面的代码进行修改如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace ASYNServer
{
class Server
{
private TcpListener tcpListener;
byte[] buffer = new byte[512];
/// <summary>
/// 接受消息
/// </summary>
List<byte> receiveDataList = new List<byte>();
public Server()
{
tcpListener = new TcpListener(new IPEndPoint(IPAddress.Parse("127.0.0.1"),10001));
//开启侦听,最多只能连接20个客户端数目
tcpListener.Start(20);
tcpListener.BeginAcceptSocket(ClientConnect, tcpListener);
}
/// <summary>
/// 客户端连接
/// </summary>
/// <param name="ar"></param>
private void ClientConnect(IAsyncResult ar)
{
try
{
TcpListener listener = (TcpListener)ar.AsyncState;
Socket client = listener.EndAcceptSocket(ar);
Console.WriteLine(client.RemoteEndPoint.ToString() + "连接成功");
//侦听结束后开始接收数据
ReceiveData(client);
//重新侦听是否有新的客户端连接
//如果这里不写 那么只能侦听一次
tcpListener.BeginAcceptSocket(ClientConnect, tcpListener);
}
catch (Exception e)
{
Console.WriteLine("ClientConnect"+e.Message);
}
}
/// <summary>
/// 接受数据的回调
/// </summary>
/// <param name="client"></param>
private void ReceiveData(Socket client)
{
SocketError socketError;
client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, out socketError, ReceiveCallback, client);
}
/// <summary>
/// 一次接收的数据的长度
/// </summary>
int receLen = 0;
private void ReceiveCallback(IAsyncResult ar)
{
try
{
SocketError socketError;
Socket client = (Socket)ar.AsyncState;
receLen = client.EndReceive(ar, out socketError);
if (receLen>0)
{
lock (receiveDataList)
{
//处理接收到的数据
OnReceiveData(client);
}
}else//当receLen<=0的时候表示客户端断开和服务器的连接
{
Console.WriteLine(client.LocalEndPoint.ToString()+"关闭了");
}
//重新接受数据
ReceiveData(client);
}
catch (Exception e)
{
Console.WriteLine("ReceiveCallback:" + e.Message + " "+e.StackTrace);
}
}
/// <summary>
/// 接受数据
/// </summary>
/// <param name="socket"></param>
private void OnReceiveData(Socket socket)
{
byte[] tempArr = new byte[receLen];
Array.Copy(buffer, tempArr, receLen);
receiveDataList.AddRange(tempArr);
if (receiveDataList.Count>=4)
{
byte[] dataArray = receiveDataList.GetRange(0, 4).ToArray();
int len = BitConverter.ToInt32(dataArray, 0);
if (receiveDataList.Count>=len+4)
{
receiveDataList.RemoveRange(0, 4);
dataArray = receiveDataList.GetRange(0, len).ToArray();
string message = Encoding.UTF8.GetString(dataArray, 0, len);
receiveDataList.RemoveRange(0, len);
Console.WriteLine("来自客户端的数据是:" + message);
Console.WriteLine();
}
}
}
/// <summary>
/// 发送数据
/// </summary>
/// <param name="socket"></param>
/// <param name="data"></param>
public void SendData(Socket socket, byte[] data)
{
SocketError socketError;
socket.BeginSend(data, 0, data.Length, SocketFlags.None, out socketError, SendCallBack, socket);//异步发送数据
}
/// <summary>
/// 发送数据的回调
/// </summary>
/// <param name="ar"></param>
private void SendCallBack(IAsyncResult ar)
{
SocketError socketError;
Socket client = (Socket)ar.AsyncState;
client.EndSend(ar, out socketError);
}
}
}
客户端代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
namespace ASYNClient
{
class Client
{
private TcpClient tcpclient = null;
byte[] result;
public Client()
{
tcpclient = new TcpClient();
//连接服务器
tcpclient.BeginConnect(IPAddress.Parse("127.0.0.1"), 10001, ConnectCallback, tcpclient);
}
/// <summary>
/// 连接回调
/// </summary>
/// <param name="ar"></param>
private void ConnectCallback(IAsyncResult ar)
{
TcpClient client = (TcpClient)ar.AsyncState;
try
{
if (client.Connected)
{
//连接成功
client.EndConnect(ar);
//开始接受数据
ReceiveData(client);
}
}
catch (Exception e)
{
Console.WriteLine("ConnectCallback:"+e.Message);
}
}
/// <summary>
/// 接收数据
/// </summary>
/// <param name="client"></param>
private void ReceiveData(TcpClient client)
{
NetworkStream stream = client.GetStream();
result = new byte[client.Available];
try
{
stream.BeginRead(result, 0, result.Length, ReadCallBack, stream);
}
catch (Exception e)
{
Console.WriteLine("ReceiveData:"+e.Message );
}
}
/// <summary>
/// 接受消息回调
/// </summary>
/// <param name="ar"></param>
private void ReadCallBack(IAsyncResult ar)
{
NetworkStream stream;
try
{
stream = (NetworkStream)ar.AsyncState;
stream.EndRead(ar);
}
catch (IOException e)
{
Console.WriteLine("远程服务器关闭");
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="data"></param>
public void SendData(byte[] data)
{
if (tcpclient.Connected)
{
NetworkStream stream = tcpclient.GetStream();
stream.BeginWrite(data,0,data.Length, SendCallback, stream);
}
}
/// <summary>
/// 发送消息回调
/// </summary>
/// <param name="ar"></param>
private void SendCallback(IAsyncResult ar)
{
NetworkStream stream = (NetworkStream)ar.AsyncState;
stream.EndWrite(ar);
}
}
}
客户端测试代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ASYNClient
{
class Program
{
static void Main(string[] args)
{
Client client = new Client();
//等待客户端连接服务器后我们按下回车键模拟客户端一次性发送多条数据
Console.ReadLine();
for (int i = 0; i < 3; i++)
{
//string message = "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
string message = "燕子去了,有再来的时候;杨柳枯了,有再青的时候;桃花谢了,有再开的时 候。但是,聪明的,你告诉我,我们的日子为什么一去不复返呢?——是有人偷了他 们罢:那是谁?又藏在何处呢?是他们自己逃走了罢——如今又到了哪里呢?我不知道他们给了我多少日子,但我的手确乎(1)是渐渐空虚(2)了。在默默里算着,八千多日子已经从我手中溜去,像针尖上一滴水滴在大海里,我的日子滴在时间的流里,没有声音,也没有影子。我不禁头涔涔(3)而泪潸潸(4)了。去的尽管去了,来的尽管来着;去来的中间,又怎样地匆匆呢?早上我起来的时候,小屋里射进两三方斜斜的太阳。太阳他有脚啊,轻轻悄悄地挪移了;我也茫茫然跟着旋转。于是——洗手的时候,日子从水盆里过去;吃饭的时候,日子从饭碗里过去;默默时,便从凝然的双眼前过去。我觉察他去的匆匆了,伸出手遮挽时,他又从遮挽着的手边过去,天黑时,我躺在床上,他便伶伶俐俐(5)地从我身上跨过,从我脚边飞去了。等我睁开眼和太阳再见,这算又溜走了一日。我掩着面叹息。但是新来的日子的影儿又开始在叹息里闪过了。在逃去如飞的日子里,在千门万户的世界里的我能做些什么呢?只有徘徊(6)罢了(7),只有匆匆罢了;在八千多日的匆匆里,除徘徊外,又剩些什么呢?过去的日子如轻烟,被微风吹散了,如薄雾,被初阳蒸融了;我留着些什么痕迹呢?我何曾留着像游丝(8)样的痕迹呢?我赤裸裸来到这世界,转眼间也将赤裸裸的回去罢?但不能平的,为什么偏要白白走这一遭啊?你聪明的,告诉我,我们的日子为什么一去不复返呢?";
byte[] messageData = Encoding.UTF8.GetBytes(message);
Console.WriteLine("messageData:"+ messageData.Length);
byte[] lenData = BitConverter.GetBytes(messageData.Length);
byte[] data = new byte[lenData.Length+messageData.Length];
Array.Copy(lenData, data, lenData.Length);
Array.Copy(messageData,0,data,lenData.Length,messageData.Length);
client.SendData(data);
}
Console.ReadLine();
}
}
}