使用套接字和多个客户端进行文件传输

时间:2011-03-24 15:22:26

标签: c# sockets

我有一个使用.Net remoting编写的大型应用程序用于文件传输。在某些情况下,套接字被强行关闭是不可能的 - 我没有直接使用套接字,而是使用字节数组的.Net远程调用(我没有在一次传输中发送整个文件,我正在拆分它)。

因此,我决定将实际的文件传输部分更改为使用套接字。

作为一个概念证明,为了确定我的原则是否正确,我编写了一个简单的控制台客户端和服务器。

我正在使用ASynch接收但是同步写入 - 我尝试过同时使用ASync但同样的研究,并保持同步使调试更容易。

应用程序执行的操作(代码如下)是服务器所在并等待传输文件,并将它们存储在给定名称的目录中。

按下enter键后,服务器会读取收到的文件并将其发送回以不同名称存储它们的客户端。我想两种方式测试文件传输。

使用客户端应用程序的一个实例,一切都很好 - 服务器接收它然后将其发送回客户端。一切都很好。是的,当您终止服务器时,客户端会抛出异常 - 但这很好 - 我知道套接字被强制关闭......我可以处理在代码工作时整理代码。

但是,当我创建客户端代码的2个实例时(不要忘记稍微修改代码以读取要发送的不同文件,并且还以不同的名称存储接收的文件) - 服务器接收来自客户端,发送第一个回来就好了,然后几个段进入第二个文件,它抛出“非阻塞套接字操作无法立即完成” - 这很奇怪,因为没有阻塞,并且接收是异步的 - 和发送实际上是阻塞的!

关于我做错了什么的任何建议 - 毫无疑问这是愚蠢的,但仍然......

最终代码的目的是能够让n个客户端联系服务器并向其发送文件,并且随机间隔让服务器将一个或多个文件发送回一些/所有客户端。 / p>

干杯人!

服务器代码

    using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace SocketServer
{

    class ConnectionInfo
    {
        public Socket Socket;
        public byte[] Buffer;
        public int client;        
    }



    class Program
    {

        static int chunkSize = 16 * 1024;
        static int chucksizeWithoutHeaderData = chunkSize - 8;
        static List<ConnectionInfo> list = new List<ConnectionInfo>();

        static Socket serverSocket;

        static int nClient = 0;


        static void AcceptCallback(IAsyncResult result)
        {


            ConnectionInfo info = new ConnectionInfo();
            info.Socket = serverSocket.EndAccept(result);

            info.Buffer = new byte[chunkSize];


            Console.WriteLine("Client connected");
            nClient++;
            info.client = nClient;

            list.Add(info);

            info.Socket.BeginReceive(info.Buffer,0,info.Buffer.Length,SocketFlags.None, new AsyncCallback(ReceiveCallBack), info);

            serverSocket.BeginAccept(new AsyncCallback(AcceptCallback),null);

        }

        static void ReceiveCallBack(IAsyncResult result)
        {
            ConnectionInfo info = result.AsyncState as ConnectionInfo;
            try
            {

                Int32 nSegmentNumber = BitConverter.ToInt32(info.Buffer,0);
                Int32 nMaxSegment = BitConverter.ToInt32(info.Buffer,4);

                string strFileName = string.Format(@"c:\temp\from-client-{0}.dat",info.client);

                int bySize = info.Socket.EndReceive(result);
                using (FileStream fs = new FileStream(strFileName, FileMode.OpenOrCreate))
                {
                    Console.WriteLine("Received segment {0} of {1} from client {2}", nSegmentNumber, nMaxSegment, info.client);
                    fs.Position = fs.Length;
                    fs.Write(info.Buffer, 8, bySize-8);

                    if (nSegmentNumber >= nMaxSegment)
                    {
                        Console.WriteLine("Completed receipt from client {0}", info.client);
                    }

                }

                info.Socket.BeginReceive(info.Buffer, 0, info.Buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), info);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }



        static void Main(string[] args)
        {
            try
            {

                Console.WriteLine("Server");

                IPAddress address = IPAddress.Parse("127.0.0.1"); //The IP address of the server
                IPEndPoint myEndPoint = new IPEndPoint(address, 6503);
                serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                serverSocket.Bind(myEndPoint);

                serverSocket.Listen(1000);

                for (int n = 0; n < 10; ++n)
                {
                    serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
                }

                Console.WriteLine("Server now waiting");
                Console.ReadLine();

                foreach (ConnectionInfo info in list)
                {


                    string strFileName = string.Format(@"c:\temp\from-client-{0}.dat", info.client);

                    using (FileStream fs = new FileStream(strFileName, FileMode.Open, FileAccess.Read, FileShare.None))
                    {
                        int nMaxChunk = 0;
                        int nCurrentChunk = 0;

                        nMaxChunk = (int)(fs.Length / chucksizeWithoutHeaderData);
                        if ((nMaxChunk * chucksizeWithoutHeaderData) < fs.Length)
                        {
                            ++nMaxChunk;
                        }


                        using (BinaryReader br = new BinaryReader(fs))
                        {
                            byte[] byBuffer;
                            Int64 nAmount = 0;
                            byte[] byMaxChunk = BitConverter.GetBytes(nMaxChunk);
                            while (fs.Length > nAmount)
                            {
                                ++nCurrentChunk;

                                byte[] byCurrentChunk = BitConverter.GetBytes(nCurrentChunk);

                                byBuffer = br.ReadBytes(chucksizeWithoutHeaderData);


                                Console.WriteLine("Sending {0}bytes, chunk {1} of {2} to client {3}", byBuffer.Length,nCurrentChunk,nMaxChunk, info.client);

                                byte [] byTransmitBuffer = new byte[byBuffer.Length + 8];
                                Array.Copy(byCurrentChunk, byTransmitBuffer, 4);
                                Array.Copy(byMaxChunk, 0,byTransmitBuffer, 4, 4);
                                Array.Copy(byBuffer, 0, byTransmitBuffer, 8, byBuffer.Length);

                                info.Socket.Send(byTransmitBuffer);
                                nAmount += byBuffer.Length;
                            }
                        }
                    }
                }


                Console.WriteLine("Press enter to end server");
                Console.ReadLine();



            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
                Console.ReadLine();
            }
        } 

    }
}

