Передача файлов с использованием TCP Socket - C#

1319
06 апреля 2017, 16:26

Доброго времени суток. Возникла проблема при попытке передать файл с помощью сокетов. Сервер обрабатывает не все отправленные на него данные (например из 26 кб получает 16 кб). Заметил один непонятный момент: если между методом socket.Receive() и записью в MemoryStream добавить строчку кода, например записи данных в лог файл (File.AppendAllText("Log.log", string.Format("Received={0}\r\n", received)), то все работает нормально. Подскажите что я делаю не так? Заранее спасибо

Код сервера:

namespace TCPServer {
    class Program {
        static void Main(string[] args) {
            try {
            IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
                Socket listenSoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                listenSoc.Bind(endPoint);
                listenSoc.Listen(10);
                Console.WriteLine("Server is running...");
                int bytes = 0;
                const int bufferSize = 8192;
                while (true) {
                    Socket handler = listenSoc.Accept();
                    NetFile file;
                    using (MemoryStream memStream = new MemoryStream()) {
                        byte[] buffer = new byte[bufferSize];
                        do {
                            int received = handler.Receive(buffer);
                            //File.AppendAllText("Log.log", string.Format("Received={0}\r\n", received));
                            memStream.Write(buffer, 0, received);
                            bytes += received;
                        }
                        while (handler.Available > 0);
                        file = new NetFile(memStream.ToArray());
                    }
                    Console.WriteLine("Size of received data: " + bytes.ToString() + " bytes");
                    using (FileStream stream = new FileStream(file.FileName, FileMode.Create, FileAccess.Write)) {
                        stream.Write(file.Data, 0, file.Data.Length);
                    }
                    handler.Shutdown(SocketShutdown.Both);
                    handler.Close();
                    bytes = 0;
                }
            }
            catch (Exception error) {
                Console.WriteLine(error.ToString());
                Console.ReadKey();
            }
        }
    }
}

Код клиента:

namespace TcpClient {
    class Program {
        static void Main(string[] args) {
            try {
                IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
                Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                socket.Connect(endPoint);
                Console.WriteLine("Client connected");
                Console.WriteLine("Sending file...");
                using (FileStream stream = new FileStream("c:\\1.sql", FileMode.Open, FileAccess.Read)) {
                    byte[] data = new byte[stream.Length];
                    int length = stream.Read(data, 0, data.Length);
                    NetFile file = new NetFile();
                    file.FileName = Path.GetFileName(stream.Name);
                    file.Data = data;
                    byte[] to = file.ToArray();
                    socket.Send(to);
                }
                Console.WriteLine("File sended");
                socket.Shutdown(SocketShutdown.Both);
                socket.Close();
                Console.ReadKey();
            }
            catch (Exception error) {
                Console.WriteLine(error.ToString());
                Console.ReadKey();
            }
        }
    }
}

Класс с информацией об отправляемом файле:

namespace ExchangeEngine.Net {
    [Serializable]
    public class NetFile {
        private byte[] _data;
        public NetFile() { }
        public NetFile(byte[] data) {
            NetFile file = FromArray(data);
            FileName = file.FileName;
            Data = file.Data;
        }
        public string FileName { get; set; }
        public byte[] Data {
            get {
                return _data;
            }
            set {
                _data = value;
                Checksum = GetMD5Hash(_data);
            }
        }
        public string Checksum { get; set; }
        public static string GetMD5Hash(byte[] source) {
            StringBuilder hash = new StringBuilder();
            using (MD5 md5Hasher = MD5.Create()) {
                byte[] data = md5Hasher.ComputeHash(source);
                for (int index = 0; index < data.Length; index++) {
                    hash.Append(data[index].ToString("x2"));
                }
                return hash.ToString();
            }
        }
        public byte[] ToArray() {
            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream()) {
                formatter.Serialize(stream, this);
                return stream.ToArray();
            }
        }
        public static NetFile FromArray(byte[] data) {
            BinaryFormatter formatter = new BinaryFormatter();
            using (MemoryStream stream = new MemoryStream(data)) {
                stream.Position = 0;
                return (NetFile)formatter.Deserialize(stream);
            }
        }
    }
}
Answer 1

Как показало расследование в комментариях, виноват код

do {
    int received = handler.Receive(buffer);
    //File.AppendAllText("Log.log", string.Format("Received={0}\r\n", received));
    memStream.Write(buffer, 0, received);
    bytes += received;
 }
 while (handler.Available > 0);

Дело в том, что из-за сетевых задержек часть данных может быть ещё в пути, когда начало уже вычитано. Сокет работает в потоковом, а не блочном режиме, и не знает, что будут ещё доставлены данные. Поэтому использовать Available для распознавания конца передачи нецелесообразно.

Самое простое и практичное решение — перед передачей информации передавать её длину. На читающей стороне принимать сначала длину, а потом читать до тех пор, пока не прочитается нужное количество байт.

Литература по теме: Message Framing и вообще TCP/IP .NET Sockets FAQ в блоге Стивена Клири.

READ ALSO
Настройка Browser-sync для PHP (Apache)

Настройка Browser-sync для PHP (Apache)

Не получается сделать так, чтобы браузер перезагружал страницы из папки Example при их измененииУ меня стоит сборка Xampp (Apache)

434
VK API загрузка фото

VK API загрузка фото

При отправке multipart/form-data по полученному url, запрос

503
Сумма элементов подмассива

Сумма элементов подмассива

В переменной лежит такой массив

275
простая реализация системы blockchain [требует правки]

простая реализация системы blockchain [требует правки]

Встречались ли вам статьи где с реальными примерами был реализован blockchain ?

417