Socket 传输问题,各店帮忙看看。

Water Lee 2019-06-02 03:51:18
自己写了个socke传数据,一直用着都挺正常的。但今天遇到传输较大字符串时,出现有时出错,有时正确的情况,大概率出错,小概率正确,各店位帮忙看看哪里有问题?
客户端连接接收数据主要代码
 public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, string strData)
{
//创建套接字
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
doServerBack = ServerBackDeel;
//开始连接到服务器
try
{
//连接指定服务器
client.Connect(conIP, conPort);
if (client.Connected)
{//连接成功
//向服务器发送服务请求
//因为传输时结束字符串可能被截为两段,所以结束符多发一次,以保证服务器收到结束符
//SendMessage("DATESTART" + strData + "DATEENDDATEEND");
SendMessage(strData);
//开始接收服务器返回结果
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
else
{//连接失败
client.Close();
}
}
catch (Exception ex)
{
client.Close();
//如果显示提示,那么在后台静默等待服务器开启时
//就会出现显示即程序暂停的情况
}
}

/// <summary>
/// 接收数据调用方法
/// </summary>
/// <param name="ar"></param>
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
if (client.Connected)
{
byteSource.AddRange(receiveBuffer);//接收数据
//检查数据中是否有结尾标记
int REnd = client.EndReceive(ar);
string strReceiveData = Encoding.UTF8.GetString(receiveBuffer, 0, REnd);
if (!string.IsNullOrEmpty(strReceiveData))
{//如果字符串不为空
//检测结束符
int endloc = strReceiveData.IndexOf("DATEEND");
if (endloc > 0)
{//接收数据结束
ReciveData = Encoding.UTF8.GetString(byteSource.ToArray());
int start = ReciveData.IndexOf("DATESTART") + "DATESTART".Length;
int end = ReciveData.IndexOf("DATEEND");
ReciveData = ReciveData.Substring(start, end - start);
//实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
string[] resultData = ReciveData.Split('\f');
if (Cls_WaterSocketMessage.GetHashValue(resultData[0]) == resultData[1])
{//哈希值字符串比较
doServerBack(resultData[0]);
}
else
{
doServerBack("FALSE");
}
timeoutObject.Set();//这里设置结果超时控制的阻塞
}
}
//this.HandleMessage(strReceiveData);
if (client.Connected)
{
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
}
}
catch (Exception ex)
{
//System.Windows.Forms.MessageBox.Show("ReceiveCallBack " + ex.Message);
}
}

服务器端发送数据代码
public void SendMessage(Socket s, string ms)
{
if (!s.Poll(500, SelectMode.SelectRead))
{
s.Send(Encoding.UTF8.GetBytes(
"DATESTART" //报文开头
+ ms //报文内容
+ "\f" //报文与其哈希值的分隔符
+ Cls_WaterSocketMessage.GetHashValue(ms) //报文内容的哈希值
+ "DATEENDDATEEND"));
}
}


出错时,我都是在传文件,几百K到几M不等。我断点查了下,每次不成功都是在哈希验证时出错。关键的关键是,有时候又能正常传输,
各店帮忙看看吧。无力了
当然,如果哪位能直接给个可以正常使用的SOCKET代码就不胜感激了。
...全文
2228 32 打赏 收藏 转发到动态 举报
写回复
用AI写文章
32 条回复
切换为时间正序
请发表友善的回复…
发表回复
「已注销」 2019-10-10
  • 打赏
  • 举报
回复
MiniSocket可以下载这个来开发socket tcp应用,比较方便,https://bbs.csdn.net/topics/394702135
Water Lee 2019-07-06
  • 打赏
  • 举报
回复
再次感谢各位帮忙
enaking 2019-06-13
  • 打赏
  • 举报
回复
这个就是要判断整条消息,然后拼接。
Water Lee 2019-06-13
  • 打赏
  • 举报
回复
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace WaterClassLibrary
{
/// <summary>
/// Socket 客户端类
/// </summary>
public class Cls_SocketClient
{
/// <summary>
/// 连接服务器的Socket
/// </summary>
public Socket client;

/// <summary>
/// 接收到的数据
/// </summary>
//private string ReciveData;

private StringBuilder strBuilder = new StringBuilder();

/// <summary>
/// SOCKET 状态
/// </summary>
ManualResetEvent timeoutObject = new ManualResetEvent(false);//通知一个或多个正在等待的线程已发生事件

/// <summary>
/// 接收服务器发来的数据后执行的方法
/// </summary>
/// <param name="ServerBackBack">服务器返回的数据</param>
public delegate void DoServerBack(string ServerBackBack);

DoServerBack doServerBack;

/// <summary>
/// 获取或设置客户端等待获取服务器回应的最长时间(毫秒)
/// 默认30000(30秒)
/// </summary>
public int millisecondsTimeout = 30000;

/// <summary>
/// 接收数据缓存
/// </summary>
private byte[] receiveBuffer = new byte[1024];

/// <summary>
/// 用于存储接收的数据
/// </summary>
List<byte> byteSource = new List<byte>();

/// <summary>
/// 接收数据长度
/// </summary>
private int ReceiveDataLength;

#region 连接并接收数据
/// 连接时应当处理超时问题,否则可能在连接时过长时间等待
/// 最终造成连接失败,或是无反应假像。

/// <summary>
/// 连接到服务器
/// </summary>
/// <param name="conIP">连接IP</param>
/// <param name="conPort">连接端品</param>
/// <param name="ServerBackDeel"></param>
/// <param name="csmt"></param>
public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, Cls_WaterSocketMessage csmt)
{
//创建套接字
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
doServerBack = ServerBackDeel;
//开始连接到服务器
try
{
#region Old code,without timeout
//连接指定服务器
client.Connect(conIP, conPort);
if (client.Connected)
{//连接成功
//向服务器发送服务请求
string s = csmt.GetSendMessageString();
SendMessage(s);

//开始接收服务器返回结果
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
else
{//连接失败
client.Close();//释放资源
}
#endregion
}
catch (Exception ex)
{
client.Close();
//如果显示提示,那么在后台静默等待服务器开启时
//就会出现显示即程序暂停的情况
//System.Windows.Forms.MessageBox.Show(ex.Message);
}
}

/// <summary>
/// 连接到服务器
/// </summary>
/// <param name="conIP">连接IP</param>
/// <param name="conPort">连接端口</param>
/// <param name="ServerBackDeel"></param>
/// <param name="strData"></param>
public void Connect(string conIP, int conPort, DoServerBack ServerBackDeel, string strData)
{
//创建套接字
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
doServerBack = ServerBackDeel;
//开始连接到服务器
try
{
//连接指定服务器
client.Connect(conIP, conPort);
if (client.Connected)
{//连接成功
//向服务器发送服务请求
SendMessage(strData);
//开始接收服务器返回结果
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
else
{//连接失败
client.Close();
}
}
catch (Exception ex)
{
client.Close();
//如果显示提示,那么在后台静默等待服务器开启时
//就会出现显示即程序暂停的情况
}
}

/// <summary>
/// 接收数据调用方法
/// </summary>
/// <param name="ar"></param>
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
if (client.Connected)
{
int REnd = client.EndReceive(ar);
byte[] tmpbyte = new byte[REnd];
Array.Copy(receiveBuffer, 0, tmpbyte, 0, REnd);
byteSource.AddRange(tmpbyte);//接收数据

#region 使用长度来标记是否已接收结束
if (ReceiveDataLength <= 0
&& byteSource.Count >= 4)
{//还未计算接收长度,同时,接收到的长度已经大于或等于长度位长度
byte[] byteLengthg = new byte[4];
Array.Copy(byteSource.ToArray(), 0, byteLengthg, 0, 4);
ReceiveDataLength = Cls_WaterSocketMessage.byteArrayToInt(byteLengthg);//使转换方式与JAVA一致

}
if (byteSource.Count >= ReceiveDataLength + 4)
{//数据已全部接收到
if (doServerBack != null)
{//返回方法不为空
string data = Encoding.UTF8.GetString(byteSource.ToArray(), 4, ReceiveDataLength);

//实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
int indexf = data.LastIndexOf('\f');
string resultData = data.Substring(0, indexf);
string hashData = data.Substring(indexf + 1, data.Length - indexf - 1);
if (Cls_WaterSocketMessage.GetHashValue(resultData) == hashData)
{//哈希值字符串比较
doServerBack(resultData);
}
else
{
doServerBack("FALSE");
}
}
timeoutObject.Set();//这里设置结果超时控制的阻塞
client.Close();//结束连接
return;
}
#endregion

if (client.Connected)
{
client.BeginReceive(receiveBuffer, 0, receiveBuffer.Length, 0, new AsyncCallback(ReceiveCallBack), null);
}
}
}
catch (Exception ex)
{

}
}

#endregion

/// <summary>
/// 断开连接
/// </summary>
public void DeConnect()
{
try
{
if (client != null)
{
client.Shutdown(SocketShutdown.Both);
client.Close();
}
}
catch (Exception ex)
{
string a = ex.Message;
}
}

/// <summary>
/// 修改为添加哈希验证
/// </summary>
/// <param name="sendmessage"></param>
private void SendMessage(string sendmessage)
{
byte[] sendData = Encoding.UTF8.GetBytes(sendmessage); //报文内容转 byte
byte[] hashcode = Encoding.UTF8.GetBytes("\f" + Cls_WaterSocketMessage.GetHashValue(sendmessage));//报文内容的哈希值
byte[] sendHead = Cls_WaterSocketMessage.intToByteArray(sendData.Length + hashcode.Length);//报文头,使转换方式与JAVA一致
if (client != null)
{
//发送报文长度,int 占4个byte
client.Send(sendHead); //发送报文头
client.Send(sendData); //发送报文信息
client.Send(hashcode); //发送信息哈希验证码
}
}

/// <summary>
/// 如果连接失败或是未返回就中断,则返回false
/// 成功连接返回true
/// 这里设置了最长等待判断时长为10000毫秒,即10秒,否则可能出现长时间等待
/// </summary>
/// <returns></returns>
public bool CheckConnect()
{
try
{
if (this.client == null)
{
return false;
}
else if (this.client.Connected == false)
{
DeConnect();
return false;
}
if (!timeoutObject.WaitOne(millisecondsTimeout,false))
{
DeConnect();
return false;
}
return true;
}
catch
{
return false;
}
}
}
}

