Что такое WEB-сокет?
Web-сокет или как его принято называть Web-socket – это технология, которая позволяет клиенту … Стоп, стоп, стоп. Всё-таки у нас тут не википедия, а твои руки, поэтому перейдём на определение попроще. Представьте, что вы стоите в глухом лесу и кричите «Аууу…», в надежде что вам кто-то ответит. Так вот сокеты, это как раз связь с тем, кто должен вам что-то сказать. Например, я кричу: «/ХочуПить», а мне отвечают «{ “ответ” : ”возьми в портфеле лежит” }». Важно лишь понимать к кому мы обращаемся, т.е. сервер. И в какую сторону кричим, т.е. порт.
Зачастую web-сокеты используют для связи между двумя программами, которые достаточно трудно между собой подружить. Да, такое существует не смотря на существующее изобилие технологий.
Как я использовал WEB-сокет?
В моём случае задача стояла следующая:
- Сотрудники техникума, в котором я работаю, снимают скан паспорта и аттестата и перемещают отсканированные файлы в расшаренную папку.
- Программа, установленная на персональном компьютере, сканирует паспорт и вытаскивает данные которые необходимо.
- Далее полученные данные подстраиваются в конкретные поля, а сканы добавляются в виде файлов на открытой страничке.
Задача поставлена, её необходимо выполнить.
Проблемы реализации проекта
В первую очередь идея была в том, чтобы напрямую с 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.
Вот таким простым способом можно реализовать выбор и загрузку файлов на сервер.