客户端代码

    using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.IO;
using System.Threading;

namespace SocketClient
{
    class Program
    {
        static TcpClient socket = new TcpClient();
        static int chunkSize = 16 * 1024;
        static int chucksizeWithoutHeaderData = chunkSize - 8;
        static byte[] byReceiveBuffer = new byte[chunkSize];
        static void ReceiveCallBack(IAsyncResult result)
        {
            Socket socket = result.AsyncState as Socket;
            try
            {
                int bySize = socket.EndReceive(result);

                Console.WriteLine("Recieved bytes {0}", bySize);

                if (bySize != 0)
                {





                    Int32 nSegmentNumber = BitConverter.ToInt32(byReceiveBuffer, 0);
                    Int32 nMaxSegment = BitConverter.ToInt32(byReceiveBuffer, 4);

                    Console.WriteLine("Received segment {0} of {1}", nSegmentNumber, nMaxSegment);

                    string strFileName = string.Format(@"c:\temp\client-from-server.dat");
                    using (FileStream fs = new FileStream(strFileName, FileMode.OpenOrCreate))
                    {
                        fs.Position = fs.Length;
                        fs.Write(byReceiveBuffer, 8, bySize-8);
                    }

                    if (nSegmentNumber >= nMaxSegment)
                    {
                        Console.WriteLine("all done");
                    }
                }


                socket.BeginReceive(byReceiveBuffer, 0, byReceiveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), socket);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }



        static void Main(string[] args)
        {
            Console.WriteLine("Press enter to go");
            Console.ReadLine();


            socket.Connect("127.0.0.1", 6503);

            Console.WriteLine("Client");
            Console.ReadLine();

            byte[] byBuffer;




            socket.Client.BeginReceive(byReceiveBuffer, 0, byReceiveBuffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallBack), socket.Client);