以上为客户端

希望各位大神指点,因结贴了就不能回复了,所以我过段时间再来结贴!
Water Lee 2019-06-13
  • 打赏
  • 举报
回复
根据各位的帮忙,代码修改并投入使用了,暂时没有发现问题。代码如下,请各位指正:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace WaterClassLibrary
{
/// <summary>
/// Socket 服务器端类
/// 客户端尽可能连接一次只能做一次发送
/// </summary>
public class Cls_SocketServer
{
private IPAddress ServerIP;
private int Port;
private Socket SocketServer;
public ArrayList Clients;
public delegate void ServiceWork(Socket forClient, String strMessage);
public event ServiceWork DoServiceWork;


#region 该定义用于处理接收数据分包
/// <summary>
/// 当前客户端状态
/// </summary>
class Cls_ClientStata
{
/// <summary>
/// 当前连接客户端
/// </summary>
public Socket client;

/// <summary>
/// 当前接收到的数据
/// </summary>
public string data;

/// <summary>
/// 用于存储接收的数据,最后转换为 string 赋值 data
/// </summary>
public List<byte> byteSource = new List<byte>();

/// <summary>
/// 每次接收的数据长度,实际可能没有这么长,需要根据实际情况处理
/// </summary>
public byte[] buffer=new byte[1024];

/// <summary>
/// 需要接收的数据长度
/// </summary>
public int ReceiveDataLength;

/// <summary>
///
/// </summary>
/// <param name="s"></param>
/// <param name="d"></param>
public Cls_ClientStata(Socket s)
{
client = s;
}
}
#endregion

/// <summary>
/// 实例服务器端Socket
/// </summary>
///<param name="P">端口号</param>
///<param name="ip">这里请使用Any,否则可能服务无法从外网访问</param>
public Cls_SocketServer(IPAddress ip, int P)
{
ServerIP = ip;
Port = P;
Clients = new ArrayList();
}

/// <summary>
/// 开始服务,可以创建客户连接
/// </summary>
/// <returns></returns>
public bool StartServer()
{
try
{
//获取IP地址
IPEndPoint ipep = new IPEndPoint(ServerIP, Port);
//实例化Socket
SocketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//将Socket与指定IP绑定
SocketServer.Bind(ipep);
//Socket 开始监听,设置监听队列的长度
SocketServer.Listen(10000);
SocketServer.BeginAccept(new AsyncCallback(ClientAccepted), SocketServer);
return true;
}
catch(Exception ex)
{
return false;
}
}

/// <summary>
/// 接受客户连接
/// </summary>
/// <param name="ar"></param>
private void ClientAccepted(IAsyncResult ar)
{
try
{
var socket = ar.AsyncState as Socket;//这里应该是SocketServer
//这就是客户端的Socket实例,我们后续可以将其保存起来
var client = socket.EndAccept(ar);//连接到的客户端
Clients.Add(client);//加入当前连接客户端列表
Cls_ClientStata ccs = new Cls_ClientStata(client);
//接收客户端的消息(这个和在客户端实现的方式是一样的)
client.BeginReceive(ccs.buffer, 0, ccs.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), ccs);
//准备接受下一个客户端请求
socket.BeginAccept(new AsyncCallback(ClientAccepted), socket);
}
catch
{

}
}

/// <summary>
/// 接收收到的信息
/// </summary>
/// <param name="ar"></param>
private void ReceiveMessage(IAsyncResult ar)
{
try
{
//使用自定义类来存储数据,以便于区分是哪个客户端发来的信息
Cls_ClientStata ccs = (Cls_ClientStata)ar.AsyncState;
Socket client = ccs.client;
int length = client.EndReceive(ar);
if (length == 0)
{//如果接收不到数据
client.Close();
return;
}
/// 虽然设置了 css.buffer 的长度为1024,但实际接收数据时,不一定能
/// 接收到1024的数据,如果此时直接认为是接收了1024,那么收到的数据
/// 就会多出空数据来,这样最终的收到的数据就会与发送的不一致,由其
/// 是在发送文件时,收到的文件将是不可用,不可以打开的。
byte[] tmpbyte = new byte[length];
Array.Copy(ccs.buffer, 0, tmpbyte, 0, length);
ccs.byteSource.AddRange(tmpbyte);//接收数据

#region 使用长度来标记是否已接收结束
if (ccs.ReceiveDataLength <= 0
&& ccs.byteSource.Count>=4)
{//还未计算接收长度,同时,接收到的长度已经大于或等于长度位长度
byte[] byteLengthg = new byte[4];
Array.Copy(ccs.byteSource.ToArray(), 0, byteLengthg, 0, 4);
ccs.ReceiveDataLength = Cls_WaterSocketMessage.byteArrayToInt(byteLengthg);//使转换方式与JAVA一致
//ccs.ReceiveDataLength = BitConverter.ToInt32(ccs.byteSource.ToArray(),0);
}
if (ccs.byteSource.Count >= ccs.ReceiveDataLength + 4)
{//数据已全部接收到
if (DoServiceWork != null)
{//返回方法不为空
ccs.data = Encoding.UTF8.GetString(ccs.byteSource.ToArray(), 4, ccs.ReceiveDataLength);

//实际使用中发现会出现数据传输错误,故加入哈希验证,减少错误数据读取率
int indexf = ccs.data.LastIndexOf('\f');
string resultData = ccs.data.Substring(0, indexf);
string hashData = ccs.data.Substring(indexf + 1, ccs.data.Length - indexf - 1);
if (Cls_WaterSocketMessage.GetHashValue(resultData) == hashData)
{//哈希值字符串比较
DoServiceWork(ccs.client, resultData);
}
else
{
DoServiceWork(ccs.client, "FALSE");
}
}
ccs.client.Close();//结束连接
return;
}
//接收下一个消息(因为这是一个递归的调用,所以这样就可以一直接收消息了)
client.BeginReceive(ccs.buffer, 0, ccs.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveMessage), ccs);
#endregion
}
catch(Exception ex)
{
}
}

/// <summary>
/// 结束服务
/// </summary>
public void Stop()
{
try
{
SocketServer.Shutdown(SocketShutdown.Both);
SocketServer.Close();
foreach (Socket s in Clients)
{
s.Shutdown(SocketShutdown.Both);
s.Close();
}
}
catch (Exception ex)
{
//throw new Exception(ex.Message);
}
}

/// <summary>
/// 发送网络信息
/// </summary>
/// <param name="s">要发送的客户端</param>
/// <param name="ms">要发送的信息</param>
public void SendMessage(Socket s, string ms)
{
if (!s.Poll(500, SelectMode.SelectRead))
{
byte[] sendData = Encoding.UTF8.GetBytes(ms); //报文内容转 byte
byte[] hashcode = Encoding.UTF8.GetBytes("\f" + Cls_WaterSocketMessage.GetHashValue(ms));//报文内容的哈希值
byte[] sendHead = Cls_WaterSocketMessage.intToByteArray(sendData.Length + hashcode.Length);//报文头,使转换方式与JAVA一致
//byte[] sendHead = BitConverter.GetBytes(sendData.Length + hashcode.Length);//报文头
//发送报文长度,int 占4个byte
s.Send(sendHead); //发送报文头
s.Send(sendData); //发送报文信息
s.Send(hashcode); //发送信息哈希验证码
}
}
}
}

