Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 89 additions & 0 deletions NewFTPServer/FTPClient/Client.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Net.Sockets;
using System.Text;

namespace SimpleFTP;

/// <summary>
/// FTP Client implementation class
/// </summary>
public class Client
{
private static int port;
private static string? hostname;

/// <summary>
/// Class constructor
/// </summary>
public Client(int port, string hostname)
{
Client.port = port;
Client.hostname = hostname;
Console.WriteLine("Client started!");
}

/// <summary>
/// Downloading a file from the server
/// </summary>
/// <param name="filePath">The relative path of the file from the specified path on the server</param>
public async Task<string> Get(string filePath)
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Замечание про запись в поток актуально :)

if (hostname == null)
{
throw new ArgumentNullException();
}

var client = new TcpClient();
await client.ConnectAsync(hostname, port);

var stream = client.GetStream();

await stream.WriteAsync(Encoding.UTF8.GetBytes($"2 {filePath}\n"));
await stream.FlushAsync();

return await GetResultFromStreamForGet(stream);
}

/// <summary>
/// Listing files in a directory on the server
/// </summary>
/// <param name="directoryPath">The relative path of the directory from the specified path on the server</param>
public async Task<string?> List(string directoryPath)
{
if (hostname == null)
{
throw new ArgumentNullException();
}

var client = new TcpClient();
await client.ConnectAsync(hostname, port);

var stream = client.GetStream();

await stream.WriteAsync(Encoding.UTF8.GetBytes($"1 {directoryPath}\n"));
await stream.FlushAsync();

return await GetResultFromStreamForList(stream);
}

private static async Task<string> GetResultFromStreamForGet(NetworkStream stream)
{

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Зачем передавать method, если он никак не используется? :)
И здесь некоторое неследование протоколу: считывать ответ надо до символа конца строки в случае list-запроса; считывать сначала длину ответа, а потом ответ, используя эту длину, для get-запроса.

var buffer = new byte[4096];

var sizeResult = await stream.ReadAsync(buffer, 0, buffer.Length);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Уже лучше, но здесь всё-таки надо сначала считывать длину ответа (до пробела), а потом ответ, используя эту длину

var result = new StringBuilder();
result.Append(Encoding.UTF8.GetString(buffer, 0, sizeResult));

return result.ToString();
}

private static async Task<string?> GetResultFromStreamForList(NetworkStream stream)
{
var reader = new StreamReader(stream, Encoding.UTF8);
var result = await reader.ReadLineAsync();
if (result != null)
{
result.Append('\n');
}
return result;
}
}
10 changes: 10 additions & 0 deletions NewFTPServer/FTPClient/FTPClient.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
5 changes: 5 additions & 0 deletions NewFTPServer/FTPClient/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
using SimpleFTP;

var client = new Client(8888, "localhost");
var result = await client.Get("../local.txt");
Console.WriteLine(result);
10 changes: 10 additions & 0 deletions NewFTPServer/FTPServer/FTPServer.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

</Project>
16 changes: 16 additions & 0 deletions NewFTPServer/FTPServer/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using SimpleFTP;

class Program
{
static async Task Main()
Comment on lines +3 to +5

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Это не нужно

{
Server server = new SimpleFTP.Server("C:/", 8888);

Task serverTask = Task.Run(async () => await server.Start());

Console.WriteLine("нажмите на любую клавишу для остановки серверка");
Console.ReadKey();

server.Stop();
}
}
197 changes: 197 additions & 0 deletions NewFTPServer/FTPServer/Server.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System.Diagnostics;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace SimpleFTP;

/// <summary>
/// A class that implements an FTP server
/// </summary>
public class Server
{
private static string? locate;
private static TcpListener? listener;
private static List<TcpClient>? clients;
private static CancellationTokenSource? cancellationToken;
private static List<Task>? tasks;

/// <summary>
/// Class constructor
/// </summary>
/// <param name="locate">The absolute path of the server location</param>
public Server(string locate, int port)
{
if (locate == null)
{
throw new ArgumentNullException();
}
Server.locate = locate;
listener = new(IPAddress.Any, port);
cancellationToken = new();
tasks = new();
clients = new();
}

/// <summary>
/// Starting the server
/// </summary>
public async Task Start()
{
if (listener == null || clients == null
|| tasks == null || cancellationToken == null)
{
throw new ArgumentNullException();
}

listener.Start();
Console.WriteLine("Server started working");
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Waiting a client");
var client = await listener.AcceptTcpClientAsync(cancellationToken.Token);
clients.Add(client);
tasks.Add(Listen(client));
}

Task.WaitAll(tasks.ToArray());
foreach (var client in clients)
{
client.Close();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Соединения лучше сразу закрывать после того, как сервер ответил" -- не исправлено

}

tasks.Clear();

listener.Stop();
}

