Socket编程异步接收大文件的问题

ourbone 2009-05-24 05:24:54
public class RemoteClient
{
private TcpClient client;
private NetworkStream streamToClient;
private const int BufferSize = 8192;
private byte[] buffer;
private ProtocolHandler handler;
private FileStream fs;

public RemoteClient(TcpClient client)
{
this.client = client;

// 获得流
streamToClient = client.GetStream();
buffer = new byte[BufferSize];
tempFilePath = Path.GetTempFileName();
fs = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write);
// 设置RequestHandler
handler = new ProtocolHandler();

}
// 开始进行读取
public void BeginRead()
{
AsyncCallback callBack = new AsyncCallback(OnReadComplete);
streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);

}

// 再读取完成时进行回调
private void OnReadComplete(IAsyncResult ar)
{
int bytesRead = 0;
try
{
//此处如果超出设置的缓冲区的值,则只能接收客户端上传的部分数据
bytesRead = streamToClient.EndRead(ar);
//解压文件
fs.Write(CCompress.DecompressBytes(buffer), 0, bytesRead);
fs.Close();
fs.Dispose();
// 这里异步调用
ParameterizedThreadStart start = new ParameterizedThreadStart(handleProtocol);
start.BeginInvoke(tempFilePath, null, null);

streamToClient.Flush();
// 再次调用BeginRead(),完成时调用自身,形成无限循环
lock (streamToClient)
{
AsyncCallback callBack = new AsyncCallback(OnReadComplete);
streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);
}
}
catch (Exception ex)
{
Dispose();
}
}
……
}


在上面的代码中,流设置缓冲区的大小为BufferSize =8192,而在客户端我是采用上传文件的方式,一次性将一个文件完整的写入网络流。如果上传的文件大于设置的缓冲区大小,那么就导致在服务器端不能完整的接收客户上传的文件,上面的代码如何修改,以便能实现客户端一次性上传一个大文件,而服务器在异步执行操作时能完整的接收
...全文
477 18 打赏 收藏 转发到动态 举报
写回复
用AI写文章
18 条回复
切换为时间正序
请发表友善的回复…
发表回复
qldsrx 2009-05-26
  • 打赏
  • 举报
回复
结帖好快,看过上面那段代码,还是有问题的,且不说文件写了两遍,效率很低。那个while也用得不当,如果能出结果,那说明运气不错,明眼人一看这里面就是函数的递归调用,递归调用里用while是很危险的,应该改为if,后面的全部放入else。
ourbone 2009-05-26
  • 打赏
  • 举报
回复
感谢大家热情帮忙,最后我在MSDN上找到了解决方式,修改代码如下:

private void OnReadComplete(IAsyncResult ar)
{
int bytesRead = 0;
try
{

NetworkStream myNetworkStream = (NetworkStream)ar.AsyncState;
bytesRead = myNetworkStream.EndRead(ar);
fs = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write);
fs.Write(buffer, 0, buffer.Length);
fs.Close();
while (myNetworkStream.DataAvailable)
{
myNetworkStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(OnReadComplete), myNetworkStream);
}

byte[] rerultBytes = File.ReadAllBytes(tempFilePath);
byte[] data = CCompress.DecompressBytes(rerultBytes);
fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);
fs.Close();
fs.Dispose();
// 这里异步调用,不然这里可能会比较耗时
ParameterizedThreadStart start = new ParameterizedThreadStart(handleProtocol);
start.BeginInvoke(tempFilePath, null, null);

}
catch (Exception ex)
{
Dispose();
}
}
qldsrx 2009-05-25
  • 打赏
  • 举报
回复
[Quote=引用 13 楼 ourbone 的回复:]
10楼的qldsrx你好,如果采用bytesRead>0这种方式判断,读取出的值是大于零,这个值如果刚好是上传的实际值大小,那么就没有机会再执行else部分的代码。
[/Quote]

怎么会没有机会再执行else部分的代码?你在if语句中再次执行BeginRead()不就会再去读取数据了吗?这样最后不就可能读取的长度为0了吗?这可是MSDN上标准的判断方法,你不用怀疑这种判断方式了。另外为了出现异常时也可以关闭文件流,你需要在catch块中也添加fs.close(),close之前先判断fs是否还存在,免得出现个对象不存在的异常哦。
ourbone 2009-05-25
  • 打赏
  • 举报