以上为服务器端
Water Lee 2019-06-09
  • 打赏
  • 举报
回复
引用 20 楼 csteacher的回复:
[quote=引用 19 楼 csteacher的回复:]可能跟路由器设置有关系
我之前也是,20000个字节,公司能解析,回到家里不能解析。后面是通过递归重复读取解决的[/quote] 我也是写的递归来获取数据的,不知道你说的是什么情况,可以具体说下吗,谢谢
csteacher 2019-06-09
  • 打赏
  • 举报
回复
引用 24 楼 Water Lee的回复:
[quote=引用 20 楼 csteacher的回复:][quote=引用 19 楼 csteacher的回复:]可能跟路由器设置有关系
我之前也是,20000个字节,公司能解析,回到家里不能解析。后面是通过递归重复读取解决的[/quote] 我也是写的递归来获取数据的,不知道你说的是什么情况,可以具体说下吗,谢谢[/quote] 前四个字节设置一个长度标志位,每次读取完以后判断下读到的已读取的总长度是不是大于等于长度标志位,不是的话,继续读取(第二次读取的时候就没有标志位了)。这样子递归
Water Lee 2019-06-09
  • 打赏
  • 举报
回复
感谢各位的帮忙,急着把程序更新到各客户端,暂时不能系统处理每位给的建议。
除了数据流处理设计逻辑上的问题,这次传输问题主要还是对socket的理解不够,但本人能力有限,只能学一点用一点了。就如 bearocean 说的,我确实是业余的程序员。我只是之前学的计算机专业,现在做的是企业管理工作,为了方便管理,自己写的程序。其实不想自己写,不过找过的软件公司报价都太高,我们就是一个小县城的私营企业,动则几十万的定制软件真的用不起啊!
这里也说明一下,我粘出的代码下面这里是有问题的,现在暂时进行了处理,传输没有问题,至于包设计问题,等具体修改了再做更新了。

if (client.Connected)
{
byteSource.AddRange(receiveBuffer);//接收数据

//检查数据中是否有结尾标记
int REnd = client.EndReceive(ar);
string strReceiveData = Encoding.UTF8.GetString(receiveBuffer, 0, REnd);
if (!string.IsNullOrEmpty(strReceiveData))
............

以上代码
byteSource.AddRange(receiveBuffer);//接收数据
receiveBuffer 当前问题主要就在这里,其定义为 byte[] receiveBuffer=new byte[1024]
我傻傻的认为定义了1024长,每次就可以获取到1024长的数据,其实不一定。
当获取数据达不到1024长时,我又把空的数据也视为收到数据拼接起来了,所有当文件较
大时,就会出现多收了一部份数据的情况,而在单机测试时,由于传输延迟小,基本不会出错
但一到局域网或是外网,多重路由一转,延迟增长,则出现概率就高了。
简单做了下处理,先发到客户端解决当前问题,代码处理如下:
int REnd = client.EndReceive(ar);
byte[] tmpbyte = new byte[REnd];
Array.Copy(receiveBuffer, 0, tmpbyte, 0, REnd);
byteSource.AddRange(tmpbyte);//接收数据
//检查数据中是否有结尾标记
string strReceiveData = Encoding.UTF8.GetString(receiveBuffer, 0, REnd);
if (!string.IsNullOrEmpty(strReceiveData))

当然以代码也还有已知逻辑问题,就是 bearocean 说的极端情况,如果网络不好,一次获取到的数据连DATAEND的长度都没有,而且刚好一段数据包含的就是这个结束符,那数据就处理不了了,只能等超时重传一次了。
当然,如果路由器有什么设置,每次连这个长度都收不到,那传输一定是失败的。
worldy 2019-06-09
  • 打赏
  • 举报
回复
引用 2 楼 Water Lee 的回复:
感谢 xian_wwq 帮忙,你说的可能性中,我可以确认不会timeout,因为每次设置只接收1024字节,其它字符串的传输也要拼包的,但没有遇到失败的情况。我主要是在传1M左右的文件时出错。


不是因为Timeout,主要原因应该是拆包分包造成,你必须接收所有的数据之后,再进行计算hash;接收完整的数据,可以:
1) 你可以定义一个结束的字符串,检测到结束字符串后再进行计算
或者
2)先发送数据长度,然后接收长度满足后,再进行计算
Water Lee 2019-06-08
  • 打赏
  • 举报
