不明白Socket通信原理的同学可以参考该文章:https://www.cnblogs.com/zxtceq/p/7728543.html点击打开链接
具体代码的编写参考该博客:https://www.cnblogs.com/sdyinfang/p/5519708.html点击打开链接
粘包和分包:
Socket通信时会对发送的字节数据进行分包和粘包处理,属于一种Socket内部的优化机制。
1.粘包:当发送的字节数据包比较小且频繁发送时,Socket内部会将字节数据进行粘包处理,既将频繁发送的小字节数据打包成 一个整包进行发送,降低内存的消耗。
2.分包:当发送的字节数据包比较大时,Socket内部会将发送的字节数据进行分包处理,降低内存和性能的消耗。
解决方案:将发送的字节数据的长度与字节数据拼接后发送,每次读取字节数据时先读取其长度,然后再对接收到的字节数组进行拆分和拼接处理。
具体实现代码如下:(环境:Visual Studio 2017 C#.NET控制台程序)
服务器端代码:
using System;
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
namespace PhotoTransmission
{
class PhotoTransController
{
private Message m_msg = new Message();
/// <summary>
/// 异步
/// </summary>
public void StartServerAsync()
{
//开启服务器
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress iPAddress = IPAddress.Parse("127.0.0.1");
IPEndPoint iPEndPoint = new IPEndPoint(iPAddress, 1017);
serverSocket.Bind(iPEndPoint); //绑定ip和端口号
serverSocket.Listen(0); //服务器端的监听(0代表不限个数)
//异步接收客户端链接并发送消息
serverSocket.BeginAccept(AsyncAcceptCallBack, serverSocket);
//Process pro = new Process();
//ProcessStartInfo proStartInfo = new ProcessStartInfo("PhotoTransmission.exe");
//proStartInfo.WindowStyle = ProcessWindowStyle.Hidden;
//pro.StartInfo = proStartInfo;
//pro.Start();
Console.ReadKey();
}
private void AsyncAcceptCallBack(IAsyncResult ar)
{
Socket serverSocket = ar.AsyncState as Socket;
Socket client = serverSocket.EndAccept(ar);
//向客户端返回消息
SendClientData(client);
//异步接收消息(回调函数)
client.BeginReceive(m_msg.Data, m_msg.StartIndex, m_msg.Data.Length, SocketFlags.None, AsyncReceiveCallBack, client);
//继续等待下一个客户端连接(回调函数)
serverSocket.BeginAccept(AsyncAcceptCallBack, serverSocket);
}
private void AsyncReceiveCallBack(IAsyncResult ar)
{
Socket client = null;
try
{
client = ar.AsyncState as Socket;
int count = client.EndReceive(ar); //异步时使用EndReceive接收
if (count == 0) //服务器不会接收空消息
{
client.Close();
return;
}
m_msg.ReadMessage(count); //读取出第一条字节数据并删除
DataProcessing(m_msg.Type);
client.BeginReceive(m_msg.Data, m_msg.StartIndex, m_msg.RemainSize, SocketFlags.None, AsyncReceiveCallBack, client);
}
catch (Exception e)
{
Console.WriteLine(e);
if (client != null) client.Close();
}
}
/// <summary>
/// 根据type的不同进行不同的数据处理
/// </summary>
/// <param name="_type"></param>
private void DataProcessing(int _type)
{
switch(_type)
{
case 0:
break;
case 1:
break;
case 2:
break;
}
}
/// <summary>
/// 向客户端返回数据
/// </summary>
private void SendClientData(Socket _client)
{
string msgStr = "努力!!奋斗!!!";
byte[] data = System.Text.Encoding.UTF8.GetBytes(msgStr);
_client.Send(data);
}
}
}
Message类代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PhotoTransmission
{
class Message
{
private byte[] m_data = new byte[10240];
private int m_startIndex = 0;
private int m_count = 0; //为一条发送的字节数据的长度
private int m_type = 0; //表示该数据体是什么类型的数据体,0表示默认为byte数组
public int Count { get { return m_count; } }
public byte[] Data { get { return m_data; } }
public int Type { get { return m_type; } }
public int StartIndex { get { return m_startIndex; } }
public int RemainSize { get { return m_data.Length - m_count; } }
/// <summary>
/// 将字节数据与字节长度拼接
/// </summary>
/// <param name="_type">表示哪种数据类型</param>
/// <param name="_msg">数据体(json等等)</param>
/// <returns></returns>
public byte[] GetBytes(int _type, string _msg)
{
byte[] msg = System.Text.Encoding.UTF8.GetBytes(_msg);
int length = msg.Length; //字节长度
m_data = new byte[length]; //根据_msg数据的长度创建对应长度的byte数组
byte[] typeByte = BitConverter.GetBytes(_type);
byte[] headByte = BitConverter.GetBytes(length).Concat(typeByte).ToArray();
byte[] newMsg = headByte.Concat(msg).ToArray(); //将字节长度与msg拼接
return newMsg;
}
/// <summary>
/// 解析字节数据
/// </summary>
/// <returns></returns>
public void ReadMessage(int _count)
{
m_startIndex = 8;
m_count += _count;
UpdataMessage();
m_type = BitConverter.ToInt32(m_data, 4);
}
/// <summary>
/// 读取完成字节数据后删除第一条字节数据
/// </summary>
private void UpdataMessage()
{
while (true)
{
int count = BitConverter.ToInt32(m_data, 0);
Console.WriteLine(System.Text.Encoding.UTF8.GetString(m_data, m_startIndex, count));
m_count -= count + 4;
Array.Copy(m_data, count + 8, m_data, 0, m_count);
if (m_count < count + 4) break;
}
m_startIndex = m_count;
}
}
}
客户端代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net.Sockets;
using System.Net;
namespace TCPClient
{
class TCPClientController
{
private void TCPClientAsync()
{
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
try
{
client.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1017));
}
catch (Exception e)
{
Debug.Log(e);
return;
}
//从服务器接收消息
byte[] buffer = new byte[1024];
int size = client.Receive(buffer);
string msgReceive = System.Text.Encoding.UTF8.GetString(buffer, 0, size);
Debug.Log(msgReceive);
byte[] data = GetBytes(56,"客户端请求数据");
client.Send(data);
client.Close();
}
/// <summary>
/// 将字节数据与字节长度拼接
/// </summary>
/// <param name="_msg"></param>
/// <returns></returns>
public byte[] GetBytes(int _type, string _msg)
{
byte[] msg = System.Text.Encoding.UTF8.GetBytes(_msg);
int length = msg.Length; //字节长度
byte[] typeByte = BitConverter.GetBytes(_type);
byte[] headByte = BitConverter.GetBytes(length).Concat(typeByte).ToArray();
byte[] newMsg = headByte.Concat(msg).ToArray(); //将字节长度与msg拼接
return newMsg;
}
}
}