Доброго времени суток. Возникла проблема при попытке передать файл с помощью сокетов. Сервер обрабатывает не все отправленные на него данные (например из 26 кб получает 16 кб). Заметил один непонятный момент: если между методом socket.Receive() и записью в MemoryStream добавить строчку кода, например записи данных в лог файл (File.AppendAllText("Log.log", string.Format("Received={0}\r\n", received)), то все работает нормально. Подскажите что я делаю не так? Заранее спасибо
Код сервера:
namespace TCPServer {
class Program {
static void Main(string[] args) {
try {
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
Socket listenSoc = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listenSoc.Bind(endPoint);
listenSoc.Listen(10);
Console.WriteLine("Server is running...");
int bytes = 0;
const int bufferSize = 8192;
while (true) {
Socket handler = listenSoc.Accept();
NetFile file;
using (MemoryStream memStream = new MemoryStream()) {
byte[] buffer = new byte[bufferSize];
do {
int received = handler.Receive(buffer);
//File.AppendAllText("Log.log", string.Format("Received={0}\r\n", received));
memStream.Write(buffer, 0, received);
bytes += received;
}
while (handler.Available > 0);
file = new NetFile(memStream.ToArray());
}
Console.WriteLine("Size of received data: " + bytes.ToString() + " bytes");
using (FileStream stream = new FileStream(file.FileName, FileMode.Create, FileAccess.Write)) {
stream.Write(file.Data, 0, file.Data.Length);
}
handler.Shutdown(SocketShutdown.Both);
handler.Close();
bytes = 0;
}
}
catch (Exception error) {
Console.WriteLine(error.ToString());
Console.ReadKey();
}
}
}
}
Код клиента:
namespace TcpClient {
class Program {
static void Main(string[] args) {
try {
IPEndPoint endPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8080);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(endPoint);
Console.WriteLine("Client connected");
Console.WriteLine("Sending file...");
using (FileStream stream = new FileStream("c:\\1.sql", FileMode.Open, FileAccess.Read)) {
byte[] data = new byte[stream.Length];
int length = stream.Read(data, 0, data.Length);
NetFile file = new NetFile();
file.FileName = Path.GetFileName(stream.Name);
file.Data = data;
byte[] to = file.ToArray();
socket.Send(to);
}
Console.WriteLine("File sended");
socket.Shutdown(SocketShutdown.Both);
socket.Close();
Console.ReadKey();
}
catch (Exception error) {
Console.WriteLine(error.ToString());
Console.ReadKey();
}
}
}
}
Класс с информацией об отправляемом файле:
namespace ExchangeEngine.Net {
[Serializable]
public class NetFile {
private byte[] _data;
public NetFile() { }
public NetFile(byte[] data) {
NetFile file = FromArray(data);
FileName = file.FileName;
Data = file.Data;
}
public string FileName { get; set; }
public byte[] Data {
get {
return _data;
}
set {
_data = value;
Checksum = GetMD5Hash(_data);
}
}
public string Checksum { get; set; }
public static string GetMD5Hash(byte[] source) {
StringBuilder hash = new StringBuilder();
using (MD5 md5Hasher = MD5.Create()) {
byte[] data = md5Hasher.ComputeHash(source);
for (int index = 0; index < data.Length; index++) {
hash.Append(data[index].ToString("x2"));
}
return hash.ToString();
}
}
public byte[] ToArray() {
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream()) {
formatter.Serialize(stream, this);
return stream.ToArray();
}
}
public static NetFile FromArray(byte[] data) {
BinaryFormatter formatter = new BinaryFormatter();
using (MemoryStream stream = new MemoryStream(data)) {
stream.Position = 0;
return (NetFile)formatter.Deserialize(stream);
}
}
}
}
Как показало расследование в комментариях, виноват код
do {
int received = handler.Receive(buffer);
//File.AppendAllText("Log.log", string.Format("Received={0}\r\n", received));
memStream.Write(buffer, 0, received);
bytes += received;
}
while (handler.Available > 0);
Дело в том, что из-за сетевых задержек часть данных может быть ещё в пути, когда начало уже вычитано. Сокет работает в потоковом, а не блочном режиме, и не знает, что будут ещё доставлены данные. Поэтому использовать Available
для распознавания конца передачи нецелесообразно.
Самое простое и практичное решение — перед передачей информации передавать её длину. На читающей стороне принимать сначала длину, а потом читать до тех пор, пока не прочитается нужное количество байт.
Литература по теме: Message Framing и вообще TCP/IP .NET Sockets FAQ в блоге Стивена Клири.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Не получается сделать так, чтобы браузер перезагружал страницы из папки Example при их измененииУ меня стоит сборка Xampp (Apache)
Встречались ли вам статьи где с реальными примерами был реализован blockchain ?