            using (FileStream fs = new FileStream(@"c:\temp\filetosend.jpg", FileMode.Open, FileAccess.Read, FileShare.None))
             {



                 using (BinaryReader br = new BinaryReader(fs))
                 {

                     int nMaxChunk = 0;
                     int nCurrentChunk = 0;

                     nMaxChunk = (int)(fs.Length / chucksizeWithoutHeaderData);
                     if ((nMaxChunk * chucksizeWithoutHeaderData) < fs.Length)
                     {
                         ++nMaxChunk;
                     }

                     byte[] byMaxChunk = BitConverter.GetBytes(nMaxChunk);

                        Int64 nAmount = 0;

                        while (fs.Length > nAmount)
                        {
                            ++nCurrentChunk;
                            byte[] byCurrentChunk = BitConverter.GetBytes(nCurrentChunk);

                            byBuffer = br.ReadBytes(chucksizeWithoutHeaderData);
                            Console.WriteLine("Sending {0}bytes, chunk {1} of {2}", byBuffer.Length, nCurrentChunk, nMaxChunk);

                            byte[] byTransmitBuffer = new byte[byBuffer.Length + 8];
                            Array.Copy(byCurrentChunk, byTransmitBuffer, 4);
                            Array.Copy(byMaxChunk, 0, byTransmitBuffer, 4, 4);
                            Array.Copy(byBuffer, 0, byTransmitBuffer, 8, byBuffer.Length);

                            socket.Client.Send(byTransmitBuffer);
                            nAmount += byBuffer.Length;
                        }


                 }
             }



            Console.WriteLine("done");


            Console.ReadLine();



        }
    }
}

3 个答案:

答案 0 :(得分:0)

由于您正在修改基础结构,因此请在C#中使用FTP库并安装一些免费的FTP服务器(即FileZilla或其他任何人)。您可以轻松地使用某些FTP库,例如可靠的this one(我在生产代码中使用它)。

答案 1 :(得分:0)

套接字编程可能很棘手。如果发送100个字节,并不意味着您将在服务器中收到100个字节。您可以在多个数据包中接收这100个字节,您必须添加代码来控制它。

我之所以这么说是因为服务器代码中的这一行:

fs.Write(info.Buffer, 8, bySize-8);

你假设你将收到至少8位,这可能是错误的。 bySize可以小于块大小(如果连接已被客户端关闭,则它可以为零,如果出现错误,则为&lt; 0)。

关于您的错误,我测试了您的代码,我可以复制您的问题:

  • 启动服务器
  • 启动客户端并传输文件
  • 退出客户端按Enter
  • 服务器崩溃

服务器崩溃,因为套接字在传输完所有文件后正在等待更多数据。客户关闭了它。

我解决了在发送文件后关闭客户端连接的问题:

socket.Client.Disconnect(false);

然后服务器接收bySize = 0字节,表示连接已关闭。在服务器中我替换了这个:

int bySize = socket.EndReceive(result);

为此:

int bySize = 0;
try
{
    bySize = info.Socket.EndReceive(result);
}
catch (Exception ex)
{
    Console.WriteLine("Error from client {0}: {1}", info.client, ex.Message);
    return;
}

if (bySize <= 0)
    return;

看看这里:http://msdn.microsoft.com/en-us/library/5w7b7x5f.aspx#Y240

在这里:http://msdn.microsoft.com/en-us/library/fx6588te.aspx

编辑:我忘记提到这一点,你只需要调用一次BeginAccept。我删除了for语句。

// for (int n = 0; n < 10; ++n)
// {
    serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), null);
// }

答案 2 :(得分:0)

我认为我可能有一个解决方案 - 虽然我不是100%自信......如果失败,我会继续测试并报告。

无论如何 - 我已经将socket.SendBufferSize和RecieveBufferSize设置为块大小的4倍,现在看起来一切都很好。

那里的东西(重新缓冲区大小)只是把问题关掉了,正如我想的那样。

但是,我在其他地方遇到了一段代码,并将这些行放入代码中解决了这个问题。

  try
                            {
                                bySent = info.Socket.Send(byTransmitBuffer);
                            }
                            catch (SocketException ex)
                            {
                                Console.WriteLine("Only sent {0}, remaining = {1}", bySent,fs.Length -nAmount);
                                if (ex.SocketErrorCode == SocketError.WouldBlock ||
                                  ex.SocketErrorCode == SocketError.IOPending ||
                                  ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)
                                {
                                    // socket buffer is probably full, wait and try again
                                    Thread.Sleep(30);
                                }
                                else
                                    throw ex;  // any serious error occurr
                            }