unity c# Tcp网络通讯
本篇附带源代码,带有处理拆包分包粘包,也会撰明具体内容。
首先对于tcp就是挥手机制,三次握手四次挥手机制。
一、三次握手
具体过程为简单解释为:
1、客户端请求服务器链接,等待服务器确认。(服务器如果确认请求,就会给这个客户端随机分配一个固定且唯一的ID号码)
2、服务器收到请求后,会向客户端发送应答,进行确认。(服务器会返回给你分配的id编码,代表下一次服务器希望收到以此为地址的固定的客户端序号,如果未来涉及断线重连这里也是非常重要的信息,要进行留存)
3、客户端收到信息后,会再次向服务器进行回复,发出确认。(就此服务器确认链接被确认,客户端服务器建立起链接)
【啰嗦一下:】
【握手为什么是三次?(防止已过期的链接请求报文突然又传输到服务器从而产生错误)、(三次才能确认双方接收和发送能力都正常)、(告知对方自己的初始序号值,并确认双方的序号值是否正确)以及(节省服务器性能【如果长时间服务器分配出id等待联接,会造成资源上的浪费,同时也可能被不法之人利用危害数据安全】)
为啥是三次不是四次?
已经可以确认链接了,无需第四次握手,浪费资源
更多可访问:详解 TCP 三次握手、四次挥手,附带精美图解和超高频面试题 - HYN的技术笔记 - SegmentFault 思否
】
二、四次挥手
即终止链接,这与tcp的机制有关,详细可看上面的链接。这里我们只做简略描述。
1、客户端发送关闭请求。
2、服务端收到后发回确认请求。
3、这个阶段下,服务器会发送最后的一些数据信息【这里面会有一些你没发完的一些数据信息,包括还未处理的内容信息】,最后会带一个结束的标识,代表服务器将这些没发完的内容发完了。
4、此时该服务器对此客户端的服务已经基本结束了,此时会发送最后的关闭信息。
5、客户端收到确认信息,再次发送确认信息,确认关闭,此时客户端自己就被关闭了,服务器在接受到指令后也就被关闭了。
这是基础内容,然后需要知道的是虽然tcp不会丢包,但是会有沾包的问题。
这个导致的问题有很多,可能是发送方造成的,也可能是接收方造成的。
1、发送方的tcp为提高传输效率,要收集到一定的数据后才可以发送,这就可能导致接受到原本是一大段的内容信息,可能是一段一段的。
2、mss规定,如果数据包过长就会被芬开传输,这样接收方就受到了粘包数据
3、接收方接收数据不及时,卡顿造成的
4、网络传输制式不统一【跨国业务中能遇到,甚至无法链接】
因此值得注意的是一般在unity开发过程中,并不太会使用tcp,一般在小游戏中往往会比较能够见到,相对来说kcp会更多,其次是http web【UnityWebRequest】(udp:往往信息传输的都是请求更新,更新完后的东西都会在本地处理一锤子买卖,干完就不认人【tps:有些境外赌博产品就是这么干的,id号就在本地保存,抓包一抓一个准】)
先上源代码吧,解析放在后面:
客户端 unity ,c#的也可根据这个改改就能用: 下面这个是核心部分
TcpClient
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using UnityEngine;
public class Client
{
static Client instance=new Client();
public static Client Instance => instance;
private TcpClient _client;
public void Start()
{
_client = new TcpClient();
Connect();
}
public async void Connect()//连接
{
try
{
//后端给的ip和服务器端口(这里暂时写的是本机ip)
await _client.ConnectAsync("127.0.0.1",7788);
Debug.Log("连接成功!");
Receive();//连接
}
catch (System.Exception e)
{
Console.WriteLine(e.Message);//打印错误
}
}
public async void Send(byte[] data)//发送消息
{
try
{
await _client.GetStream().WriteAsync(data,0,data.Length);
Debug.Log("发送成功!");
}
catch (Exception e)
{
Debug.Log(e.Message);
_client.Close();
}
}
byte[] data = new byte[4096];
int msgLenth = 0;//记录长度
public async void Receive()//接收数据
{
try
{
while (_client.Connected)
{
byte[] buff = new byte[4096];
int lengh=await _client.GetStream().ReadAsync(buff,0,buff.Length);
if (lengh>0)
{
Debug.Log($"接收到数据了:{lengh}");
MassengerHelper.Instance.CopyToData(buff,lengh);
Debug.Log(Encoding.UTF8.GetString(buff,0,lengh));
Array.Copy(buff, 0, data, msgLenth, lengh);
// JsonHelper.ToObject<JsonHelper.JsonTest>(buff);
msgLenth += lengh;
Handle();
}
else
{
_client.Close();
}
}
}
catch (Exception e)
{
Debug.Log(e.Message);
_client.Close();
}
}
private void Handle()
{
//注意:这里只是举个例子,一般不会这么小,空间大小一般遵循二进制准则为1024
//然后在这里我们对消息体进行封装,减少了传输,即:客户端和服务端都拿了一个密码本
//双方约定传输内容,比如1,为登录,2为退出。。。
//包体大小(4) 协议ID(4) 包体(byte[])
if (msgLenth >= 8)
{
byte[] _size = new byte[4];
Array.Copy(data, 0, _size, 0, 4);
int size = BitConverter.ToInt32(_size, 0);
//本次要拿的长度
var _length = 8 + size;
if (msgLenth >= _length)
{
//拿出id
byte[] _id = new byte[4];
Array.Copy(data, 4, _id, 0, 4);
int id = BitConverter.ToInt32(_id, 0);
//包体
byte[] body = new byte[size];
Array.Copy(data, 8, body, 0, size);
if (msgLenth > _length)
{
for (int i = 0; i < msgLenth - _length; i++)
{
data[i] = data[_length + i];
}
}
msgLenth -= _length;
Debug.Log($"收到客户端请求:{id}");
}
}
}
}
然后这里有个关联代码:
public class MassengerHelper
{
private static MassengerHelper instance=new MassengerHelper();
public static MassengerHelper Instance => instance;
byte[] data = new byte[4096];
int msgLenth = 0;
public void CopyToData(byte[] buffer,int length)
{
Array.Copy(buffer,0,data,msgLenth,length);
msgLenth += length;
Handle();
}
private void Handle()
{
//包体大小(4bit)协议(4)包体(byte[])
if (msgLenth>=8)
{
byte[] _size = new byte[4];
Array.Copy(data,0,_size,0,4);
int size=BitConverter.ToInt32(_size,0);//获知包体具体大小
//本次要拿的长度
var _length = 8 + size;
if (msgLenth>=_length)
{
//拿出id
byte[] _id = new byte[4];
Array.Copy(data,4,_size,0,4);
int id = BitConverter.ToInt32(_id,0);
//包体
byte[] body = new byte[size];
Array.Copy(data,8,body,0,size);
if (msgLenth>_length)
{
for (int i = 0; i < msgLenth-_length; i++)
{
data[i] = data[_length];
}
}
msgLenth -= _length;
Debug.Log($"收到客户请求:{id}");
//添加监听
MessanagerCenter.Instance.Dispatch(id, body);
}
}
}
public void SendToSever(int id,string str)
{
Debug.Log("ID"+id);
var body = Encoding.UTF8.GetBytes(str);
byte[] send_buff = new byte[body.Length+8];
int size = body.Length;
var _size = BitConverter.GetBytes(size);
var _id = BitConverter.GetBytes(id);
Array.Copy(_size,0,send_buff,0,4);
Array.Copy(_id,0,send_buff,4,4);
Array.Copy(body,0,send_buff,8,body.Length);
Client.Instance.Send(send_buff);
}
}
具体详细等我哪天有时间再更新吧,如果有错误欢迎大佬提出意见