最近在研究网络同步,感觉很有意思。服务端与客户端都是用c#写的。很遗憾,现在的项目服务端都是用c++或者java写的。可能c#写出的效率不行?
socket通信协议我们通过知道的两种。一种是TCP协议,一种是UDP协议。TCP是以传输数据稳定而夺得游戏开发者的垂爱。UDP因为容易掉数据导致对于精准数据的传输方面被坐冷板凳,但是UDP的优点是传输速度快,适用于工业展示类数据传输。丢几个数据无所谓,反正是要对整体数据走势分析。
通常我们使用TCP协议。而TCP传输过程中会出现粘包分包问题。
粘包:
当我们高频率传输一些小的数据时,TCP协议会自动将这些小的数据打包在一起发送出去,产生了所谓的粘包问题。这是TCP的内部优化。
分包:
当我们一次发送大量数据时,TCP会自动将数据切分多块然后分批次发送。
解决方法:我们在每次发送数据时,都要加上数据头。通常我们设定数据头为int32类型,占四个字节。数据头的内容表示我们发送的数据占了多少字节。这样我们对于粘包的数据,可以根据数据头对数据进行一步一步的解析读取。
下面是Socket通信的代码部分:
首先我们需要创建一个message类来处理接收到的数据:
数据处理帮助类:
public class Messgae
{
//用来存储接收到消息的数据
private byte[] data=new byte[1024];
//开始往数组里添加数据的索引 因为可能存在分包的问题 所以startIndex不一定都是0
private int startIndex = 0;
public byte[] Data { get { return data; } }
public int StartIndex { get { return startIndex; } }
public int RemainSize { get { return data.Length - startIndex; } }
public void ReadMessage(int newDataAmount,Action ProcessCallback )
{
startIndex += newDataAmount;
while (true)
{
if(startIndex<=4) return;
int count = BitConverter.ToInt32(data, 0);
//有可能存在粘包 需要根据数据头来解析数据
if (startIndex - 4 >= count)
{
//TODO 解析数据
ActionCode actionCode = (ActionCode)BitConverter.ToInt32(data, 4);
Debug.Log(actionCode);
string s = Encoding.UTF8.GetString(data, 8, count - 4);
Debug.Log(s);
ProcessCallback(actionCode,s);//解析完一段数据 就去调用回调函数来解析我们获取到的数据
Array.Copy(data,count+4,data,0,startIndex-4-count);//这里要将已经解析的数据从存储数组中去除 开始下一段数据的解析
startIndex -= (count + 4);
}
}
}
客户端:
public class ClientManager : BaseManager
{
private static string IP = "";
private static int PORT = 8888;
private Socket clientSocket;
Messgae message=new Messgae();
void StartClientSocket()
{
clientSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipEndPoint=new IPEndPoint(int.Parse(IP),PORT);
try
{
clientSocket.Connect(ipEndPoint);
StartReceieve();
}
catch (Exception e)
{
Debug.Log("无法连接到服务器!"+e);
}
}
void StartReceieve()
{
//开始异步接收服务端发送过来的消息 我们需要创建一个message类来处理我们读取到的数据
clientSocket.BeginReceive(message.Data, message.StartIndex, message.RemainSize, SocketFlags.None,
ReceieveCallBack, null);
}
private void ReceieveCallBack(IAsyncResult ar )
{
try
{
int count = clientSocket.EndReceive(ar);
message.ReadMessage(count);//此处我们创建一个message类来处理我们读取到的数据
StartReceieve();
}
catch (Exception e)
{
Debug.Log("客户端接收消息错误"+e);
}
}
//发送数据给服务端
private void Send(byte[] data)
{
clientSocket.Send(data);
}
服务端:
class Server
{
private Socket serverSocket;
private IPEndPoint ipEndPoint;
private List clientList=new List();
private ControllerManager controllerMgr;
public Server() { }
public Server(string ip, int port)
{
controllerMgr=new ControllerManager(this);
ipEndPoint = new IPEndPoint(IPAddress.Parse(ip), port);
}
public void Start()
{
serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
serverSocket.Bind(ipEndPoint);
serverSocket.Listen(0);//0代表不限制连接服务器的客户端的数量
serverSocket.BeginAccept(AcceptCallBack, null);//AcceptCallback代表接收到客户端来的消息后的回调函数
}
private void AcceptCallBack(IAsyncResult ar)
{
Socket clientSocket = serverSocket.EndAccept(ar);
Client client = new Client(clientSocket, this);//这里我们创建一个Client类来专门管理连接到服务器的客户端
clientList.Add(client);
client.Start();
serverSocket.BeginAccept(AcceptCallBack, null);
}
}
在服务器端用来管理连接服务器的客户端的Client类:
class Client
{
private Socket clientSocket;
private Server server;
private Message message = new Message();
public Client(Socket clientSocket, Server server)
{
this.clientSocket = clientSocket;
this.server = server;
}
public void Start()
{
//这里用来中断客户端的断连 不然服务端要一直报错
if(clientSocket==null||clientSocket.Connected==false) return;
clientSocket.BeginReceive(message.Data, message.StartIndex, message.RemainSize, SocketFlags.None,
ReceieveCallback, null);
}
private void ReceieveCallback(IAsyncResult ar)
{
try
{
//这里用来中断客户端的断连 不然服务端要一直报错
if (clientSocket == null || clientSocket.Connected == false) return;
int count = clientSocket.EndReceive(ar);
if (count == 0)
{
Close();
}
Start();
}
catch (Exception e)
{
Console.WriteLine("解析客户端错误!" + e);
}
}
public void Send(byte[] bytes)
{
clientSocket.Send(bytes);
}
private void Close()
{
if (clientSocket != null)
clientSocket.Close();
}
}
在项目架构上,我们通常不会只发送头数据加字符串数据,根据项目需要我们也要加上我们的请求模块和响应模块。希望本博客能帮到你!