socket分段发送大文件的方法

stning 2009-07-28 09:57:27
请大家踊跃发表一件和贴代码。大量分分送上,up的也有分。
...全文
3447 37 打赏 收藏 转发到动态 举报
写回复
用AI写文章
37 条回复
切换为时间正序
请发表友善的回复…
发表回复
lisengl 2012-08-29
  • 打赏
  • 举报
回复
刚遇到这个问题,上来看看。
fengyecsdn 2009-07-29
  • 打赏
  • 举报
回复
其实发送1K和发送100M,在传输上没区别。
就是做保障方面要考虑周全。

我做过一个组件,代码现在不在电脑里。
所以我只讲下大概思路。楼主可以自己写出来的。

1:我的传输组件,每次传送任务包最大是2M。不建议再大了,再大,既不可靠,也浪费很多效率。如果应用层要发送大于2M的数据,则要求业务层自行处理切割。并发送指令通知对方业务层对这些包的处理方式。

2:每次任务中不大于2M的包,在组件内会呗切帧,每帧从16字节到2K都可以通过设置实现。当然,每个帧都要有帧头。帧头中包括协议信息,版本,地址等,还有帧序号,包序号,分组号等。

3:接收方户设立缓存,将接收的帧按照包序号分别缓存,并按帧序号排序,等待重组。 当某包序号的全部帧都接收到以后,则将这些帧重组为包,提交给应用层。
如果某个帧接到的数据出错,或者某帧序号出现跳跃,则接收方会向发送方发送一个通知,告知对方XX包的YY帧重发。
另外根据一定的算法,检查缓存中未完成的包和帧。如在某一定时间内该包都不再有新帧到达,或者已有的帧无法连接成为包,则丢弃该包已收到的全部帧,清空缓存,并发出通知,告知对方和本地应用层,XX包传输失败,已丢弃。

4:由于我的组件设计上是可以多包并行发送,且内设多个管道。 所以实际物理传输的数据可能是混杂的,帧尺寸也不同。所以开始作的时候,出现错误数据很多。后来发现 .NET 本身的TCPCLIENT或者 NETSTREAM对象在读写的时候常出现位数错误。比如发送126个字节,接受那实际是读128个字节,丢弃2字节。 (那些例子里 收发的都是缓冲区长度,有空数据来填补这些差位。但是我这里没有空数据补充。)所以后边的包就错啦。 后来想出个办法,所有的包都用16的倍数长度, 不足的用0来补充够16的倍数。问题解决。

我建议你也做成组件化的东西。别每次项目都写一些类似的重复的东西。



zhushoudong 2009-07-28
  • 打赏
  • 举报
回复
顶楼上的 说法,我觉得还是要看业务的需求
fengyecsdn 2009-07-28
  • 打赏
  • 举报
回复
上边的代码好长好长啊。。

其实我想简单说说思路。 楼主能提这样的问题,说明楼主对TCP或者UDP的通信实现已经完全没问题了。

要传递大文件,无非就是分块,之后考虑续传。
块分的多大 要考虑三方面, 一是你网络环境(速度啊、稳定性啊) 一个是你的业务需要(频率、数量等) 再就是你的性能要求。
块越大, 并发性,灵活性就差一些。 块越小,传输效率就差一些。

业务上分块 传输上 分帧, 加序号,设缓存, 愿意的话加验证, 最好加上时间戳。
十八道胡同 2009-07-28
  • 打赏
  • 举报
回复
顶个先,
「已注销」 2009-07-28
  • 打赏
  • 举报
回复
mark
sadikaqy 2009-07-28
  • 打赏
  • 举报
回复
建议一次不要申请太大的内存,每次固定读4k(或8k),读了就写入文件流,循环利用那个缓冲区。其他的文件流和那个缓冲区可以交给系统自己管理。只要你不要一次申请太多就好了。
LQknife 2009-07-28
  • 打赏
  • 举报
回复
先说麻烦事吧 我也做过,要是单个文件还好办定义好 包头 包体 包头里边是文件信息:文件名、文件大小等等
要是文件夹就麻烦了,接收方还要创建相应的文件夹和子目录,我是没实现 ^_^
最后是异常处理,断点续传,高级点能实现暂停功能
mjp1234airen4385 2009-07-28
  • 打赏
  • 举报
回复
要做数据校验的。
把你发送的数据用crc方法校验一下,把校验码打包到数据包里,
在服务端,取得数据后,再用同样的方法校验一下,得出的校验码和传送的校验码相同则本包有效,否则从新发送。
zzxap 2009-07-28
  • 打赏
  • 举报
