c# socket 传输文件
大家好,我是一名大四的学生。最近在做毕设,遇到了在c#中socket传输大文件的问题。
我的大体思路有两个(参考网上的资料)
----------------------
(1) 个人认为效率比较低
连接..
发送第一个字节块...
等待确认...(接收确认信息)
确认
发送第二个字节块...
等待确认...
确认
发送第三个.....
....
(2)个人认为效率比较高
发送端一直写入网络流
接收端一直读取网络流
----------------------
我在每个字节块的前面加入了一个int型的变量,它表示每次发送文件数据的长度
我根据思路(2)写了代码
如下
----------------------
接收端
----------------------
发送端
----------------------
我发现有时能够成功,有时却陷入死循环,比如我传输2k的txt,结果很长时间都没有停止,我中断后发现生成了200M的txt
实在搞不懂是为什么,请大家帮我分析分析.
-------------------------------
关于思路(1)
我不会控制socket和相对应的networkstream
比如说发送端发送数据后,要等待接收端的确认信息
怎样让发送端停止发送数据来等待确认信息
接收到后又怎样重新发送数据
说简单一点就是发送端和接收端有序的操作网络流的问题
请大家多多指点
-----------------------------------
最后的问题:您觉得在socket上传输大文件时,如何能提高传输的效率?
networkstream的同步异步操作是怎么回事?能不能跟我说说他们的区别,最好来一个实例(beginread中的AsyncCallback asyncCallback到底是什么东西)
问了好多问题,可是我没有多少分给大家觉得很惭愧(等我有份了一定补给大家),但是我会衷心的谢谢大家
我的mail:6233843@gmail.com 如果有code的话您可以mail我
再一次谢谢大家
[解决办法]
你为什么不把这两种方案结合在一起呢?
首先把文件的总长度和每次发送的大小先发送出去,等接收端接受并分析,然后开始。
比如每次发送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 方法。
下面的代码示例异步接收来自远程主机的无连接数据报。
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()); }
[解决办法]
另外如果你想保证传输的质量的话,用TCP/IP协议吧,它的优点是可以控制传输中的质量(降低出错率),缺点是效率稍微低点。
TcpClient 类提供了一些简单的方法,用于在同步阻止模式下通过网络来连接、发送和接收流数据。
为使 TcpClient 连接并交换数据,使用 TCP ProtocolType 创建的 TcpListener 或 Socket 必须侦听是否有传入的连接请求。可以使用下面两种方法之一连接到该侦听器:
创建一个 TcpClient,并调用三个可用的 Connect 方法之一。
使用远程主机的主机名和端口号创建 TcpClient。此构造函数将自动尝试一个连接。
注意
如果要在同步阻止模式下发送无连接数据报,请使用 UdpClient 类。
给继承者的说明 要发送和接收数据,请使用 GetStream 方法来获取一个 NetworkStream。调用 NetworkStream 的 Write 和 Read 方法与远程主机之间发送和接收数据。使用 Close 方法释放与 TcpClient 关联的所有资源。
下面的代码示例建立 TcpClient 连接。
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();}
[解决办法]
学习了!!
最好定一个协议!!
发送文件的时候封装协议!!
接受文件的时候解释协议!!
[解决办法]
一直打开怎么可以呢
要有一个通信协议,比如说传输大小,开始,结束,都要定义的
[解决办法]
我看看看你的接受端代码,好像只把isread的状态改变了但是没有传到上面去啊 ,每次发送过来的数据你的 isread都是true,你要吧isread首先设为false然后在 文件流不>0的情况下才是 true。
------解决方案--------------------
http://topic.csdn.net/u/20080202/09/2a639488-5c1e-42d2-9d11-3f6e2ceebc26.html
楼主可以参考我的回帖。
另外,楼主如果不是必须自己写所有代码,我可以给你我的一个类库。封好了传输功能。
对算法嘛。楼主的想法几本是对的。但是没自己看你的代码质量。
C/S都开两个线,一读一写。每个包头上加序号, 3579/100/1 3579/100/2 3579/100/3以此类推
3579表示该数据块的编号。 100表示一共分了100个包,后便的数字表示当前是第几个包。
接受方把接到的数据包都保存下来,一旦发现数据不连续,就请求一下。比如3579/100/23 之后得到 3579/100/25
那么就再请求一下 3579/100/24, 发送方得到这个后,再把24包补发一次(包里要注明补发,以免接收方吧计数器弄错),
数据传输完成。接收方返回一个确认指令。传输完成。
[解决办法]
以上的算法可以根据需要变换。 比如你要传输的东西太大,接收和发送方都吧所有数据放内存里等待校验和重发是很浪费的。
那么你在发送前就该先吧数据分段。比如不分段需要1000个包,那么你可以线分成20段,每段50个包。每完成一段,就确认一下,吧数据写入磁盘,继续处理下一段。
不过如果要求传输的是命令队列(一般这东西要求及时处理,不能耽搁)那么你最好采用类似ATM的传输方式。接受一个,处理一个。
[解决办法]
LZ可以送10分技术分给我吗?急用`谢谢啦`好心有好报!