-
Notifications
You must be signed in to change notification settings - Fork 0
+клиент, +сервер #23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
+клиент, +сервер #23
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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) | ||
| { | ||
| 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) | ||
| { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Зачем передавать method, если он никак не используется? :) |
||
| var buffer = new byte[4096]; | ||
|
|
||
| var sizeResult = await stream.ReadAsync(buffer, 0, buffer.Length); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
| } | ||
| 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> |
| 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); |
| 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> |
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| } | ||
| } | ||
| 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(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
| }); | ||
| } | ||
| } | ||
| 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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Замечание про запись в поток актуально :)