回复
引用 17 楼 bearocean的回复:
楼主,你的包处理逻辑与常规的做法差别很大,思路上感觉存在问题。
TCP处理粘包基本上是跑不掉的。
你要考虑一个极端的验证模型是:
即便每次到达的数据只有1个字节,你的程序依然能够正确解析数据,这样才是达标的。

所以一般的逻辑是先要自己进行应用协议的设计.
你这里也有设计,那就是用DATASTART 和DATAEND做分隔。。但个人认为这个设计太业余了。
1、你的协议做了一个假设,传输的数据必然是文本数据。但一般是完全可以做到不对数据进行解释,这样弹性最大,比如哪天要加个需求,需要传输图片?
2、一般情况,传输文件实际上无法对实际传输的内容进行控制,比如你的文件里面如果刚好就有 DATAEND, 那会怎样.


大家用得比较多的协议模型一般是这样的:

head, body
最简单的:
head 只是一个2字节整数,用来标记包的大小.
body是实际的字节流.

所以发送方的伪码是:(如果你就想发送字符串)
sendString(_str:string)
bodySize = _str.length();
pack = new bytes[2 + _str.length()]
pack[0] = low(bodySize)
pack[1] = high(bodySize)
memcpy(pack[2], _str)

接收随便给你一个代码:

private onReceive(_msg:any = null):void{
// console.log("接收数据");
// _msg为ArrayBuffer
// 强制转换类型
let data:ArrayBuffer = _msg;
// Log.debug("recev data bytes length:" + data.byteLength);

// 实现Marshal
let unmarshalResult:GUnmarshalResult= this.protocol.unmarshal(data);
if(unmarshalResult.status != GUnmarshalResult.UNMARSHAL_STATUS_OK){
g.common.Log.error("unmarshal failed.");
return;
}
let pack:any = unmarshalResult.pack;
// Log.debug("get pack:" + pack);

// 通知消息接收
GNet.onRecv(this, pack);
}




// 将字节流转换为包对象
// 由于TS无法返回多个返回值,所以返回对象
public unmarshal(_data:ArrayBuffer):GUnmarshalResult{
// TODO:这里做了一个假设:websocket已经处理好了粘包和分包问题
// 准备参数
let pack:Pack = null;
let status:number = GUnmarshalResult.UNMARSHAL_STATUS_OK;

do{
// 判定数据长度必须大于HEADER_SIZE
let dataSize:number = _data.byteLength;
if(dataSize < Pack.HEADER_SIZE){
status = GUnmarshalResult.UNMARSHAL_STATUS_ERROR;
break;
}

// 获取bodySize,cmd和seqNum
let byte:Byte = new Byte(_data);
byte.endian = Protocol.ENDIAN;
let bodySize:number = byte.getUint32();
let cmd:number = byte.getUint32();
let seqNum:number = byte.getUint32();

// 获取body
let body:ArrayBuffer = _data.slice(Pack.HEADER_SIZE, dataSize);

// 生成pack
pack = new Pack(cmd, body, seqNum);
}while(false);

// 生成result,并返回
let result:GUnmarshalResult = new GUnmarshalResult();
result.pack = pack;
result.status = status;
return result;
}




