C#开始/结束接收-如何读取大数据?
当以1024字节为单位读取数据时,如何继续从接收大于1024字节的消息的套接字读取数据,直到没有数据为止?我是否应该只使用BeginReceive读取数据包的长度前缀,然后在检索到该前缀后,使用Receive()(在异步线程中)读取数据包的其余部分?还是有别的办法 编辑: 我以为Jon Skeet的链接有解决方案,但代码中有一点减速。我使用的代码是:C#开始/结束接收-如何读取大数据?,c#,networking,sockets,asynchronous,C#,Networking,Sockets,Asynchronous,当以1024字节为单位读取数据时,如何继续从接收大于1024字节的消息的套接字读取数据,直到没有数据为止?我是否应该只使用BeginReceive读取数据包的长度前缀,然后在检索到该前缀后,使用Receive()(在异步线程中)读取数据包的其余部分?还是有别的办法 编辑: 我以为Jon Skeet的链接有解决方案,但代码中有一点减速。我使用的代码是: public class StateObject { public Socket workSocket = null; publi
public class StateObject
{
public Socket workSocket = null;
public const int BUFFER_SIZE = 1024;
public byte[] buffer = new byte[BUFFER_SIZE];
public StringBuilder sb = new StringBuilder();
}
public static void Read_Callback(IAsyncResult ar)
{
StateObject so = (StateObject) ar.AsyncState;
Socket s = so.workSocket;
int read = s.EndReceive(ar);
if (read > 0)
{
so.sb.Append(Encoding.ASCII.GetString(so.buffer, 0, read));
if (read == StateObject.BUFFER_SIZE)
{
s.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0,
new AyncCallback(Async_Send_Receive.Read_Callback), so);
return;
}
}
if (so.sb.Length > 0)
{
//All of the data has been read, so displays it to the console
string strContent;
strContent = so.sb.ToString();
Console.WriteLine(String.Format("Read {0} byte from socket" +
"data = {1} ", strContent.Length, strContent));
}
s.Close();
}
现在,这种修正方法在大多数情况下都可以正常工作,但当数据包的大小是缓冲区的倍数时,它就失败了。原因是,如果缓冲区在读取时被填满,则假定有更多数据;但同样的问题还是发生了。对于exmaple,一个2字节的缓冲区在一个4字节的数据包上被填充两次,并假设有更多的数据。然后它会阻塞,因为没有什么可读的了问题是接收函数不知道数据包何时结束。
这让我想到了两种可能的解决方案:要么我可以有一个数据包结束分隔符,要么我可以读取数据包头以找到长度,然后准确地接收该数量(正如我最初建议的那样) 不过,每一个都有问题。我不喜欢使用分隔符的想法,因为用户可能会以某种方式将其从应用程序输入到一个字符串中的数据包中,并将其搞糟。这对我来说也有点草率 长度头听起来不错,但我计划使用协议缓冲区——我不知道数据的格式。有长度标题吗?它有多少字节?这是我自己实现的吗?等等
我该怎么办?否-再次从回调处理程序调用
BeginReceive
,直到EndReceive
返回0。基本上,您应该继续异步接收,假设您想要异步IO的最大好处
如果您查看MSDN页面,您将看到一个示例。(无可否认,这并不像可能的那样容易理解。)您应该先阅读长度前缀。一旦你有了这些,你就可以继续以块的形式读取字节(你可以异步读取,正如你推测的那样),直到你用完了你知道的从线路上输入的字节数
请注意,在某些情况下,在读取最后一个块时,您可能不希望读取完整的1024字节,这取决于长度前缀所表示的总字节数以及您已读取的字节数。有关信息(一般开始/结束用法),您可能希望查看;这种方法对我来说很有效,而且省去了很多痛苦…当。考虑到那些已经参与进来的政要们,我甚至不敢回答这个问题,但接下来就是了。伟大的人们啊,要温柔 如果没有阅读Marc的博客的好处(由于公司的互联网政策,它在这里被屏蔽),我将提供“另一种方式” 在我看来,诀窍是将数据的接收与数据的处理分开 我使用这样定义的StateObject类。它与MSDN StateObject实现的不同之处在于,它不包括StringBuilder对象,缓冲区大小常量是私有的,并且为了方便起见,它包括一个构造函数
public class StateObject
{
private const int BUFFER_SIZE = 65535;
public byte[] Buffer = new byte[BUFFER_SIZE];
public readonly Socket WorkSocket = null;
public StateObject(Socket workSocket)
{
WorkSocket = workSocket;
}
}
我还有一个Packet类,它只是一个围绕缓冲区和时间戳的包装器
public class Packet
{
public readonly byte[] Buffer;
public readonly DateTime Timestamp;
public Packet(DateTime timestamp, byte[] buffer, int size)
{
Timestamp = timestamp;
Buffer = new byte[size];
System.Buffer.BlockCopy(buffer, 0, Buffer, 0, size);
}
}
我的ReceiveCallback()函数如下所示
public static ManualResetEvent PacketReceived = new ManualResetEvent(false);
public static List<Packet> PacketList = new List<Packet>();
public static object SyncRoot = new object();
public static void ReceiveCallback(IAsyncResult ar)
{
try {
StateObject so = (StateObject)ar.AsyncState;
int read = so.WorkSocket.EndReceive(ar);
if (read > 0) {
Packet packet = new Packet(DateTime.Now, so.Buffer, read);
lock (SyncRoot) {
PacketList.Add(packet);
}
PacketReceived.Set();
}
so.WorkSocket.BeginReceive(so.Buffer, 0, so.Buffer.Length, 0, ReceiveCallback, so);
} catch (ObjectDisposedException) {
// Handle the socket being closed with an async receive pending
} catch (Exception e) {
// Handle all other exceptions
}
}
public static ManualResetEvent PacketReceived=新的ManualResetEvent(false);
公共静态列表PacketList=新列表();
公共静态对象SyncRoot=新对象();
公共静态void ReceiveCallback(IAsyncResult ar)
{
试一试{
StateObject so=(StateObject)ar.AsyncState;
int read=so.WorkSocket.EndReceive(ar);
如果(读取>0){
数据包=新数据包(DateTime.Now,so.Buffer,read);
锁定(同步根){
PacketList.Add(包);
}
PacketReceived.Set();
}
so.WorkSocket.BeginReceive(so.Buffer,0,so.Buffer.Length,0,ReceiveCallback,so);
}捕获(ObjectDisposedException){
//在异步接收挂起的情况下处理正在关闭的套接字
}捕获(例外e){
//处理所有其他异常
}
}
请注意,此实现绝对不处理接收到的数据,也不期望接收到多少字节。它只需接收套接字上的任何数据(最多65535字节),并将该数据存储在数据包列表中,然后立即排队等待另一个异步接收
由于处理每个异步接收的线程不再进行处理,因此数据显然将由不同的线程处理,这就是为什么Add()操作通过lock语句同步的原因。此外,处理线程(无论是主线程还是其他专用线程)需要知道何时有数据要处理。要做到这一点,我通常使用ManualResetEvent,这就是我上面所展示的
以下是处理过程的工作原理
static void Main(string[] args)
{
Thread t = new Thread(
delegate() {
List<Packet> packets;
while (true) {
PacketReceived.WaitOne();
PacketReceived.Reset();
lock (SyncRoot) {
packets = PacketList;
PacketList = new List<Packet>();
}
foreach (Packet packet in packets) {
// Process the packet
}
}
}
);
t.IsBackground = true;
t.Name = "Data Processing Thread";
t.Start();
}
static void Main(字符串[]args)
{
螺纹t=新螺纹(
代表(){
列出数据包;
while(true){
PacketReceived.WaitOne();
PacketReceived.Reset();
锁定(同步根){
数据包=数据包列表;
PacketList=新列表();
}
foreach(分组中的分组){
//处理数据包
}
}
}
);
t、 IsBackground=true;
t、 Name=“数据处理线程”;
t、 Start();
}
这是我用于所有套接字通信的基本基础结构。它在数据接收和数据处理之间提供了很好的分离
至于你问的另一个问题,是
public static void Read_Callback(IAsyncResult ar)
{
StateObject so = (StateObject) ar.AsyncState;
Socket s = so.workSocket;
int read = s.EndReceive(ar);
if (read > 0)
{
so.sb.Append(Encoding.ASCII.GetString(so.buffer, 0, read));
if (s.Available == 0)
{
// All data received, process it as you wish
}
}
// Listen for more data
s.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0,
new AyncCallback(Async_Send_Receive.Read_Callback), so);
}