在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
再次之前要说一下TCP和UDP的区别 TCP是可靠传输,UDP是不可靠传输; 但是TCP有一个缺点就是会粘包,因为TCP是基于数据流的协议,而UDP是基于数据报的协议 一、什么是粘包 发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。 所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
两种情况下会发生粘包: ①发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包) ②接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 拆包的发生情况 当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。 补充问题一:为何tcp是可靠传输,udp是不可靠传输 tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的(也就是存在三次握手确定消息已经成功发送到对端) 而udp发送数据,对端是不会返回确认信息的,因此不可靠(而udp没有确认机制) 补充问题二:send(字节流)和recv(1024)及sendall recv里指定的1024意思是从缓存里一次拿出1024个字节的数据 send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失 二、解决粘包问题的方法粘包问题的关键在于: 接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据 解决方法: 我们可以把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了) 发送时: 先发报头长度 再编码报头内容然后发送 最后发真实内容 接收时: 先手报头长度,用struct取出来 根据取出的长度收取报头内容,然后解码,反序列化 从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容 正是由于TCP的粘包问题,所以在针对我们的项目中,我选择了使用UDP socket的通信方法 这个方法中主要就是两个方法,一个是消息的发送,一个是消息的接收 项目是基于Unity的所以语言选择了C# using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace WindowsFormsApp2
{
/// <summary>
/// 发送接收消息帮助类
/// </summary>
public class UDP_CLIENT_HELPER
{
static Socket client;
private string _ip_server = string.Empty;
private int _port_server = 6001;
private string _ip_client = string.Empty;
private int _port_client = 6000;
public UDP_CLIENT_HELPER(string _Ip_server, int _Port_server, string _Ip_client, int _Port_client)
{
this._ip_server = _Ip_server;
this._port_server = _Port_server;
this._ip_client = _Ip_client;
this._port_client = _Port_client;
if (client == null)
{
client = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
client.Bind(new IPEndPoint(IPAddress.Parse(_ip_client), _port_client));
}
}
/// <summary>
/// 发送消息
/// </summary>
/// <param name="message"></param>
public void sendMsg(string message)
{
//绑定服务器端口和ip并发送
EndPoint point = new IPEndPoint(IPAddress.Parse(_ip_server), _port_server);
client.SendTo(Encoding.UTF8.GetBytes(message), point);
}
/// <summary>
/// 接收消息
/// </summary>
public void ReciveMsg()
{
while (true)
{
//循环接收服务器发来的消息 并返回JObject对象
EndPoint point = new IPEndPoint(IPAddress.Any, 0);
byte[] buffer = new byte[1024];
int length = client.ReceiveFrom(buffer, ref point);//接收数据报
string message = Encoding.UTF8.GetString(buffer, 0, length);
Newtonsoft.Json.Linq.JObject obj = (Newtonsoft.Json.Linq.JObject)JsonConvert.DeserializeObject(message);
string type = obj["message"]["type"].ToString();
}
}
}
}
|
请发表评论