服务器类似代码(golang)
func  Unmarshal(_data []byte) (interface{}, int32) {
// 校验_data的长度必须大于HEADER_SIZE
dataSize := uint32(len(_data))
if dataSize < HEADER_SIZE {
return nil, e2.ERR_GATE_PROTOCOL_ERROR
}

// 获取BodySize
endian := binary.LittleEndian
bodySize := endian.Uint32(_data)

// 再次检查长度
fullSize := bodySize + HEADER_SIZE
if dataSize < fullSize {
return nil, e2.ERR_GATE_PROTOCOL_ERROR
}

// 获取cmd
cmd := endian.Uint32(_data[4:])

// 获取seqNum
seqNum := endian.Uint32(_data[8:])

// 生成package
// TODO:这里有一个拷贝!
pack := &Pack{}
pack.BodySize = bodySize
pack.CMD = cmd
pack.SeqNum = seqNum
pack.Body = make([]byte, bodySize)
copy(pack.Body, _data[HEADER_SIZE:fullSize])

return pack, e.OK

}
谢谢,确实存在你说的这个问题,正在考虑怎样修正,非常感谢。
weixin_45195654 2019-06-06
  • 打赏
  • 举报
回复
你懂的,我也遇到过
csteacher 2019-06-05
  • 打赏
  • 举报
回复
引用 19 楼 csteacher的回复:
可能跟路由器设置有关系
我之前也是,20000个字节,公司能解析,回到家里不能解析。后面是通过递归重复读取解决的
csteacher 2019-06-05
  • 打赏
  • 举报
回复
可能跟路由器设置有关系
weixin_45188567 2019-06-05
  • 打赏
  • 举报
回复
考虑长短链接的问题
「已注销」 2019-06-04
  • 打赏
  • 举报
回复
关注一下,顺便看看自己的账号
bearocean 2019-06-04
  • 打赏
  • 举报
回复
楼主,你的包处理逻辑与常规的做法差别很大,思路上感觉存在问题。
TCP处理粘包基本上是跑不掉的。
你要考虑一个极端的验证模型是:
即便每次到达的数据只有1个字节,你的程序依然能够正确解析数据,这样才是达标的。

所以一般的逻辑是先要自己进行应用协议的设计.
你这里也有设计,那就是用DATASTART 和DATAEND做分隔。。但个人认为这个设计太业余了。
1、你的协议做了一个假设,传输的数据必然是文本数据。但一般是完全可以做到不对数据进行解释,这样弹性最大,比如哪天要加个需求,需要传输图片?
2、一般情况,传输文件实际上无法对实际传输的内容进行控制,比如你的文件里面如果刚好就有 DATAEND, 那会怎样.


大家用得比较多的协议模型一般是这样的:

head, body
最简单的:
head 只是一个2字节整数,用来标记包的大小.
body是实际的字节流.

所以发送方的伪码是:(如果你就想发送字符串)
sendString(_str:string)
bodySize = _str.length();
pack = new bytes[2 + _str.length()]
pack[0] = low(bodySize)
pack[1] = high(bodySize)
memcpy(pack[2], _str)

接收随便给你一个代码:

private onReceive(_msg:any = null):void{
// console.log("接收数据");
// _msg为ArrayBuffer
// 强制转换类型
let data:ArrayBuffer = _msg;
// Log.debug("recev data bytes length:" + data.byteLength);

// 实现Marshal
let unmarshalResult:GUnmarshalResult= this.protocol.unmarshal(data);
if(unmarshalResult.status != GUnmarshalResult.UNMARSHAL_STATUS_OK){
g.common.Log.error("unmarshal failed.");
return;
}
let pack:any = unmarshalResult.pack;
// Log.debug("get pack:" + pack);

// 通知消息接收
GNet.onRecv(this, pack);
}




// 将字节流转换为包对象
// 由于TS无法返回多个返回值,所以返回对象
public unmarshal(_data:ArrayBuffer):GUnmarshalResult{
// TODO:这里做了一个假设:websocket已经处理好了粘包和分包问题
// 准备参数
let pack:Pack = null;
let status:number = GUnmarshalResult.UNMARSHAL_STATUS_OK;

do{
// 判定数据长度必须大于HEADER_SIZE
let dataSize:number = _data.byteLength;
if(dataSize < Pack.HEADER_SIZE){
status = GUnmarshalResult.UNMARSHAL_STATUS_ERROR;
break;
}

// 获取bodySize,cmd和seqNum
let byte:Byte = new Byte(_data);
byte.endian = Protocol.ENDIAN;
let bodySize:number = byte.getUint32();
let cmd:number = byte.getUint32();
let seqNum:number = byte.getUint32();

// 获取body
let body:ArrayBuffer = _data.slice(Pack.HEADER_SIZE, dataSize);

// 生成pack
pack = new Pack(cmd, body, seqNum);
}while(false);

// 生成result,并返回
let result:GUnmarshalResult = new GUnmarshalResult();
result.pack = pack;
result.status = status;
return result;
}




