AVollane
@AVollane
Начинающий C# разработчик

Почему иногда данные по TCP доходят корректно, а иногда нет?

Здравствуйте. Пишу клиент-серверное приложение. Одно приложение (сервер) берёт из файла конфигурации нужные IP-адреса, порты и директории с файлами, а затем отправляет все файлы в директории на определённый эндпоинт. Тем временем другое приложение (клиент) фиксирует подключение сервера и начинает читать данные из NetworkStream. Перед каждым файлом идёт килобайтный заголовок (строка) формата: <размер файла>|<имя файла>|<"LAST", если последний>. Иногда (примерно 1 раз к 8 передачам) происходит проблема. Размер файла не получается преобразовать в число, что может свидетельствовать о том, что байты считались некорректно, или были повреждены.
Я перехватываю исключение:
long fileSize = 0;
                    try
                    {
                        fileSize = Int64.Parse(headerElements[0]);
                    }
                    catch (Exception)
                    {
                        ConsoleMessenger.Error("Can't to parse header.");
                        break;
                    }

Что может быть не так? TCP ведь обеспечивает точную передачу данных без искажений. При том, эта ошибка возникает не всегда, в основном файлы передаются корректно.
Вот код сервера, который подключается к клиентам:
public class FileBroadcastingServer
    {
        private Socket _server;
        private List<RemoteMonitor> _remoteMonitors;

        public FileBroadcastingServer(List<RemoteMonitor> monitors)
        {
            _server = new Socket(SocketType.Stream, ProtocolType.Tcp);
            _remoteMonitors = monitors;
        }

        public void Broadcast()
        {
            ConsoleMessenger.Info($"Broadcasting to {_remoteMonitors.Count} remote monitors");
            foreach(RemoteMonitor monitor in _remoteMonitors)
            {
                string[] paths = Directory.GetFiles(monitor.FilesDirectory);
                IPEndPoint endPoint = monitor.IPEndPoint;
                SendFiles(endPoint, paths);
            }
        }

        public void SendFiles(IPEndPoint endPoint, string[] paths)
        {
            try { _server.Connect(endPoint); }
            catch(Exception)
            {
                ConsoleMessenger.UnableToConnect($"Unable to connect to {endPoint.Address}:{endPoint.Port}. Skipping. . .");
                return;
            }

            NetworkStream ns = null;
            try
            {
                ns = new NetworkStream(_server);
            }
            catch (Exception)
            {
                ConsoleMessenger.Error("Unable to get network stream. Skipping. . .");
                _server.Close();
                return;
            }

            for(int i = 0; i < paths.Length; i++)
            {
                string path = paths[i];
                FileInfo fileInfo = new FileInfo(path);
                string header = fileInfo.Length + "|" + fileInfo.Name; // формируем заголовок

                if (i == paths.Length - 1)
                    header += "|LAST"; // если файл последний в очереди, то отправить тег

                byte[] headerBuffer = new byte[1024]; // буфер заголовка
                Encoding.ASCII.GetBytes(header, headerBuffer);

                byte[] fileBuffer = new byte[fileInfo.Length]; // файловый буфер
                using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read))
                {
                    fs.Read(fileBuffer);
                }

                try
                {
                    ns.Write(headerBuffer); // записываем в поток заголовок
                    ns.Write(fileBuffer); // записываем в потоке файл
                }
                catch (Exception)
                {
                    ns.Close();
                    _server.Close();
                    ConsoleMessenger.Error("Unable to write data into the network stream. Skipping. . .");
                    return;
                }
                ConsoleMessenger.Success($"File {fileInfo.Name} has been sended!");
                Task.Delay(2000);
            }
            ConsoleMessenger.Success("Dispatch finished!");
            ns.Close();
            _server.Close();
        }
    }


Вот код клиента, принимающего файлы от сервера:
public class Receiver
    {
        private Socket _receiver;
        private string _savingDirectory;
        private IPEndPoint _ipEndPoint;

        private Socket _handler;

        public Receiver(Socket receiver, IPEndPoint ipEndPoint, string savingDirectory)
        {
            _receiver = receiver;
            _savingDirectory = savingDirectory;
            _ipEndPoint = ipEndPoint;
        }

        public void ReceiveFiles()
        {
            _receiver.Bind(_ipEndPoint);
            while (true)
            {
                _receiver.Listen(10);
                _handler = _receiver.Accept();

                ConsoleMessenger.Success("Connected!");

                NetworkStream ns = new NetworkStream(_handler);
                byte[] headerBuffer = new byte[1024];

                bool isLast = false;
                while (!isLast)
                {
                    Task.Delay(2000);
                    ns.Read(headerBuffer); // прочитали заголовок
                    string header = Encoding.ASCII.GetString(headerBuffer);

                    string[] headerElements = header.Split('|');
                    if (headerElements.Length == 3)
                        isLast = true;

                    // Уязвимое место! --------------------
                    long fileSize = 0;
                    try
                    {
                        fileSize = Int64.Parse(headerElements[0]);
                    }
                    catch (Exception)
                    {
                        ConsoleMessenger.Error("Can't to parse header.");
                        break;
                    }
                    // -------------------------------------
                    string fileName = headerElements[1];

                    byte[] fileBuffer = new byte[fileSize];
                    ns.Read(fileBuffer);

                    string savingDir = _savingDirectory;

                    char[] invalidCharacters = Path.GetInvalidPathChars();
                    if (!savingDir.EndsWith('/'))
                        savingDir += "/";

                    savingDir += fileName;

                    string savingFileDir = new string(savingDir.Where(x => !invalidCharacters.Contains(x)).ToArray());
                    using (FileStream fs = new FileStream(savingFileDir, FileMode.OpenOrCreate, FileAccess.Write))
                    {
                        fs.Write(fileBuffer);
                    }

                    ConsoleMessenger.Success($"File {fileName} has been received!");
                }
                ConsoleMessenger.Info("Waiting for connections. . .");
                ns.Close();
            }
        }
    }


Прошу помочь и объяснить.
  • Вопрос задан
  • 121 просмотр
Решения вопроса 1
sarapinit
@sarapinit Куратор тега C#
Точу водой камень
ns.Read(headerBuffer); // прочитали заголовок
Вот здесь нет гарантии, что вы прочитали именно столько байт сколько у вас буфер.
Поэтому метод Read возвращает количество фактически прочитанных байт.
Это классические проблемы чтения, про которые можно почитать тут
Там как раз про то, как решить это все с помощью pipelines, которые упоминал Василий Банников
Ответ написан
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы