WEB-сокеты или отдай мне эти файлы

Что такое WEB-сокет?

Web-сокет или как его принято называть Web-socket – это технология, которая позволяет клиенту … Стоп, стоп, стоп. Всё-таки у нас тут не википедия, а твои руки, поэтому перейдём на определение попроще. Представьте, что вы стоите в глухом лесу и кричите «Аууу…», в надежде что вам кто-то ответит. Так вот сокеты, это как раз связь с тем, кто должен вам что-то сказать. Например, я кричу: «/ХочуПить», а мне отвечают «{ “ответ” : ”возьми в портфеле лежит” }». Важно лишь понимать к кому мы обращаемся, т.е. сервер. И в какую сторону кричим, т.е. порт.

Зачастую web-сокеты используют для связи между двумя программами, которые достаточно трудно между собой подружить. Да, такое существует не смотря на существующее изобилие технологий.

web-сокеты

Как я использовал WEB-сокет?

В моём случае задача стояла следующая:

  1. Сотрудники техникума, в котором я работаю, снимают скан паспорта и аттестата и перемещают отсканированные файлы в расшаренную папку.
  2. Программа, установленная на персональном компьютере, сканирует паспорт и вытаскивает данные которые необходимо.
  3. Далее полученные данные подстраиваются в конкретные поля, а сканы добавляются в виде файлов на открытой страничке.

Задача поставлена, её необходимо выполнить.

Проблемы реализации проекта

В первую очередь идея была в том, чтобы напрямую с JS, проверять наполнение расшаренной папки на компьютере, и в момент, когда все файлы будут перемещены просто подхватывать их в поля. Проблема оказалась в том, что JS абсолютно не умеет работать с файлами, а именно брать файл по указанному пути и что-то с ним делать.

Знаю, что сейчас многие скажут, что это возможно, просто ты не очень умный человек, но опять-таки не забывайте, что мне нужно было парсить паспортные данные абитуриентов [ссылка на статью] и передать эти данные на уже открытое заявление.

После того как ссылки на google.com перешли за первые 10 показов выдачи было решено просто на просто разбить файл на массив байт и посредством Json, передать их по веб сокету.

И всё бы было не плохо, план пушка-гонка, вот только единственное что не работало, это то, что размер отправляемого массива явно отличался от массива, что приходил на сервер.

Как оказалось проблема заключалась в кодировке в JSON со стороны JS и C#. Тогда немного пораскинув мыслями, пришло гениальное решение разбивать не на массив байт, а просто кодировать в base64.

Base64 – стандарт кодирования двоичных данных при помощи только 64 символов ASCII. Грубо говоря, использовать вместо 16 пальцев на руке, только 4.

К великому счастью, кодирование и декодирование на обеих сторонах работало превосходно.

Реализация WEB-сокета для передачи файлов

Начнём с того, что напишем сервер, который будет прослушивать любой свободный порт, в частности у меня это будет 5000.

Проект будем реализовывать на C#, поэтому нам понадобиться несколько библиотек:

После того как библиотеки подключены, прописываем следующий код:

public partial class MainWindow : Window
{
    public class CustomFile
    {
        public string Name { get; set; }
        public string File { get; set; }
    }
    public class Response
    {
        public List<CustomFile> CustomFiles = new List<CustomFile>();
    }
    /// <summary>
    /// Входящий класс прослушивания сообщения
    /// </summary>
    public class GetData : WebSocketBehavior
    {
        /// <summary>
        /// Метод сообщения
        /// </summary>
        protected override void OnMessage(WebSocketSharp.MessageEventArgs e)
        {
            // Выводим надпись в консоль
            Console.WriteLine("Входящее подключение");
            // Переменная которая контролирует наполненность папки
            bool SendMessage = false;
            // Проверяем что папка не пустая
            while (!SendMessage)
            {
                // Получаем все файлы которые находятся в папке сканирования
                string[] ScanFiles = Directory.GetFiles(PathScan);
                // Если кол-во файлов в папке 5
                if (ScanFiles.Length == 5)
                {
                    // Создаём переменную, которая отвечает за верное наименование файлов в папке
                    bool SendMessageServer = true;
                    // Перебираем файлы в папке
                    foreach (string ScanFile in ScanFiles)
                    {
                        // Получаем наименование файла
                        string NameFile = Path.GetFileName(ScanFile);
                        // Если наименование файла соответствует одному из предложенных
                        if (NameFile == "А1.pdf" || NameFile == "А2.pdf" || NameFile == "А3.pdf" || NameFile == "П1.pdf" || NameFile == "П2.pdf") {}
                        else
                            // Если наименование не соответствует, запрещаем отправку
                            SendMessageServer = false;
                    }
                    // Если все файлы пронаименованы верно
                    if (SendMessageServer)
                    {
                        // Инициадизируем класс ответа
                        Response response = new Response();
                        // Перебираем файлы
                        foreach (string ScanFile in ScanFiles)
                        {
                            // Читаем файл по байтово
                            byte[] byteFile = File.ReadAllBytes(ScanFile);
                            // Получаем наименование файла
                            string NameFile = Path.GetFileName(ScanFile);
                            // Создаём объект, хранящий наименование файла и сам файл сконвертированный в base64
                            CustomFile newFile = new CustomFile()
                            {
                                Name = ScanFile,
                                File = Convert.ToBase64String(byteFile)
                            };
                            // Добавялем созданный объект в ответ
                            response.CustomFiles.Add(newFile);
                        }
                        // Возвращаем ответ на клиент
                        Send(JsonConvert.SerializeObject(response));
                        // Говорим что сообщение отправленно, чтобы не остаться в цикле
                        SendMessage = true;
                    }
                }
                else
                {
                    // Выводим сообщение об ожидании файлов
                    Console.WriteLine("Жду файлы: " + ScanFiles.Length);
                }
            }
        }
    }
    /// <summary>
    /// Путь до папки с файлами
    /// </summary>
    public static string PathScan = @"C:\Users\idol\Desktop\Scan\";
    /// <summary>
    /// Инициализируем класс, указывая IP-адрес и порт по которому будем прослушивать подключения
    /// </summary>
    public static WebSocketServer wssv = new WebSocketServer("ws://127.0.0.1:5000");
    public MainWindow()
    {
        InitializeComponent();
        // Подписываемся на команду /GetDocuments
        wssv.AddWebSocketService<GetData>("/GetDocuments");
        // Запускаем прослушивание входящего сообщения
        wssv.Start();
    }
}