回复
回调方法应调用 EndReceiveFrom 方法。当应用程序调用 BeginReceiveFrom 时,系统将会使用单独的线程来执行指定的回调方法,并将在 EndReceiveFrom 上一直阻止到 Socket 读取数据或引发异常为止。如果想要在调用 BeginReceiveFrom 方法后使原始线程阻止,请使用 WaitHandle.WaitOne。当需要原始线程继续执行时,请在回调方法中调用 T:System.Threading.ManualResetEvent 的 Set 方法。有关如何编写 callback 方法的其他信息,请参见 Callback 示例。

注意
在调用 BeginReceiveFrom 之前,必须使用 Bind 方法显式地将 Socket 绑定到本地终结点,否则 BeginReceiveFrom 将会引发 SocketException。


该方法将数据读入 buffer 参数中,并捕获从其发送数据的远程主机终结点。有关如何检索此终结点的信息,请参考 EndReceiveFrom。如果打算从未知主机或多个主机异步接收无连接的数据报,则最适合使用此方法。在这些情况下,BeginReceiveFrom 将会读取本地网络缓冲区接收到的第一个排队数据报。如果您接收到的数据报大于 buffer 的大小,则 BeginReceiveFrom 方法将在 buffer 中尽可能多地填充消息内容,并引发 SocketException。如果您使用的是不可靠协议,多余的数据将会丢失。而如果当前使用的是可靠协议,则服务提供程序将保留多余的数据,而且通过使用一个足够大的缓冲区调用 BeginReceiveFrom 方法来检索这些数据。

虽然 BeginReceiveFrom 是用于无连接协议的,但您同样可以使用面向连接的协议。如果选择这样做,则必须通过调用 Connect / BeginConnect 方法来建立远程主机连接,或者调用 Accept 或 BeginAccept 方法来接受传入的连接请求。如果在建立连接或接受连接之前就调用了 BeginReceiveFrom 方法,则您将得到 SocketException。您也可以在调用 BeginReceiveFrom 方法之前,为无连接协议建立默认远程主机。在上述任何一种情况下,BeginReceiveFrom 方法都会忽略 remoteEP 参数,并且只从已连接的或默认的远程主机接收数据。

对于面向连接的套接字,BeginReceiveFrom 将读取所有可用的数据,直到达到 size 参数所指定的字节数。

若要取消挂起的 BeginReceiveFrom,请调用 Close 方法。

下面的代码示例异步接收来自远程主机的无连接数据报。

C# code
IPHostEntry lipa = Dns.Resolve("host.contoso.com");
IPEndPoint lep = new IPEndPoint(lipa.AddressList[0], 11000);

Socket s = new Socket(lep.Address.AddressFamily,
SocketType.Dgram,
ProtocolType.Udp);

IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
EndPoint tempRemoteEP = (EndPoint)sender;
s.Connect(sender);

try{
while(true){
allDone.Reset();
StateObject so2 = new StateObject();
so2.workSocket = s;
Console.WriteLine("Attempting to Receive data from host.contoso.com");

s.BeginReceiveFrom(so2.buffer, 0, StateObject.BUFFER_SIZE,0, ref tempRemoteEP,
new AsyncCallback(Async_Send_Receive.ReceiveFrom_Callback), so2);
allDone.WaitOne();
}
}
catch (Exception e){
Console.WriteLine(e.ToString());
}

在端口 11000 上建立 UdpClient 连接。将很短的字符串消息发送到两个单独的远程主机。Receive 方法在接收消息前阻止执行。使用传递给 Receive 的 IPEndPoint 可以显示响应主机的标识。

C# code
// This constructor arbitrarily assigns the local port number.
UdpClient udpClient = new UdpClient(11000);
try{
udpClient.Connect("www.contoso.com", 11000);

// Sends a message to the host to which you have connected.
Byte[] sendBytes = Encoding.ASCII.GetBytes("Is anybody there?");

udpClient.Send(sendBytes, sendBytes.Length);

// Sends a message to a different host using optional hostname and port parameters.
UdpClient udpClientB = new UdpClient();
udpClientB.Send(sendBytes, sendBytes.Length, "AlternateHostMachineName", 11000);

//IPEndPoint object will allow us to read datagrams sent from any source.
IPEndPoint RemoteIpEndPoint = new IPEndPoint(IPAddress.Any, 0);

// Blocks until a message returns on this socket from a remote host.
Byte[] receiveBytes = udpClient.Receive(ref RemoteIpEndPoint);
string returnData = Encoding.ASCII.GetString(receiveBytes);

// Uses the IPEndPoint object to determine which of these two hosts responded.
Console.WriteLine("This is the message you received " +
returnData.ToString());
Console.WriteLine("This message was sent from " +
RemoteIpEndPoint.Address.ToString() +
" on their port number " +
RemoteIpEndPoint.Port.ToString());

udpClient.Close();
udpClientB.Close();

}
catch (Exception e ) {
Console.WriteLine(e.ToString());
}

