Загрузка файла через TcpClient с HTTP-сервера
Я изучаю HTTP и решаю использовать TcpClient для загрузки файлов из Интернета.
private void requireBtn_Click(object sender, EventArgs e) {
var client = new TcpClient(addressBox.Text, 80);
var networkStream = client.GetStream();
var sWriter = new StreamWriter(networkStream, Encoding.Default);
foreach (string querLine in queryBox.Lines) {
sWriter.WriteLine(querLine);
}
sWriter.WriteLine();
sWriter.Flush();
string responceText = "";
var sReader = new StreamReader(networkStream);
string respLine;
while ((respLine = sReader.ReadLine()).Length > 0) {
responceText += respLine + "\r\n";
}
responceBox.Text = responceText;
Regex reContentLength = new Regex(@"(?<=Content-Length:\s)\d+", RegexOptions.IgnoreCase);
Int32 contentLength = Int32.Parse(reContentLength.Match(responceText).Value);
this.Text = contentLength.ToString();
if (networkStream.CanRead) {
var fileStream = new FileStream(@"C:\img.png", FileMode.Create);
byte[] buffer = new byte[1024];
int numberOfBytesRead = 0;
do {
numberOfBytesRead = networkStream.Read(buffer, 0, buffer.Length);
fileStream.Write(buffer, 0, numberOfBytesRead);
}
while (networkStream.DataAvailable);
fileStream.Flush();
fileStream.Close();
MessageBox.Show("Done!");
}
else
MessageBox.Show("Fail!");
client.Close();
}
Но загруженный файл поврежден. Я не могу понять, почему... Исходный файл
3 ответа
private void requireBtn_Click(object sender, EventArgs e) {
string host = "atomAltera.SkyHost.ge";
string query = "GET /mems.txt HTTP/1.1\r\n" +
"Host: atomAltera.SkyHost.ge\r\n" +
"User-Agent: NuclightWeb\r\n" +
"Connection: close\r\n"+
"\r\n";
var client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
client.Connect(host, 80);
var networkStream = new NetworkStream(client);
var bytes = Encoding.Default.GetBytes(query);
networkStream.Write(bytes, 0, bytes.Length);
var bReader = new BinaryReader(networkStream, Encoding.Default);
string responce = "";
string line;
char c;
do {
line = "";
c = '\u0000';
while (true) {
c = bReader.ReadChar();
if (c == '\r')
break;
line += c;
}
c = bReader.ReadChar();
responce += line + "\r\n";
} while (line.Length > 0);
responceBox.Text = responce;
Regex reContentLength = new Regex(@"(?<=Content-Length:\s)\d+", RegexOptions.IgnoreCase);
Int32 contentLength = Int32.Parse(reContentLength.Match(responce).Value);
this.Text = contentLength.ToString();
var fileStream = new FileStream(@"C:\m.txt", FileMode.Create);
byte[] buffer = new byte[4 * 1024];
int n = 0;
int read = 0;
while (n < contentLength) {
if (networkStream.DataAvailable) {
read = networkStream.Read(buffer, 0, buffer.Length);
n += read;
fileStream.Write(buffer, 0, read);
}
}
fileStream.Flush();
fileStream.Close();
client.Close();
}
Оно работает! Может быть StreamReader читает лишние байты?
Я предлагаю использовать contentLength вместо networkStream.DataAvailable, чтобы определить, когда передача данных завершена.DataAvailable просто означает, что есть данные, полученные стеком ОС, однако, в зависимости от сетевой маршрутизации и фрагментации, они могут не прийти к моменту проверки состояния, поэтому вы прекращаете получать эти данные слишком рано.
Чтобы доказать или опровергнуть это, вы можете сравнить двоичные файлы между тем, что загрузила ваша программа, и тем, что загружал бы обычный браузер. Если первое урезано, то мы нашли проблему.
Вам нужно использовать значение из Content-Length
заголовок (который может быть больше, чем 32-разрядное целое число, кстати). Продолжайте читать, пока не будет получено столько байтов.
Однако сервер может не использовать Content-Length
заголовок на всех. Это может использовать Transfer-Encoding
заголовок, который включает в себя chunked
кодирование, или он может вообще не использовать заголовок. HTTP определяет несколько различных способов определения длины данных, и вы должны поддерживать столько, сколько сможете, чтобы учесть все виды серверов. См. RFC 2616, разделы 4.3 и 4.4 для конкретных правил, которым вы должны следовать.