Pracuję w C # i komunikuję się między 2 aplikacjami, które piszę. Polubiłem Web API i JSON. Teraz jestem w punkcie, w którym piszę procedurę wysyłania rekordu między dwoma serwerami, który zawiera dane tekstowe i plik.
Według Internetu mam użyć żądania wieloczęściowego / formularza danych, jak pokazano tutaj:
Pytanie SO „Formularze wieloczęściowe z klienta C #”
Zasadniczo piszesz zapytanie ręcznie w takim formacie:
Content-type: multipart/form-data, boundary=AaB03x
--AaB03x
content-disposition: form-data; name="field1"
Joe Blow
--AaB03x
content-disposition: form-data; name="pics"; filename="file1.txt"
Content-Type: text/plain
... contents of file1.txt ...
--AaB03x--
Skopiowano z RFC 1867 - Przesyłanie plików w formacie HTML w formacie HTML
Ten format jest dość niepokojący dla kogoś, kto jest przyzwyczajony do ładnych danych JSON. Oczywiście rozwiązaniem jest utworzenie żądania JSON i kodowanie pliku Base64 i zakończenie takiego żądania:
{
"field1":"Joe Blow",
"fileImage":"JVBERi0xLjUKJe..."
}
I możemy korzystać z serializacji i deserializacji JSON w dowolnym miejscu. Ponadto kod do wysyłania tych danych jest dość prosty. Wystarczy utworzyć klasę do serializacji JSON, a następnie ustawić właściwości. Właściwość ciągu pliku jest ustawiona w kilku trywialnych wierszach:
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
byte[] file_bytes = new byte[fs.Length];
fs.Read(file_bytes, 0, file_bytes.Length);
MyJsonObj.fileImage = Convert.ToBase64String(file_bytes);
}
Nigdy więcej głupich separatorów i nagłówków dla każdego elementu. Teraz pozostałym pytaniem jest wydajność. Więc profilowałem to. Mam zestaw 50 przykładowych plików, które musiałbym wysłać za pomocą drutu o zakresie od 50 KB do około 1,5 MB. Najpierw napisałem kilka wierszy, aby po prostu przesłać strumieniowo w pliku do tablicy bajtów, aby porównać to z logiką przesyłaną strumieniowo w pliku, a następnie przekonwertować na strumień Base64. Poniżej 2 fragmenty kodu, które profilowałem:
Bezpośrednie przesyłanie strumieniowe do profilu danych wieloczęściowych / formularzy
var timer = new Stopwatch();
timer.Start();
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
byte[] test_data = new byte[fs.Length];
fs.Read(test_data, 0, test_data.Length);
}
timer.Stop();
long test = timer.ElapsedMilliseconds;
//Write time elapsed and file size to CSV file
Przesyłaj strumieniowo i koduj do profilu, tworząc żądanie JSON
var timer = new Stopwatch();
timer.Start();
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
byte[] file_bytes = new byte[fs.Length];
fs.Read(file_bytes, 0, file_bytes.Length);
ret_file = Convert.ToBase64String(file_bytes);
}
timer.Stop();
long test = timer.ElapsedMilliseconds;
//Write time elapsed, file size, and length of UTF8 encoded ret_file string to CSV file
W rezultacie prosty odczyt zawsze zajmował 0 ms, ale kodowanie Base64 trwało 5 ms. Poniżej znajdują się najdłuższe czasy:
File Size | Output Stream Size | Time
1352KB 1802KB 5ms
1031KB 1374KB 7ms
463KB 617KB 1ms
Jednak w produkcji nigdy nie pisałeś na ślepo danych wieloczęściowych / formularzy bez uprzedniego sprawdzenia ogranicznika, prawda? Zmodyfikowałem więc kod danych formularza, aby sprawdzał bajty separatora w samym pliku, aby upewnić się, że wszystko zostanie poprawnie przeanalizowane. Nie napisałem zoptymalizowanego algorytmu skanowania, więc po prostu zmniejszyłem separator, aby nie marnował dużo czasu.
var timer = new Stopwatch();
timer.Start();
using (FileStream fs = File.Open(file_path, FileMode.Open, FileAccess.Read, FileShare.Read))
{
byte[] test_data = new byte[fs.Length];
fs.Read(test_data, 0, test_data.Length);
string delim = "--DXX";
byte[] delim_checker = Encoding.UTF8.GetBytes(delim);
for (int i = 0; i <= test_data.Length - delim_checker.Length; i++)
{
bool match = true;
for (int j = i; j < i + delim_checker.Length; j++)
{
if (test_data[j] != delim_checker[j - i])
{
match = false;
break;
}
}
if (match)
{
break;
}
}
}
timer.Stop();
long test = timer.ElapsedMilliseconds;
Teraz wyniki pokazują, że metoda form-data będzie faktycznie znacznie wolniejsza. Poniżej znajdują się wyniki z czasami> 0ms dla każdej metody:
File Size | FormData Time | Json/Base64 Time
181Kb 1ms 0ms
1352Kb 13ms 4ms
463Kb 4ms 5ms
133Kb 1ms 0ms
133Kb 1ms 0ms
129Kb 1ms 0ms
284Kb 2ms 1ms
1031Kb 9ms 3ms
Nie wydaje się, żeby zoptymalizowany algorytm lepiej sprawdziłby się, ponieważ mój ogranicznik miał tylko 5 znaków. W każdym razie nie trzykrotnie lepiej, co jest zaletą wynikającą z kodowania Base64 zamiast sprawdzania bajtów pliku w poszukiwaniu separatora.
Oczywiście kodowanie Base64 zwiększy rozmiar, jak pokazałem w pierwszej tabeli, ale tak naprawdę nie jest tak źle nawet z UTF-8 obsługującym Unicode i dobrze by się skompresowało, jeśli zajdzie taka potrzeba. Ale prawdziwą korzyścią jest to, że mój kod jest ładny, przejrzysty i łatwo zrozumiały i nie zaszkodzi moim oczom spojrzeć na ładunek żądania JSON.
Dlaczego więc, u licha, nikt nie miałby po prostu kodować Base64 w JSON zamiast korzystać z danych wieloczęściowych / formularzy? Istnieją Standardy, ale zmieniają się stosunkowo często. Normy są tak naprawdę tylko sugestiami, prawda?