另外如果你想保证传输的质量的话,用TCP/IP协议吧,它的优点是可以控制传输中的质量(降低出错率),缺点是效率稍微低点。
TcpClient 类提供了一些简单的方法,用于在同步阻止模式下通过网络来连接、发送和接收流数据。

为使 TcpClient 连接并交换数据,使用 TCP ProtocolType 创建的 TcpListener 或 Socket 必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接到该侦听器:

创建一个 TcpClient,并调用三个可用的 Connect 方法之一。

使用远程主机的主机名和端口号创建 TcpClient。此构造函数将自动尝试一个连接。

注意
如果要在同步阻止模式下发送无连接数据报,请使用 UdpClient 类。


给继承者的说明 要发送和接收数据,请使用 GetStream 方法来获取一个 NetworkStream。调用 NetworkStream 的 Write 和 Read 方法与远程主机之间发送和接收数据。使用 Close 方法释放与 TcpClient 关联的所有资源。

下面的代码示例建立 TcpClient 连接。

C# code
static void Connect(String server, String message)
{
try
{
// Create a TcpClient.
// Note, for this client to work you need to have a TcpServer
// connected to the same address as specified by the server, port
// combination.
Int32 port = 13000;
TcpClient client = new TcpClient(server, port);

// Translate the passed message into ASCII and store it as a Byte array.
Byte[] data = System.Text.Encoding.ASCII.GetBytes(message);

// Get a client stream for reading and writing.
// Stream stream = client.GetStream();

NetworkStream stream = client.GetStream();

// Send the message to the connected TcpServer.
stream.Write(data, 0, data.Length);

Console.WriteLine("Sent: {0}", message);

// Receive the TcpServer.response.

// Buffer to store the response bytes.
data = new Byte[256];

// String to store the response ASCII representation.
String responseData = String.Empty;

// Read the first batch of the TcpServer response bytes.
Int32 bytes = stream.Read(data, 0, data.Length);
responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
Console.WriteLine("Received: {0}", responseData);

// Close everything.
stream.Close();
client.Close();
}
catch (ArgumentNullException e)
{
Console.WriteLine("ArgumentNullException: {0}", e);
}
catch (SocketException e)
{
Console.WriteLine("SocketException: {0}", e);
}

Console.WriteLine("\n Press Enter to continue...");
Console.Read();
}
zzxap 2009-07-28
  • 打赏
  • 举报
回复
int port = 1234;

int port = 1234;
IPAddress ip = IPAddress.Parse("127.0.0.1");
socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPoint iep = new IPEndPoint(ip,port);
//socket.Blocking = false;
socket.Bind(iep);
socket.Listen(10);
Console.WriteLine("start......");
try
{
for (int i = 0; i < 10;i++ )
{

}
}
catch
{
Console.WriteLine("异常!");
socket.Close();
}

接收端
private void Receive(Socket socket)
{
NetworkStream ns = new NetworkStream(socket);
FileStream fs = new FileStream("c:\\file.txt", FileMode.OpenOrCreate);
bool isRead = true;
while (isRead)
{
int count = ns.Read(this._receiveBuf, 0, this._receiveBuf.Length);
int datanum = 0;
datanum = BitConverter.ToInt32(this._receiveBuf, 0); //从buffer中的前4个字节读出count
if (datanum > 0) //确定每次要接受多少字节数
{
fs.Write(this._receiveBuf, 4, datanum);
}
else //如果接受字节数为0 就推出
{
isRead = false;
}
}
this.txtFile.Text = "文件传输成功";
fs.Close();
}
---------------------
发送端
private void btSend_Click(object sender, System.EventArgs e)
{
if (this._isConnect)
{
_ns = _tc.GetStream();
string path = this.txtPath.Text.Trim();
FileStream fs = new FileStream(path, FileMode.Open);
int sendCount = 0;
byte[] countbuffer = null;
byte[] clientbuffer = new byte[1004];
while (sendCount < fs.Length && _ns.CanWrite)
{
int count = fs.Read(_sendBuf, 0, _sendBuf.Length); //读出要发送的数据
countbuffer = BitConverter.GetBytes(count);
countbuffer.CopyTo(clientbuffer,0);
_sendBuf.CopyTo(clientbuffer, 4);
this._ns.Write(clientbuffer, 0, 4 + count); //写入网络流
sendCount += count;
}
countbuffer = BitConverter.GetBytes(0); //发送完文件后 发送count = 0
this._ns.Write(countbuffer, 0, countbuffer.Length); //使接收端停止
_ns.Close();
fs.Close();
}
}

