Siki学院的视频教程指路牌:http://www.sikiedu.com/course/61.
我们要发送出去的数据成为包,当我们发送的数据包很小,发送间隔又很短的时候,这对于性能的消耗是很大的,Socket就会自动优化,把这些数据组合起来作为一条消息发送到服务器端。
出现的情况一般是在大型网络游戏中,要一直更新定位,血量等等的数据,就很容易出现粘包的问题了。
当我们发送的数据包很大的时候,Socket就会把包分开去传输。一方面,如果整个包去发送的话,如果发送失败,就要重新来过,十分耗费时间;另一方面,把大的数据包分成小包去发送,发送的也会更快,如果有某几个包发送失败了,只需要单独重新发送那几个包就可以了。
在发送的数据包取几位,记录数据包的长度,当所接收到的数据包长度小于记录的长度,我们就暂时不处理,直到接收到的数据包长度和记录长度相等,再统一处理。
解决的主要是由于强制分包导致每个包最后几位或者最前几位识别错误(byte转string)的问题。
(有点像我以前学过的课《计算机网络》里面的内容,有学过的朋友应该比较好理解吧o( ̄▽ ̄)ブ)
那么具体记录长度的字节要有多大呢?
因为记录长度的肯定是整型int,那么我们根据所选的int所占用的字节长度,来作为数据包的长度存储。
int有int16,int32,int64,这里选的是int32,即32位,4个字节。
字节转换方式
说到转成字节,那我们按照之前消息发送那样子用,把数据包的长度转换成字节:
int count = 10000;
System.Text.Encoding.UTF8.GetBytes(count.ToString());
它的转换原理是针对字符串(引用类型)的,它转换是先将字符串一个个字符转换成对应的字节来存储,比如说字符串“10000”,转换出来的字节数组就是“49;48;48;48;48;”(一个分号一个字节)
那如果是“100”,转换出来的就是:“49;48;48;”
至于为什么1对应49
是因为在UTF-8中:1的UTF-8编码对应的十进制数是49,
可见,转换出来的int所占字节的位数是不固定的,它和这个数据的字符串对应的长度有关,因为是一个数字对应一个字符转换成字节。
这显然是不满足我们采取固定字节长度来存储数据包大小的要求啦。
所以,我们转换字节的方式不能用以前的办法,应该要用针对值类型的字节转换方式:
BitConverter.GetBytes(count);
至于为什么233直接是233;0;0;0;
是因为233可以用一个字节表示完全,一个字节是8位,
二进制:11101001
同理,10000
十进制:10000
二进制:00100111;00010000
这是我转成二进制输出的效果:
我的问题:
我不理解的是,根据输出,是从左往右填写,在后面补0,为什么不是从右往左填值,在左边补0?
namespace TCPClient
{
/// <summary>
/// 处理消息的拼接和组装,添加长度信息等
/// </summary>
public class MessagePackup
{
/// <summary>
/// 将消息字符串转换成字节数组
/// </summary>
/// <param name="msg">要发送的消息</param>
/// <returns></returns>
static public byte[] GetByte(string msg)
{
// 数据转字节
byte[] dataBytes = System.Text.Encoding.UTF8.GetBytes(msg);
// 长度转字节
byte[] dataLength = BitConverter.GetBytes(dataBytes.Length);
// 拼接
byte[] msgBytes = dataLength.Concat(dataBytes).ToArray();
return msgBytes;
}
}
}
while (true)
{
// 客户端向服务器发送消息,发送的消息由用户在控制台输入
string msgSend = Console.ReadLine();
socketClient.Send(MessagePackup.GetByte(msgSend));
}
// 存储接收到的byte消息
private byte[] receiveBytes = new byte[1024];
// 填充位置的标记,记录上一次数据存储的位置
// 也相当于当前数组内存储的数据长度
private int addIndex = 0;
/// <summary>
/// 提供给外部 存储消息的字节数组
/// </summary>
public byte[] receiveMssage
{
get {
return receiveBytes; }
}
/// <summary>
/// 提供给外部 获取消息存储的开始位置
/// </summary>
public int AddIndex
{
get {
return addIndex; }
}
由于粘包情况,我们需要判断当前这个数据还是否能存得下数据,所以我们添加一个remainSize来记录剩余大小。
/// <summary>
/// 提供给外部 获取当前数组剩余长度
/// </summary>
public int RemainSize
{
get {
return receiveBytes.Length - addIndex; }
}
目前这些字段的关系如下图辣:
2.服务器消息处理:
由于服务器的消息接收处理是在AcceptCallback()回调函数中,我们就在那里面进行修改。
先声明一个静态全局的MessageUnpack类的对象:msgUnpack
在开启异步接收BeginAceive()的参数修改:
// 异步接收客户端的消息
// 读取的数据存储数组,存储的起始位置,接收数据的长度,套接字的标志位,接收到消息时要执行的函数,要给函数传递的信息
clientSocket.BeginReceive(msgUnpack.receiveMssage, msgUnpack.AddIndex, msgUnpack.RemainSize, SocketFlags.None, ReceiveCallback, clientSocket);
我们把存储的起始位置改为了追加的下标addIndex;
可接收数据的长度是remainSize,用于防止字节数组无法存储的情况;
3.由于接收消息的操作在BeginReceive()的回调函数:ReceiveCallback()中:
① 在接收到新消息的时候,更新追加下标的值addIndex,让其向后移
② 对消息进行解析,我们将这步操作封装在MessageUnpack类里的ReadMessage()发方法内:
Ⅰ.判断消息有效性,即是否长度小于一个int32所占字节的长度
Ⅱ. 判断数据是否接收完毕:
1. 获取消息长度:读取第一个int32字节:
int msgSize = BitConverter.ToInt32( receiveMssage, 0);
2. 当前字符数据长度大于接收到的该条数据:(addIndex>=msgSize+sizeof(int32))
2.1 则可以处理:
string msgRecv = System.Text.Encoding.UTF8.GetString(receiveMssage, 4, msgSize);
消息处理结束后,消息字节数组前移,追加下标addIndex前移:
int moveSize = msgSize + 4;
Array.Copy(receiveBytes, moveSize, receiveBytes, 0, addIndex - moveSize);
addIndex -= moveSize;
2.2 否则等待下一条消息,直到满足2.1
代码
/// <summary>
/// 接收新消息 更新追加下标后移
/// </summary>
/// <param name="addSize"></param>
public void UpdateAddIndex(int addSize)
{
addIndex += addSize;
}
/// <summary>
/// 消息解析
/// </summary>
public void ReadMessage()
{
while (true)
{
// 接收数据无效
if (addIndex <= 4) return;
// 有效数据 消息解析
// 1. 获取消息长度,从消息数据的第0位开始读取一个int32的大小的数据
int msgSize = BitConverter.ToInt32(receiveMssage, 0);
// 2.处理长度后的消息内容
// 2.1 消息长度是否足够 【粘包和正常情况时】
if ((addIndex - 4) >= msgSize)
{
// 数据长度足够,从第4位开始读取
string msgRecv = System.Text.Encoding.UTF8.GetString(receiveMssage, 4, msgSize);
Console.WriteLine("消息解析\n" +"size:"+ msgSize+" 内容:"+ msgRecv);
// 将后半部分的数据往前移
int moveSize = msgSize + 4;
Array.Copy(receiveBytes, moveSize, receiveBytes, 0, addIndex - moveSize);
// 追加下标前移
addIndex -= moveSize;
}
// 2.2 接收的消息比消息长度短【分包的情况时】
else
{
break;
}
}
}
客户端实验代码:
for (int i = 0; i < 2333; ++i)
{
socketClient.Send(MessagePackup.GetByte(i.ToString()));
}
粘包解决效果:
实验结果显示没有出现粘包现象。
由于我没有保留原来的代码,只好截视频的粘包结果了:
视频循环是0-100,在第二次发送可以看到包背结合了
前言 开发环境:JDK 开发语言:Java 开发工具 Android Studio JDK 安装配置 HelloWorld AS 默认配置修改 AVD 安装配置
时间单位: 秒(second),时间单位 : s, 毫秒(millisecond),时间单位:ms 微秒(microsecond),时间单位:μs时间换算: 1s【秒】 = 1000ms【毫秒】 1ms【毫秒】 = 1000μs【微秒】 1μs【微秒】 = 1000ns【纳秒】 1ns 【纳秒】= 1000ps【皮秒】1秒(s) = 1000 毫秒(ms) = 1,000,000 微秒(μs) = 1,0
There are three interesing C sources.// specsym.c#include extern char __executable_start[];extern char etext[], _etext[], __etext[];extern char edata[], _edata[];extern char end[], _end[];int main()
Is Graph Structure Necessary for Multi-hop Question Answering?论文:EMNLP 2020-Is Graph Structure Necessary for Multi-hop Question Answering任务探讨多步推理问答任务中是否需要图结构的问题。验证自注意力或者Transformer可能更加擅长处理多步推理问答任务。方法(模型)本文的实验使用Distractor设定。基线模型使用一个检索模型检出候选段落中
近期做了一道C语言的题,里面用到了字符指针来使用字符串,以前也遇到过这种问题,今天也是好不容易了解了其中的道道声明字符指针并赋值字符串:char *s1 = "abc"其实实际物理存储模式和字符数组是一样的,一个地址存一个字符,但是有两个和字符数组完全不同的地方。1、赋值后不可修改数据2、移动指针输出字符时,是从当前指针位置开始输出,到‘\0’停止 char *s1 = "abc";//...
iOS代码覆盖率方法总结
Android:View体系一. 示图坐标系1.View简介View是Android所有控件的基类,同时ViewGroup也是继承自View,看下面这张图我们就会有一个直观的了解:2.Android坐标系在Android中,将屏幕的左上角的顶点作为Android坐标系的原点,这个原点向右是X轴正方向,原点向下是Y轴正方向。3.View坐标系View坐标系以父视图到左上角为坐标原点,过...
今天在学习Ajax时,客户端发送请求总是报错。No ‘Access-Control-Allow-Origin’ header is present on the requested resource.我是用的是nodemon 和 express 框架来测试ajax请求。以下是代码部分:/* app.js *///引入express框架const express = require('express');//引入路径处理模块const path = require('path')//创建we
ESP32Arduino使用内部霍尔传感器开发板:ESP32 Dev Module实例代码/******************************************************* ESP32 使用内部霍尔传感器 功能:使用内部霍尔传感器获取磁场强度的值打印到串口监视器 引脚: *******************************************************/const int LED = 2;cons
原理:如果一个样本在特征空间中的K个最相邻的样本中的大多数属于某一个类别,则该样本也属于这个类别,并具有这个类别上样本的特性,KNN方法在类别决策上仅仅依据最邻近的一个或者几个样本的类别来决定待分样本所属的类别。图解: (其中“” 代表A类别,“” 代表B类别 ,“”代表待判断的样本) 通过计算“” 与“和“”各点的距离(),选择距离“”最邻近的一个或者几个样本(也就是“和...
(1)首先ifconfig命令查看一下1|[email protected]_6dq:/ # ifconfigeth0 Link encap:Ethernet HWaddr 12:21:12:12:12:34 inet addr:192.168.1.1 Bcast:192.168.1.255 Mask:255.255.255.0 UP BROAD...
量化交易100个知识点 重新认识 ROE(净资产收益率)的风险和竞争优势知识点: ROE(净资产收益率) 企业经营模式和经济周期净资产收益率ROE(Rate of Return on Common Stockholders’ Equity),净资产收益率又称股东权益报酬率/净值报酬率/权益报酬率/权益利润率/净资产利润率,是净利润与...