Принцип алгоритма достаточно прост. В первую очередь мы подписываемся на прослушивание входящих сообщений от клиента. Если сообщение пришло, мы начинаем проверять папку с файлами. Если файлов в папке пять штук, и их имена соответствуют нашим требованиям. Происходит отправка наименований файлов и самих файлов разбитых побайтово и сконвертированных в base64 на клиент.

Далее переходим к клиенту:

// Переменная отвечающая за соединение с сервером
var Connecting = false;
// Web-сокет, который подключается к серверу
let Socket = new WebSocket("ws://127.0.0.1:5000/GetDocuments");
// Функция открытия сокета
Socket.onopen = function(e) {
	// Выводим сообщение в консоль
	console.log("[open] Соединение установлено");
	// Запоминаем что соединение произошло
	Connecting = true;
	// Получаем файлы
	GetPassport();
};
// Функция сообщщений
 Socket.onmessage = function(event) {
	// Распаршиваем информацию которуая поступила от сервера
	const data = JSON.parse(event.data);
	// Создаём объект файлов для паспорта
	var dt_passport = new DataTransfer();
	// Создаём объект файлов для аттестата
	var dt_attestat = new DataTransfer();
	// Получаем объект файлов
	var Files = data["CustomFiles"];
	for(var iDocument = 0; iDocument < Files.length; iDocument++) {
		// Получаем наименование файла
		var NameFile = Files[iDocument]["Name"].split("\\").pop();
		// Получаем сам файл
		var fileBase64 = Files[iDocument]["File"];
		// Генеруруем сам файл из base64, в File
		var NewFile = dataURLtoFile('data:application/pdf;base64,'+fileBase64, NameFile);
		// Если файл относится к аттестату
		if(NameFile[0] == "А") {
			// Добавляем в аттестат
			dt_attestat.items.add(NewFile);
		// Если файл относится к паспорту
		} else if(NameFile[0] == "П") {
			// Добавляем в паспорт
			dt_passport.items.add(NewFile);
		}
	}
	// Загружаем файлы на сервер
	id = 1;
	sendFiles(dt_attestat.files);	
	// Через 10 секунд загружаем файлы паспорта на сервер
	setTimeout(function() {
		id = 0;
		sendFiles(dt_passport.files);
	}, 10000);
	// Закрываем соединение
	Socket.close();
};
// Это взято из интернета))))
function dataURLtoFile(dataurl, filename) {
    var arr = dataurl.split(','),
        mime = arr[0].match(/:(.*?);/)[1],
        bstr = atob(arr[arr.length - 1]), 
        n = bstr.length, 
        u8arr = new Uint8Array(n);
    while(n--){
        u8arr[n] = bstr.charCodeAt(n);
    }
    return new File([u8arr], filename, {type:mime});
}
// Функция закрытия соединения  
Socket.onclose = function(event) {
	if (event.wasClean) {
		console.log(`[close] Соединение закрыто чисто, код=${event.code} причина=${event.reason}`);
	} else {
	  	console.log('[close] Соединение прервано');
	}
	// Запоминаем что соединение закрыто
	Connecting = false;
};
// Функция ошибок  
Socket.onerror = function(error) {
	// Выводим сообщение в консоль
	console.log(`[error]`);
};
// Функция получения паспорта
function GetPassport() {
	// Если соединение открыто
	if(Connecting)
		// Обращаемся к серверу
		Socket.send("GetDocuments");
}

Пояснение: При успешном открытии соединения между сервером и клиентом, мы получаем файлы от сервера. Далее эти файлы вносятся в специальные объекты и отправляются на сервер посредством AJAX.

Вот таким простым способом можно реализовать выбор и загрузку файлов на сервер.

Пока нет оценок, но вы можете быть первым!

Оцените