你为什么不把这两种方案结合在一起呢?
首先把文件的总长度和每次发送的大小先发送出去,等接收端接受并分析,然后开始。
比如每次发送4K(这是操作系统文件管理中使用到的最小文件大小,你可以看看你系统中的任何一个文件,占用空间都是4K的整数倍),
最后一次可能会少与4K,但是接受方是可以计算出来的。
必要时,你可以使用多线程,分段发送,接收端收集后分段组合,这还要多使用一个段号码。
socket是最底层的类,传输效率最高!
对于你说的异步操作,一句话说不清楚,基本上可以用“非阻塞模型”来概括,就是调用后立马返回,不是等到操作完成后才返回!
打个比方:阻塞模型
while(isok)
{
readdata(data);//从文件读数据
send(data); //一直等到data发送完毕后才返回,其实这期间本来可以进行下一次读操作
//影响了效率。
if(读完)
isok=false;
else
isok=true;
}
非阻塞模型,可以在发送过程中进行读取操作,提高了效率。
当然,在第二次发送前,必须等待第一次发送操作完成才行,需要检测和控制!

while (sendCount < fs.Length && _ns.CanWrite)
{
int count = fs.Read(_sendBuf, 0, _sendBuf.Length); //读出要发送的数据
countbuffer = BitConverter.GetBytes(count);
countbuffer.CopyTo(clientbuffer,0);
_sendBuf.CopyTo(clientbuffer, 4);
this._ns.Write(clientbuffer, 0, 4 + count); //写入网络流
sendCount += count;
}

有点乱:你每次读取1000还是1004??不是前四个字节是长度吗?为什么从文件里读取1004个字节啊?
BeginReceiveFrom 方法启动从远程主机异步读取无连接数据报的操作。调用 BeginReceiveFrom 方法将使您能够在单独的执行线程中接收数据。

您可以创建一个实现 AsyncCallback 委托的回调方法并将它的名称传递给 BeginReceiveFrom 方法。为此,您的 state 参数至少必须包含用于通信的已连接或默认 Socket。如果您的回调需要更多信息,则可以创建一个小型类来保存 Socket 和其他必需的信息。通过 state 参数将此类的一个实例传递给 BeginReceiveFrom 方法。

回调方法应调用 EndReceiveFrom 方法。当应用程序调用 BeginReceiveFrom 时,系统将会使用单独的线程来执行指定的回调方法,并将在 EndReceiveFrom 上一直阻止到 Socket 读取数据或引发异常为止。如果想要在调用 BeginReceiveFrom 方法后使原始线程阻止,请使用 WaitHandle.WaitOne。当需要原始线程继续执行时,请在回调方法中调用 T:System.Threading.ManualResetEvent 的 Set 方法。有关如何编写 callback 方法的其他信息,请参见 Callback 示例。

注意
在调用 BeginReceiveFrom 之前,必须使用 Bind 方法显式地将 Socket 绑定到本地终结点,否则 BeginReceiveFrom 将会引发 SocketException。


该方法将数据读入 buffer 参数中,并捕获从其发送数据的远程主机终结点。有关如何检索此终结点的信息,请参考 EndReceiveFrom。如果打算从未知主机或多个主机异步接收无连接的数据报,则最适合使用此方法。在这些情况下,BeginReceiveFrom 将会读取本地网络缓冲区接收到的第一个排队数据报。如果您接收到的数据报大于 buffer 的大小,则 BeginReceiveFrom 方法将在 buffer 中尽可能多地填充消息内容,并引发 SocketException。如果您使用的是不可靠协议,多余的数据将会丢失。而如果当前使用的是可靠协议,则服务提供程序将保留多余的数据,而且通过使用一个足够大的缓冲区调用 BeginReceiveFrom 方法来检索这些数据。

虽然 BeginReceiveFrom 是用于无连接协议的,但您同样可以使用面向连接的协议。如果选择这样做,则必须通过调用 Connect / BeginConnect 方法来建立远程主机连接,或者调用 Accept 或 BeginAccept 方法来接受传入的连接请求。如果在建立连接或接受连接之前就调用了 BeginReceiveFrom 方法,则您将得到 SocketException。您也可以在调用 BeginReceiveFrom 方法之前,为无连接协议建立默认远程主机。在上述任何一种情况下,BeginReceiveFrom 方法都会忽略 remoteEP 参数,并且只从已连接的或默认的远程主机接收数据。

