高分求C#使用TCP发送或接收数据不全,数据重组 用TCP接收数据的时候,经常是发送一次,接收却需要两次,这样便要对接收的数据进行重组,但目前还没想到如何进行数据的重组,望高人指点,最好说的越详细越好! 解决方案 » 免费领取超大流量手机卡,每月29元包185G流量+100分钟通话, 中国电信官方发货 晕,这个功能本来就应该是由TCP层来完成的工作,到TCP层的时候,只要你的数据在缓存区中,不会出现需要receive两次的情况吧?至于数据的重组,我一般是用一个自己的cache来完成这样的工作。 楼上的朋友,比如我用TCP发送"123456"这个字符串,可是在我接收的时候有可能就是收到123,这时我再发送123的话,那么会收到"123456123"这样的数据,请问这是为何?前提是我已经刷新了流的! 是TCP本身机制的问题, 你发送数据时首尾加上标识,接收时,判断标识,首尾都有则完全,首有,尾没有,则说明他会分多次发送,自己就要重组有人这么回答过,但不知具体应如何实现! 我都是自己用Socket来接收数据,没有用过NetStream这种方式。至少我可以肯定,如果用Socket接收byte[]形式的数据,在网络通信正常的情况下,绝对不会出现你说的情况。你说的情况我要测试一下NetStream的情况后再处理看是不是它有什么特殊的处理,不过我觉得你还是先检查一下自己的通信过程是否有一些比如多线程同步的问题没有处理好。 那麻烦楼上的朋友测试一下NetStream看看,谢了!主要就是如果我定义一个6个字节的字节数组,那么如果我发送的数据不能填满6个字节的话,那么TCP就会分次接收完,如果一次填满6个字节的话就只需要接收一次,而且如果第一次把6个字节填满了,如果第二次没填满的话,多余的那几个字节会用上一次填的字节去补上,就是不知这是为什么! 使用 NetStream 确实出现数据不完整的时候 这可能和个人的设计思路有关, 我一开始也是总出现问题。序列化后的数据长度在接收方长度有时候会出现不一致的时候。不过后来我把数据改成定长最大3K 缓冲区设4K 每次发送完4K,每次接收完4K就不出错了。然后再改一下,吧数据长度组织一个定长的头存储数据区的长度。数据体是序列化的数据按16字节取倍数(数据的余数补足16字节。) 接收方取了头,取出长度再取制定长的数据体 倒是不出错了。 看到这个问题,为了回顾一下以前学过的这些知识,刚刚写了个最最简单的通信,我用了2种方法,一种是用socket,另一种是用TcpListener,而且我都只写了服务端,希望对你有所帮助*******第一个用socket Socket server; Socket client; Thread s; IPAddress ip = IPAddress.Parse("127.0.0.1"); int prot = 6000; /// <summary> /// 开始监听 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { //输入IP try { ip = IPAddress.Parse(textBox1.Text); } catch { textBox3.Text = "你输入的IP地址格式不正确,请重新输入!"; } //输入端口 try { int i = Int32.Parse(textBox2.Text); if (i >= 0 && i <= 65535) { prot = i; } else { textBox3.Text = "请输入0--65535之间的数字"; } } catch { textBox3.Text = "请输入端口号!"; } //开始监听 try { s = new Thread(new ThreadStart(start)); s.Start(); } catch (Exception ee) { textBox3.Text = ee.Message; } } /// <summary> /// 监听请求 /// </summary> void start() { //创建套接字 server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //绑定到本地 IPEndPoint ipPoint = new IPEndPoint(ip, prot); server.Bind(ipPoint); //开始侦听 server.Listen(5); //接受客户端请求 client = server.Accept(); textBox3.Text = "主机" + Dns.GetHostName() + "端口" + textBox2.Text + "开始监听........"; if (client.Connected) { textBox3.Text = "与客户端建立了连接"; while (true) { //接受数据 byte[] data = new byte[1024]; int size = client.Receive(data); string result = Encoding.Unicode.GetString(data, 0, size); if (result == "[---===退出===---]") { textBox3.Text = "你失去了与客户端的联系"; server.Shutdown(SocketShutdown.Both); server.Close(); } //richTextBox1.AppendText(result); else { string[] aa = result.Split('~'); richTextBox1.AppendText(aa[0] + "说:" + aa[1] + "\r"); } } } } private void button4_Click(object sender, EventArgs e) { try { //发送数据 //byte[] msg = Encoding.Unicode.GetBytes(richTextBox2.Text); byte[] msg = Encoding.Unicode.GetBytes(Dns.GetHostName() + "~" + richTextBox2.Text); client.Send(msg); //这里既可以将BYTE类型的数组从新解码再输出也可以直接输出richTextBox2中的文本 string aa = Encoding.Unicode.GetString(msg); //richTextBox1.AppendText("本机" + aa + "\r"); richTextBox1.AppendText(Dns.GetHostName() + "说:" + richTextBox2.Text + "\r"); richTextBox2.Clear(); } catch { textBox3.Text = "尚未进行连接,无法发送信息!"; } } private void button2_Click(object sender, EventArgs e) { richTextBox2.Clear(); } private void button3_Click(object sender, EventArgs e) { if (MessageBox.Show("你是否打开了监听?", "提示信息", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) { if (MessageBox.Show("你确定要关闭窗体吗?", "提示信息", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) { try { server.Shutdown(SocketShutdown.Both); server.Close(); Application.Exit(); } catch { textBox3.Text = "你尚未打开监听,此操作无效"; } } } else { Application.Exit(); } }*****************第二中用了TcpListener //用于侦听客户端的连接 TcpListener listener = null; //用于收发数据 NetworkStream stream = null; //接收客户端连接的线程 Thread serverstart = null; //接收客户端数据的线程 Thread receviestart = null; /// <summary> /// 开始聊天 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button1_Click(object sender, EventArgs e) { label1.Text = "正在开始...."; //可以不加IP地址,系统将自动为你的机器分配IP地址 listener = new TcpListener(9050); listener.Start(5); serverstart = new Thread(new ThreadStart(server)); serverstart.Start(); } void server() { label1.Text = "等待客户端的连接"; TcpClient client = listener.AcceptTcpClient(); label1.Text = "客户端连接成功"; button1.Enabled = false; button2.Enabled = true; button3.Enabled = true; stream = client.GetStream(); receviestart = new Thread(new ThreadStart(recevie)); receviestart.Start(); } void recevie() { while (true) { byte[] data = new byte[1024]; int size = stream.Read(data,0,data.Length); if (size == 0) break; string result = Encoding.Unicode.GetString(data,0,size); richTextBox1.AppendText(result+"\r\n"); } receviestart.Abort(); serverstart.Abort(); stream.Close(); listener.Stop(); button1.Enabled = true; button2.Enabled = false; button3.Enabled = false; label1.Text = "已经停止了聊天!"; } /// <summary> /// 发送消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void button2_Click(object sender, EventArgs e) { sendmsg(richTextBox2.Text); richTextBox2.Clear(); } void sendmsg(string msg) { byte[] data = Encoding.Unicode.GetBytes(msg); try { stream.Write(data, 0, data.Length); stream.Flush(); } catch { receviestart.Abort(); serverstart.Abort(); stream.Close(); listener.Stop(); button1.Enabled = true; button2.Enabled = false; button3.Enabled = false; label1.Text = "已经停止了聊天!"; } } TCP与UDP的差别就在于前者是可靠的面向连接的流协议,后者是不可靠的无连接的包协议。所以,采用TCP协议发送的数据,到达接收端时只保证字节流的顺序,但是每一个字节何时到达是不能预知的。而UDP发送的数据,是作为一个完整的包到达接收端的,不会出现分包的问题。因此,为了接收TCP协议的数据包,必须由应用层判断数据包的开始和结束。技术上有很多种手段,比如采用固定的起始、结束标志,或者按照固定的数据结构来解析。 fengyecsdn说的很对!我说一下我的方法,也是学别人的:发送端:每次发送正式数据前,先计算一下要发送的字节数(长度),然后先发送这个长度值(设定为4个字节),然后接着发送数据;接收端:先接受4个字节,转化为长度值,然后再始接受。 其实 就是你一顶要有自己的报头我一般是这样协议标记(4字节,一个INT32)本协议内是固定的频道号(4字节,一个INT32)我的个人习惯,我一般将一套程序的不同部分的数据传输用频道区分,这样在数据重组和转发上很方便指令标记(4字节,一个INT32)本协议内制定了几种,但总归是有限的几种或者只有一种数据体加密压缩KEY(8字节,一个LONG)根据需要这里存的是用来加解密或者压缩的KEY值。数据包本身长度(8字节,一个LONG)就是有效数据的长度(我一般是实际长度然后补加上N个空字节,使数据体长度成为16字节的倍数 N=16+x ,X是16减去数据体本身长度除16的余数的结果)。保留段(4字节,一个INT32)备用其他特色信息数据体(N字节,N是16的整数倍数) 比如数据体序列化本身为 165字节,那么数据体包装后是192字节(165+11+16)加上数据报头的32字节 一共发送224字节接收放先读32字节头 分析后再读192 数据体超过长度的时候就要分片发送和接收我一般是按2K或者4K分一片, 在我的数据体里都标记好指令号 分片序号 总分片号 接收方准备队列数组,接到信息直接压到对应数组里。 满了就排序,反序列,OK就成功读取。如果出现错误 ,丢弃,向对方发送失败指令,并指定刚才的指令号。 这个方法和我刚刚想到的方法基本上用的是同一个方法"Buffer.BlockCopy",实现起来一定也很相似吧!不过还是特别感谢这位朋友! 晕,原来是这样的。并不是使用NetStream你没有收到正确的数据,而是你没有正确的解释吧?是协议设计的问题吧。 其实LZ的原始问题里是有些逻辑错误没处理好接收数据的问题。不过 NetStream也不是没有问题。 网上一些帖子也说过它发送大量数据的时候无法保证数据的正确顺序。比如我说的数据头中如果不在数据体后加一定的空字节补充成16的倍数,就有接收数据不完整的情况。很让人费解。 帮忙看一下这个登录验证的错误 弱弱的问:是不是继承类就一定比基类的功能(方法)多 c# winfrom编程 导出EXCEL文件出错 如何在窗体上根据鼠标的位置改变一个label的位置呢? button中click代码 datagrid能否自己手工添加数据 菜鸟问题:在C#中如何根据条件查询、更新dataset? C# winform .net4.0 数据库操作问题?(如何添加,保存,修改)想了几天了,请高手帮忙!!!! c#中字符串函数有哪些?我怎么找不到Replce等函数了? radioButton1.Checked==true 出错? C#的get,set 问题
至于数据的重组,我一般是用一个自己的cache来完成这样的工作。
主要就是如果我定义一个6个字节的字节数组,那么如果我发送的数据不能填满6个字节的话,那么TCP就会分次接收完,如果一次填满6个字节的话就只需要接收一次,而且如果第一次把6个字节填满了,如果第二次没填满的话,多余的那几个字节会用上一次填的字节去补上,就是不知这是为什么!
不过后来我把数据改成定长最大3K 缓冲区设4K 每次发送完4K,每次接收完4K就不出错了。
然后再改一下,吧数据长度组织一个定长的头存储数据区的长度。数据体是序列化的数据按16字节取倍数(数据的余数补足16字节。) 接收方取了头,取出长度再取制定长的数据体 倒是不出错了。
*******第一个用socket Socket server;
Socket client;
Thread s;
IPAddress ip = IPAddress.Parse("127.0.0.1");
int prot = 6000;
/// <summary>
/// 开始监听
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
//输入IP
try
{
ip = IPAddress.Parse(textBox1.Text);
}
catch
{
textBox3.Text = "你输入的IP地址格式不正确,请重新输入!";
}
//输入端口
try
{
int i = Int32.Parse(textBox2.Text);
if (i >= 0 && i <= 65535)
{
prot = i;
}
else
{
textBox3.Text = "请输入0--65535之间的数字";
}
}
catch
{
textBox3.Text = "请输入端口号!";
}
//开始监听
try
{
s = new Thread(new ThreadStart(start));
s.Start();
}
catch (Exception ee)
{
textBox3.Text = ee.Message;
}
}
/// <summary>
/// 监听请求
/// </summary>
void start()
{
//创建套接字
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定到本地
IPEndPoint ipPoint = new IPEndPoint(ip, prot);
server.Bind(ipPoint);
//开始侦听
server.Listen(5);
//接受客户端请求
client = server.Accept();
textBox3.Text = "主机" + Dns.GetHostName() + "端口" + textBox2.Text + "开始监听........";
if (client.Connected)
{
textBox3.Text = "与客户端建立了连接";
while (true)
{
//接受数据
byte[] data = new byte[1024];
int size = client.Receive(data);
string result = Encoding.Unicode.GetString(data, 0, size);
if (result == "[---===退出===---]")
{
textBox3.Text = "你失去了与客户端的联系";
server.Shutdown(SocketShutdown.Both);
server.Close();
}
//richTextBox1.AppendText(result);
else
{
string[] aa = result.Split('~');
richTextBox1.AppendText(aa[0] + "说:" + aa[1] + "\r");
}
}
}
} private void button4_Click(object sender, EventArgs e)
{
try
{
//发送数据
//byte[] msg = Encoding.Unicode.GetBytes(richTextBox2.Text);
byte[] msg = Encoding.Unicode.GetBytes(Dns.GetHostName() + "~" + richTextBox2.Text);
client.Send(msg);
//这里既可以将BYTE类型的数组从新解码再输出也可以直接输出richTextBox2中的文本
string aa = Encoding.Unicode.GetString(msg);
//richTextBox1.AppendText("本机" + aa + "\r");
richTextBox1.AppendText(Dns.GetHostName() + "说:" + richTextBox2.Text + "\r");
richTextBox2.Clear();
}
catch
{
textBox3.Text = "尚未进行连接,无法发送信息!";
}
} private void button2_Click(object sender, EventArgs e)
{
richTextBox2.Clear();
} private void button3_Click(object sender, EventArgs e)
{
if (MessageBox.Show("你是否打开了监听?", "提示信息", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes)
{
if (MessageBox.Show("你确定要关闭窗体吗?", "提示信息", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes)
{
try
{
server.Shutdown(SocketShutdown.Both);
server.Close();
Application.Exit();
}
catch
{
textBox3.Text = "你尚未打开监听,此操作无效";
}
}
}
else
{
Application.Exit();
}
}*****************第二中用了TcpListener //用于侦听客户端的连接
TcpListener listener = null;
//用于收发数据
NetworkStream stream = null;
//接收客户端连接的线程
Thread serverstart = null;
//接收客户端数据的线程
Thread receviestart = null;
/// <summary>
/// 开始聊天
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
label1.Text = "正在开始....";
//可以不加IP地址,系统将自动为你的机器分配IP地址
listener = new TcpListener(9050);
listener.Start(5);
serverstart = new Thread(new ThreadStart(server));
serverstart.Start();
}
void server()
{
label1.Text = "等待客户端的连接";
TcpClient client = listener.AcceptTcpClient();
label1.Text = "客户端连接成功";
button1.Enabled = false;
button2.Enabled = true;
button3.Enabled = true;
stream = client.GetStream();
receviestart = new Thread(new ThreadStart(recevie));
receviestart.Start();
}
void recevie()
{
while (true)
{
byte[] data = new byte[1024];
int size = stream.Read(data,0,data.Length);
if (size == 0)
break;
string result = Encoding.Unicode.GetString(data,0,size);
richTextBox1.AppendText(result+"\r\n");
}
receviestart.Abort();
serverstart.Abort();
stream.Close();
listener.Stop();
button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = false;
label1.Text = "已经停止了聊天!"; }
/// <summary>
/// 发送消息
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
sendmsg(richTextBox2.Text);
richTextBox2.Clear();
}
void sendmsg(string msg)
{
byte[] data = Encoding.Unicode.GetBytes(msg);
try
{
stream.Write(data, 0, data.Length);
stream.Flush();
}
catch
{
receviestart.Abort();
serverstart.Abort();
stream.Close();
listener.Stop();
button1.Enabled = true;
button2.Enabled = false;
button3.Enabled = false;
label1.Text = "已经停止了聊天!";
}
}
所以,采用TCP协议发送的数据,到达接收端时只保证字节流的顺序,但是每一个字节何时到达是不能预知的。
而UDP发送的数据,是作为一个完整的包到达接收端的,不会出现分包的问题。
因此,为了接收TCP协议的数据包,必须由应用层判断数据包的开始和结束。
技术上有很多种手段,比如采用固定的起始、结束标志,或者按照固定的数据结构来解析。
发送端:每次发送正式数据前,先计算一下要发送的字节数(长度),然后先发送这个长度值(设定为4个字节),然后接着发送数据;
接收端:先接受4个字节,转化为长度值,然后再始接受。
频道号(4字节,一个INT32)我的个人习惯,我一般将一套程序的不同部分的数据传输用频道区分,这样在数据重组和转发上很方便
指令标记(4字节,一个INT32)本协议内制定了几种,但总归是有限的几种或者只有一种
数据体加密压缩KEY(8字节,一个LONG)根据需要这里存的是用来加解密或者压缩的KEY值。
数据包本身长度(8字节,一个LONG)就是有效数据的长度(我一般是实际长度然后补加上N个空字节,使数据体长度成为16字节的倍数 N=16+x ,X是16减去数据体本身长度除16的余数的结果)。
保留段(4字节,一个INT32)备用其他特色信息数据体(N字节,N是16的整数倍数) 比如数据体序列化本身为 165字节,那么数据体包装后是192字节(165+11+16)
加上数据报头的32字节 一共发送224字节接收放先读32字节头 分析后再读192
我一般是按2K或者4K分一片, 在我的数据体里都标记好
指令号 分片序号 总分片号 接收方准备队列数组,接到信息直接压到对应数组里。 满了就排序,反序列,OK就成功读取。如果出现错误 ,丢弃,向对方发送失败指令,并指定刚才的指令号。
不过 NetStream也不是没有问题。 网上一些帖子也说过它发送大量数据的时候无法保证数据的正确顺序。比如我说的数据头中如果不在数据体后加一定的空字节补充成16的倍数,就有接收数据不完整的情况。很让人费解。