情况很简单,我将.NET服务端Socket异步程序封装为类,并抛出一个事件,在外部调用了此事件,将收到服务端的TCP数据。于是,我在UI线程中(一个WinForm窗体类代码中)实例化此接收类,并订阅事件,实时接收TCP数据,并且将一些数据提取于界面上,现在的问题是:我的数据常常丢包,客户端可能发过来10条数据,但UI线程上只显示3、4条。这是为什么?
废话不多说,先上关键代码:这是我的异步Socket处理,其中SocketHelper封装一个byte[] 和Socket,用于异步间的状态保存。AsyncEventArgs是自定义的事件参数,e.AsyncResult为byte[],可得到一次TCP数据报。 /// <summary>
/// 消息抵达处理方法委托
/// </summary>
/// <param name="sender"></param>
/// <param name="e">信息参数</param>
public delegate void MessageArrivalHandler(object sender,AsyncEventArgs e); public class FluxSocketManager
{ private Socket socket; /// <summary>
/// 消息抵达事件
/// </summary>
public event MessageArrivalHandler MessageArrivalEvent; private Socket sendSocket; #endregion public void BeginReceive()
{
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint end = new IPEndPoint(IPAddress.Any, Int32.Parse(ConfigInfo.GetInstance().DevicePort));
socket.Bind(end); socket.Listen(100);
//开始异步监听
socket.BeginAccept(new AsyncCallback(Accept), socket);
}
catch (Exception ex)
{ }
} void Accept(IAsyncResult ia)
{
Socket soc = ia.AsyncState as Socket; try
{
Socket worker = soc.EndAccept(ia); soc.BeginAccept(new AsyncCallback(Accept), soc); byte[] buf = new byte[1];
EntityInfo.SocketHelper helper = new EntityInfo.SocketHelper(buf, worker); //开始异步接收
worker.BeginReceive(buf, 0, 1, SocketFlags.None, new AsyncCallback(Receive), helper);
}
catch (Exception ex)
{ }
} void Receive(IAsyncResult ia)
{
try
{
EntityInfo.SocketHelper helper = ia.AsyncState as EntityInfo.SocketHelper;
Socket worker = helper.s;
worker.EndReceive(ia); byte[] buffer = new byte[worker.Available + 1];
worker.Receive(buffer, 1, buffer.Length - 1, SocketFlags.None);
buffer[0] = helper.b[0]; this.sendSocket = worker; //调用事件绑定的方法
OnMessageArrival(new AsyncEventArgs(buffer)); worker.BeginReceive(helper.b, 0, 1, SocketFlags.None, new AsyncCallback(Receive), helper);
}
catch (Exception ex)
{ }
} public void SendBack(byte[] buffer)
{
try
{
this.sendSocket.Send(buffer);
}
catch (Exception ex)
{ }
} private void OnMessageArrival(AsyncEventArgs e)
{
if (this.MessageArrivalEvent != null)
{
this.MessageArrivalEvent(this, e);
}
}
}至于UI线程就比较简单,在Form.cs中代码为: public partial class MainForm : Form
{
private class FluxSocketManager mySocket = new FluxSocketManager(); public MainForm()
{
InitializeComponent();
mySocket.MessageArrivalEvent += new FluxSocketManager.MessageArrivalEvent(mySocket_MessageArrivalEvent);
Control.CheckForIllegalCrossThreadCalls = false;
} /// <summary>
/// 当收到客户端的数据时触发此方法,UI线程丢失包的情况就在这里!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void mySocket_MessageArrivalEvent(object sender, AsyncEventArgs e)
{
byte[] message = e.AsynsBytes; //e.AsynsBytes即为收到的字节数组 //将收到的message用于处理UI
}
}
有一个问题要注意,就是客户端数量较少(一般1个客户端),而且发送频率快,但是事件中的消息处理花费时间比较长,例如保存一个3m大小的图片……
那么为什么UI线程上异步会丢包呢?如何解决这个问题?
深夜发帖,确实因为这个问题困扰已久,请sp1234或者其他高手协助!谢谢!
废话不多说,先上关键代码:这是我的异步Socket处理,其中SocketHelper封装一个byte[] 和Socket,用于异步间的状态保存。AsyncEventArgs是自定义的事件参数,e.AsyncResult为byte[],可得到一次TCP数据报。 /// <summary>
/// 消息抵达处理方法委托
/// </summary>
/// <param name="sender"></param>
/// <param name="e">信息参数</param>
public delegate void MessageArrivalHandler(object sender,AsyncEventArgs e); public class FluxSocketManager
{ private Socket socket; /// <summary>
/// 消息抵达事件
/// </summary>
public event MessageArrivalHandler MessageArrivalEvent; private Socket sendSocket; #endregion public void BeginReceive()
{
try
{
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint end = new IPEndPoint(IPAddress.Any, Int32.Parse(ConfigInfo.GetInstance().DevicePort));
socket.Bind(end); socket.Listen(100);
//开始异步监听
socket.BeginAccept(new AsyncCallback(Accept), socket);
}
catch (Exception ex)
{ }
} void Accept(IAsyncResult ia)
{
Socket soc = ia.AsyncState as Socket; try
{
Socket worker = soc.EndAccept(ia); soc.BeginAccept(new AsyncCallback(Accept), soc); byte[] buf = new byte[1];
EntityInfo.SocketHelper helper = new EntityInfo.SocketHelper(buf, worker); //开始异步接收
worker.BeginReceive(buf, 0, 1, SocketFlags.None, new AsyncCallback(Receive), helper);
}
catch (Exception ex)
{ }
} void Receive(IAsyncResult ia)
{
try
{
EntityInfo.SocketHelper helper = ia.AsyncState as EntityInfo.SocketHelper;
Socket worker = helper.s;
worker.EndReceive(ia); byte[] buffer = new byte[worker.Available + 1];
worker.Receive(buffer, 1, buffer.Length - 1, SocketFlags.None);
buffer[0] = helper.b[0]; this.sendSocket = worker; //调用事件绑定的方法
OnMessageArrival(new AsyncEventArgs(buffer)); worker.BeginReceive(helper.b, 0, 1, SocketFlags.None, new AsyncCallback(Receive), helper);
}
catch (Exception ex)
{ }
} public void SendBack(byte[] buffer)
{
try
{
this.sendSocket.Send(buffer);
}
catch (Exception ex)
{ }
} private void OnMessageArrival(AsyncEventArgs e)
{
if (this.MessageArrivalEvent != null)
{
this.MessageArrivalEvent(this, e);
}
}
}至于UI线程就比较简单,在Form.cs中代码为: public partial class MainForm : Form
{
private class FluxSocketManager mySocket = new FluxSocketManager(); public MainForm()
{
InitializeComponent();
mySocket.MessageArrivalEvent += new FluxSocketManager.MessageArrivalEvent(mySocket_MessageArrivalEvent);
Control.CheckForIllegalCrossThreadCalls = false;
} /// <summary>
/// 当收到客户端的数据时触发此方法,UI线程丢失包的情况就在这里!
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void mySocket_MessageArrivalEvent(object sender, AsyncEventArgs e)
{
byte[] message = e.AsynsBytes; //e.AsynsBytes即为收到的字节数组 //将收到的message用于处理UI
}
}
有一个问题要注意,就是客户端数量较少(一般1个客户端),而且发送频率快,但是事件中的消息处理花费时间比较长,例如保存一个3m大小的图片……
那么为什么UI线程上异步会丢包呢?如何解决这个问题?
深夜发帖,确实因为这个问题困扰已久,请sp1234或者其他高手协助!谢谢!
谢谢楼上,这个页面的几个Demo我也有看过,但就事论事的说,不太适合我的问题。
worker.Receive(buffer, 1, buffer.Length - 1, SocketFlags.None);
buffer[0] = helper.b[0];
this.sendSocket = worker;
//调用事件绑定的方法
OnMessageArrival(new AsyncEventArgs(buffer));
worker.BeginReceive(helper.b, 0, 1, SocketFlags.None, new AsyncCallback(Receive), helper);”
这一段代码写的一团糟,什么乱七八糟的,居然还有个worker.Receive,难怪你会发现数据丢失。你发现数据丢失实际上是数据覆盖了
粘包了。LZ这样处理比较巧妙,利用了异步的特性,同时又避免数据量大IO唤醒线程消耗的CPU
为什么我这里调用worker.Receive(),会将数据覆盖掉呢?
其实我说的数据丢失,是指一个很简单的东西:每条数据到来时,UI上必然要新增一个记录,问题是客户端确实发了10条以上的记录,但UI上仅显示了3、4条,所以,我对异步通讯与UI线程上的交互处理一直很疑惑,不知道为什么会产生这样的现象!
也谢谢你对本贴的关心!
因为开始你进入beginRecive的时候已经去TCP自带的缓冲区里取了一次数据,这次取的数据你是放在buff 里的,而你在void Receive(IAsyncResult ia)这个里面又去取了TCP自带的缓冲区里取了一次数据,而你却把第二次取得数据通知给UI了,那你第一次的数据哪去了?你有没有去检查一下你接受到的数据,如果我说的没错的话,那边第一次发的数据你应该永远都收不到
不是这样的,每次数据到来时都提取字段写一行ListView,有10条自然能生成10行,现在只有3行。而且,如果是多次数据重叠在一起,那肯定会提取失败,并且在日志中报错,现在不报错。
你不要把它弄成false
使用委托BeginInvoke 出现这样的错误。需要在接收后就记录日志来查。把问题分开去找。
而且边界没处理。会出现粘包的现象。一般会先把包的长度发过来,然后根据包的长度来申请bufferworker.Receive(buffer, 1, buffer.Length - 1, SocketFlags.None);
这个若是buffer大的话,还需要判断是否已经接收完了。否则继续接收。
(UDP协议是数据报协议,所以接收数据都是以数据包的形式进行的,理论上不会存在粘包)
从LZ贴出的代码来看~ 是不是可以解析成下面的过程?开始 Accept
--- 第一个包 ---
首先 BeginReceive 1字节
然后 EndReceive (实际只接收了第一个字节)
--- 第一个包接收完成 ---
--- 第二个包 ---
接着 Receive (从第二个字节起接收)
--- 第二个包接收完成 ---
……如果上述过程成立的话,那么就是LZ的异步接收机制直接丢弃了一半的数据包。