对于面向连接的套接字,BeginReceiveFrom 将读取所有可用的数据,直到达到 size 参数所指定的字节数。

若要取消挂起的 BeginReceiveFrom,请调用 Close 方法。

下面的代码示例异步接收来自远程主机的无连接数据报。
BeginReceiveFrom 方法启动从远程主机异步读取无连接数据报的操作。调用 BeginReceiveFrom 方法将使您能够在单独的执行线程中接收数据。

您可以创建一个实现 AsyncCallback 委托的回调方法并将它的名称传递给 BeginReceiveFrom 方法。为此,您的 state 参数至少必须包含用于通信的已连接或默认 Socket。如果您的回调需要更多信息,则可以创建一个小型类来保存 Socket 和其他必需的信息。通过 state 参数将此类的一个实例传递给 BeginReceiveFrom 方法。

  • 打赏
  • 举报
回复
怕丢包,下载完对文件做一下MD5校验就可以了。
zgke 2009-07-28
  • 打赏
  • 举报
回复
public struct FilePack
{
/// <summary>
/// 数据包大小
/// </summary>
public ushort PackSize;

/// <summary>
/// 文件GUID
/// </summary>
public byte[] FileGuid; //16

///// <summary>
///// 包数据SHA1验证 ...
///// </summary>
//public byte[] PackSHA1;

/// <summary>
/// 包总数量
/// </summary>
public ushort PackCount;

/// <summary>
/// 包号
/// </summary>
public ushort PackIndex;

/// <summary>
/// 包数据
/// </summary>
public byte[] PackData;

}

这样发啊...
stning 2009-07-28
  • 打赏
  • 举报
回复
ls的代码不完整
muyebo 2009-07-28
  • 打赏
  • 举报
回复
我自己收集的方法,没有真正用过
       private void SendFileStream(Socket socketStream, FileStream fs, byte[] byPack, string strFileName)
{
byte[] byHead = new byte[128];
byHead[0] = byPack[0];
byHead[1] = byPack[1];

byte[] byFileName = encodingGB.GetBytes(strFileName);
byHead[2] = Convert.ToByte(byFileName.Length);

byFileName.CopyTo(byHead, 3);

if (socketStream != null)
{
SocketSendMessage(socketStream, byHead);
}
else
{
MessageBox.Show("socket");
return;
}
//socketStream.Send(byHead);

byte[] byBody = new byte[PACKET_SIZE];

int nPackCount = Convert.ToInt32(byPack[0]) * Convert.ToInt32(byPack[1]);

int nCurrentPack = 0;

while (true)
{
if (socketStream != null)
{
SocketReceiveMessage(socketStream);
}


if (Convert.ToChar(byRec[0]) == 'E')
{
fs.Dispose();
fs.Close();
break;
}
else if (Convert.ToChar(byRec[0]) == 'S')
{

nCurrentPack = Convert.ToInt32(byRec[1]) * 256 + Convert.ToInt32(byRec[2]);

fs.Seek((nCurrentPack - 1) * PACKET_SIZE, SeekOrigin.Begin);

byBody = new byte[PACKET_SIZE];
if ((nCurrentPack == nPackCount) && (nLastPackSize != 0))
{
fs.Read(byBody, 0, nLastPackSize);
}
else
{
fs.Read(byBody, 0, PACKET_SIZE);

}

if (socketStream != null)
{
SocketSendMessage(socketStream, byBody);
}
else
{
MessageBox.Show("socket");
return;
}
}


}

}
满衣兄 2009-07-28
  • 打赏
  • 举报
回复
封包,定义包头包尾.
stning 2009-07-28
  • 打赏
  • 举报
回复
[Quote=引用 4 楼 czjearth 的回复:]
楼主能否说详细些,

大文件分段, 那和发小文件有何区别?

[/Quote]丢包,通过网络传输,各种问题就可能有
czjearth 2009-07-28
  • 打赏
  • 举报
回复


把文件读到 流中, 分段发送就是
stning 2009-07-28
  • 打赏
  • 举报
回复
[Quote=引用 1 楼 jsoner 的回复:]
将文件分段传送,每次传送8KB。

先定义一个单个数据包的最大长度 
然后发送文件。如果大小超过单个包大小就拆分后发送。 
接收段只需要把数据拼合起来就好了。 
[/Quote]200分的帖子,等到现在才sf。。汗
czjearth 2009-07-28
  • 打赏
  • 举报
回复
楼主能否说详细些,

大文件分段, 那和发小文件有何区别?
加载更多回复(17)

110,545

社区成员

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

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

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