private static Task Listen(TcpClient client)
{
if (cancellationToken == null)
{
Stop();
throw new ArgumentNullException(nameof(cancellationToken));
}

return Task.Run(async () =>
{
if (client == null)
{
Stop();
throw new ArgumentNullException("client");
}

var stream = client.GetStream();

if (!cancellationToken.IsCancellationRequested)
{
var reader = new StreamReader(stream, Encoding.UTF8);
var stringCommand = await reader.ReadLineAsync();

if (stringCommand == null)
{
stream.Close();
Stop();
throw new InvalidOperationException();
}

if (stringCommand[0] == '1')
{
List(stringCommand.TrimStart('1', ' '), stream);
}
else
{
Get(stringCommand.TrimStart('2', ' '), stream);
}
}
}
);
}

/// <summary>
/// Server shutdown
/// </summary>
public static void Stop()
{
if (listener != null && cancellationToken != null)
{
cancellationToken.Cancel();
}
}

private static void Get(string directory, NetworkStream stream)
{
Task.Run(async () =>
{
if (locate == null)
{
stream.Close();
Stop();
throw new NullReferenceException();
}

string combinePath = Path.Combine(locate, directory);
DirectoryInfo info = new DirectoryInfo(combinePath);
var path = info.FullName;

if (!File.Exists(path))
{
await stream.WriteAsync(Encoding.UTF8.GetBytes("-1"));
}
else
{
var textBytes = File.ReadAllBytes(path);
await stream.WriteAsync(Encoding.UTF8.GetBytes($"{textBytes.Length} "));
await stream.WriteAsync(textBytes);
}
stream.Close();
});
}
private static void List(string directory, NetworkStream stream)
{
Task.Run(async () =>
{
if (locate == null)
{
stream.Close();
Stop();
throw new NullReferenceException();
}

string combinePath = Path.Combine(locate, directory);
DirectoryInfo info = new DirectoryInfo(combinePath);
var path = info.FullName;

if (!Directory.Exists(path))
{
await stream.WriteAsync(Encoding.UTF8.GetBytes("-1\n"));
}
else
{
var filesAndDirectories = new StringBuilder();
var directories = Directory.GetDirectories(path);

var sizeFilesAndDirectories = directories.Length;

foreach (var directory in directories)
{
filesAndDirectories.Append(" " + directory.Substring(directory.LastIndexOf('\\') + 1) + " true");
}

var files = Directory.GetFiles(path);
sizeFilesAndDirectories += files.Length;

foreach (var file in files)
{
filesAndDirectories.Append(" " + file.Substring(file.LastIndexOf('\\') + 1) + " false");
}

filesAndDirectories.Append('\n');
filesAndDirectories.Insert(0, sizeFilesAndDirectories.ToString());

await stream.WriteAsync(Encoding.UTF8.GetBytes(filesAndDirectories.ToString()));
}
stream.Close();
});
}
}
37 changes: 37 additions & 0 deletions NewFTPServer/NewFTPServer.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.4.33403.182
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FTPServer", "FTPServer\FTPServer.csproj", "{D9E2CBBA-EE73-4149-A774-70DC12B4886E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FTPClient", "FTPClient\FTPClient.csproj", "{1C09410D-0CEF-4464-8162-7F4F16EC5066}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestsFTP", "TestsFTP\TestsFTP.csproj", "{98F4C7F9-B324-4D82-AE9B-965025AF9B6C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D9E2CBBA-EE73-4149-A774-70DC12B4886E}.Release|Any CPU.Build.0 = Release|Any CPU
{1C09410D-0CEF-4464-8162-7F4F16EC5066}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1C09410D-0CEF-4464-8162-7F4F16EC5066}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1C09410D-0CEF-4464-8162-7F4F16EC5066}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1C09410D-0CEF-4464-8162-7F4F16EC5066}.Release|Any CPU.Build.0 = Release|Any CPU
{98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98F4C7F9-B324-4D82-AE9B-965025AF9B6C}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {39F5B087-17DF-4DAF-9AC5-B12D1F761E54}
EndGlobalSection
EndGlobal
Loading