服务器类似代码(golang)
func  Unmarshal(_data []byte) (interface{}, int32) {
// 校验_data的长度必须大于HEADER_SIZE
dataSize := uint32(len(_data))
if dataSize < HEADER_SIZE {
return nil, e2.ERR_GATE_PROTOCOL_ERROR
}

// 获取BodySize
endian := binary.LittleEndian
bodySize := endian.Uint32(_data)

// 再次检查长度
fullSize := bodySize + HEADER_SIZE
if dataSize < fullSize {
return nil, e2.ERR_GATE_PROTOCOL_ERROR
}

// 获取cmd
cmd := endian.Uint32(_data[4:])

// 获取seqNum
seqNum := endian.Uint32(_data[8:])

// 生成package
// TODO:这里有一个拷贝!
pack := &Pack{}
pack.BodySize = bodySize
pack.CMD = cmd
pack.SeqNum = seqNum
pack.Body = make([]byte, bodySize)
copy(pack.Body, _data[HEADER_SIZE:fullSize])

return pack, e.OK

}
sdghchj 2019-06-04
  • 打赏
  • 举报
回复
你就那么自信一定能找到"DATESTART","DATEEND"这些串?start,end不可能为-1么?

ReciveData = Encoding.UTF8.GetString(byteSource.ToArray());
int start = ReciveData.IndexOf("DATESTART") + "DATESTART".Length;
int end = ReciveData.IndexOf("DATEEND");
ReciveData = ReciveData.Substring(start, end - start);
m0_37646670 2019-06-04
  • 打赏
  • 举报
回复
引用 12 楼 Water Lee 的回复:
[quote=引用 10 楼 xian_wwq 的回复:]
看已有代码,有一种情况是没处理,
strReceiveData如果不包含结束字符,
则这部分数据就会被抛弃,
后续接收的数据即使包含了结束字符,
因为数据不完整也无法解析的。

你说的这种情况之前是出现过,不过已经处理了
发送端发送代码如下
/// <summary>
/// 发送网络信息
/// </summary>
/// <param name="s">要发送的客户端</param>
/// <param name="ms">要发送的信息</param>
public void SendMessage(Socket s, string ms)
{
if (!s.Poll(500, SelectMode.SelectRead))
{
s.Send(Encoding.UTF8.GetBytes(
"DATESTART" //报文开头
+ ms //报文内容
+ "\f" //报文与其哈希值的分隔符
+ Cls_WaterSocketMessage.GetHashValue(ms) //报文内容的哈希值
+ "DATEENDDATEEND")); //这里连续发送两个结束符,确保总有一个会被收到。
}
}

其中的哈希值就是因为发现收到的数据与发送的不一致而加上去的。
其实这些代码已经是投入生产使用的,现在也没有出过大的问题。现在客户要求加入软件升级功能,那在原来基础上,最直接的就是用SOCKET把要更新的文件传输到客户端。然后就出现了接收数据与发送不一致的问题。
当然,可以明确的是已经收到数据不存在粘包问题,但数据被改变了。现在最大的问题是同一台电脑开服务端与客户端就没有问题,挂到局域网就不行,代码逻辑看了好几遍了,还没有有找到原因[/quote]

试试发送循环里加个SLEEP

datafansbj 2019-06-03
  • 打赏
  • 举报
回复
基于流的传输,稳妥的方法是,在流的首端放置一个后续数据长度的头(4或8字节),先收这个头,收全后再根据这个长度收后续的数据,直到收全,就不怕分包的问题了。
Water Lee 2019-06-03
  • 打赏
  • 举报
回复
引用 11 楼 耗子哭死猫 的回复:
数据大用压缩GZipStream
byte[] -> MemoryStream -> Zip(Stream) -> Socket发送

1M左右的数据还不能算大吧!
加载更多回复(12)

110,529

社区成员

发帖
与我相关
我的任务
社区描述
.NET技术 C#
社区管理员
  • C#
  • Web++
  • by_封爱
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

让您成为最强悍的C#开发者

试试用AI创作助手写篇文章吧