回复
[Quote=引用 14 楼 CGabriel 的回复:]
在文件流的头部加上文件流的长度标识位
[/Quote]

我在客户端的发送文件代码如下:

public void SendMessage(string filePath)
{
try
{
byte[] data = File.ReadAllBytes(filePath);
//压缩文件
data = CCompress.CompressBytes(data);
lock (streamToServer)
{
streamToServer.Write(data, 0, data.Length); // 发往服务器
}
}
catch (Exception ex)
{
throw ex;
}
}

能否说说在客户端如何加上长度,在服务器又如何首先读出其长度?
CGabriel 2009-05-25
  • 打赏
  • 举报
回复
在文件流的头部加上文件流的长度标识位
ourbone 2009-05-25
  • 打赏
  • 举报
回复
10楼的qldsrx你好,如果采用bytesRead>0这种方式判断,读取出的值是大于零,这个值如果刚好是上传的实际值大小,那么就没有机会再执行else部分的代码。
koko0123 2009-05-25
  • 打赏
  • 举报
回复
学习了
starj1 2009-05-25
  • 打赏
  • 举报
回复
学习下,这方面的还不懂...
qldsrx 2009-05-24
  • 打赏
  • 举报
回复
用这个IF判断,如果bytesRead>0,说明读取到数据,那么你就可以写入文件,否则就可以关闭文件,一次读取结束。streamToClient也可以一起关闭,你自己组织下需要执行的代码,放到if-else中执行即可。

if(bytesRead>0)
{
fs.Write(CCompress.DecompressBytes(buffer), 0, bytesRead);
}
else
{
fs.Close();
}
qldsrx 2009-05-24
  • 打赏
  • 举报
回复
楼主还在吗?
问题应该是出在这里了

fs.Close();
fs.Dispose();


你在第一次读取回调时就将FileStream的对象fs给关闭了,第二次再想写入就不行了,你应该在全部读完后再关闭fs。等会给你整理下代码。
ourbone 2009-05-24
  • 打赏
  • 举报
回复
以下是调用BeginRead的代码

public class CSocketServer
{
public void StartService()
{
//eSys.DbManager.CDbManager dbManager = new eSys.DbManager.CDbManager();
try
{
CServerGlobalData.ServerPort = CServerDataHandle.Port;
TcpListener listener = new TcpListener(IPAddress.Any, CServerGlobalData.ServerPort);


listener.Start();

while (true)
{

// 获取一个连接,同步方法,在此处中断
TcpClient client = listener.AcceptTcpClient();
RemoteClient wapper = new RemoteClient(client);
wapper.BeginRead();
}

}
catch (Exception ex)
{
CLog.Write("服务启动异常:" + ex);
}
}
}


上传小的文件如果小于8192字节的没有问题,如果大于8192那么,只上传了一部分数据。
qldsrx 2009-05-24
  • 打赏
  • 举报
回复
如果我没有猜错,streamToClient被你给锁住了,新的线程无法再读取了。
qldsrx 2009-05-24
  • 打赏
  • 举报
回复
晕,你有调用啊,不过干吗不直接写BeginRead,还那么麻烦用个锁,把锁拆了试试
qldsrx 2009-05-24
  • 打赏
  • 举报
回复
我感觉是BeginRead这部分你没有重复调用,一般在OnReadComplete内部需要继续调用BeginRead,判断是否还有数据,没有的话就关闭.
ourbone 2009-05-24
  • 打赏
  • 举报
回复
也就是这一句:
bytesRead = streamToClient.EndRead(ar);
实际读出的值按设置的缓冲区的大小取到的值。如果上传的文件大于缓冲区设置的值,那么bytesRead就取不到真实大小。
peihexian 2009-05-24
  • 打赏
  • 举报
回复
很明显,要做拆包处理,假设你要传输的是个dvd电影,而且是avi格式的,动辄1个G以上,你不可能一次性的发送到网络上,什么样的网络也撑不住你一次写入1G多的字节数据,你可以拆包成小的数据包,采取少量多次的方式发送,即一次发送几K的数据,对方接收以后做缓存或临时写硬盘处理,最后收到结束包的特殊数据包以后再合并一下。
qldsrx 2009-05-24
  • 打赏
  • 举报
回复
只有部分代码,有点晕,没看懂。
ourbone 2009-05-24
  • 打赏
  • 举报
回复
我自己先顶一下

110,545

社区成员

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

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

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