دوچرخه‌سواری پیشرفته یا برنامه مشتری-سرور مبتنی بر چارچوب C#.Net

ورود

همه چیز از آنجا شروع شد که یکی از همکاران به من پیشنهاد داد که یک وب سرویس کوچک ایجاد کنم. قرار بود چیزی شبیه Tinder باشد، اما برای جمعیت فناوری اطلاعات. عملکرد بسیار ساده است، شما ثبت نام می کنید، یک نمایه را پر می کنید و به موضوع اصلی می روید، یعنی پیدا کردن فردی برای صحبت و گسترش ارتباطات خود و ایجاد آشنایی های جدید.

در اینجا باید عقب نشینی کنم و کمی از خودم بگویم تا در آینده روشن تر شود که چرا چنین قدم هایی در توسعه برداشتم.

در حال حاضر من سمت Technical Artist را در یک استودیوی بازی دارم، تجربه برنامه نویسی من در سی شارپ فقط بر اساس نوشتن اسکریپت ها و ابزارهای کاربردی برای Unity و علاوه بر این، ایجاد پلاگین هایی برای کارهای سطح پایین با دستگاه های اندرویدی است. من هنوز از این دنیای کوچک جسارت نکرده بودم و بعد چنین فرصتی پیش آمد.

بخش 1. نمونه سازی قاب

پس از اینکه تصمیم گرفتم این سرویس چگونه باشد، شروع به جستجوی گزینه هایی برای پیاده سازی کردم. ساده ترین کار این است که نوعی راه حل آماده پیدا کنیم، که مانند جغد روی کره زمین، مکانیک ما را بکشد و کل چیز را در معرض انتقاد عمومی قرار دهد.
اما این جالب نیست، من هیچ چالش یا حسی در آن ندیدم و بنابراین شروع به مطالعه فناوری های وب و روش های تعامل با آنها کردم.

مطالعه را با دیدن مقالات و مستندات در C# .Net شروع کردم. در اینجا راه های مختلفی برای تکمیل کار پیدا کردم. مکانیسم های زیادی برای تعامل با شبکه وجود دارد، از راه حل های کامل مانند سرویس های ASP.Net یا Azure گرفته تا تعامل مستقیم با اتصالات TcpHttp.

پس از انجام اولین تلاش خود با ASP، بلافاصله آن را رد کردم؛ به نظر من، این تصمیم برای سرویس ما بسیار دشوار بود. ما حتی از یک سوم قابلیت های این پلتفرم استفاده نمی کنیم، بنابراین به جستجوی خود ادامه دادم. انتخاب بین TCP و Http مشتری-سرور بود. در اینجا، در هابره، به مقاله ای در مورد آن برخوردم سرور چند رشته ای، پس از جمع آوری و آزمایش آن، تصمیم گرفتم به طور خاص بر روی تعامل با اتصالات TCP تمرکز کنم، به دلایلی فکر کردم که http به من اجازه ایجاد یک راه حل بین پلتفرم را نمی دهد.

اولین نسخه سرور شامل پردازش اتصال، ارائه محتوای ثابت صفحه وب و شامل پایگاه داده کاربر بود. و برای شروع، تصمیم گرفتم قابلیتی برای کار با سایت ایجاد کنم تا بعداً بتوانم پردازش برنامه را در اندروید و iOS اضافه کنم.

در اینجا چند کد وجود دارد
موضوع اصلی که مشتریان را در یک حلقه بی پایان دریافت می کند:

using System;
using System.Net.Sockets;
using System.Net;
using System.Threading;

namespace ClearServer
{

    class Server
    {
        TcpListener Listener;
        public Server(int Port)
        {
            Listener = new TcpListener(IPAddress.Any, Port);
            Listener.Start();

            while (true)
            {
                TcpClient Client = Listener.AcceptTcpClient();
                Thread Thread = new Thread(new ParameterizedThreadStart(ClientThread));
                Thread.Start(Client);
            }
        }

        static void ClientThread(Object StateInfo)
        {
            new Client((TcpClient)StateInfo);
        }

        ~Server()
        {
            if (Listener != null)
            {
                Listener.Stop();
            }
        }

        static void Main(string[] args)
        {
            DatabaseWorker sqlBase = DatabaseWorker.GetInstance;

            new Server(80);
        }
    }
}

خود کنترل کننده مشتری:

using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer
{
    class Client
    {


        public Client(TcpClient Client)
        {

            string Message = "";
            byte[] Buffer = new byte[1024];
            int Count;
            while ((Count = Client.GetStream().Read(Buffer, 0, Buffer.Length)) > 0)
            {
                Message += Encoding.UTF8.GetString(Buffer, 0, Count);

                if (Message.IndexOf("rnrn") >= 0 || Message.Length > 4096)
                {
                    Console.WriteLine(Message);
                    break;
                }
            }

            Match ReqMatch = Regex.Match(Message, @"^w+s+([^s?]+)[^s]*s+HTTP/.*|");
            if (ReqMatch == Match.Empty)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            string RequestUri = ReqMatch.Groups[1].Value;
            RequestUri = Uri.UnescapeDataString(RequestUri);
            if (RequestUri.IndexOf("..") >= 0)
            {
                ErrorWorker.SendError(Client, 400);
                return;
            }
            if (RequestUri.EndsWith("/"))
            {
                RequestUri += "index.html";
            }

            string FilePath =

quot;D:/Web/TestSite{RequestUri}";

if (!File.Exists(FilePath))
{
ErrorWorker.SendError(Client, 404);
return;
}

string Extension = RequestUri.Substring(RequestUri.LastIndexOf('.'));

string ContentType = "";

switch (Extension)
{
case ".htm":
case ".html":
ContentType = "text/html";
break;
case ".css":
ContentType = "text/css";
break;
case ".js":
ContentType = "text/javascript";
break;
case ".jpg":
ContentType = "image/jpeg";
break;
case ".jpeg":
case ".png":
case ".gif":
ContentType =


quot;image/{Extension.Substring(1)}";
break;
default:
if (Extension.Length > 1)
{
ContentType =


quot;application/{Extension.Substring(1)}";
}
else
{
ContentType = "application/unknown";
}
break;
}

FileStream FS;
try
{
FS = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
}
catch (Exception)
{
ErrorWorker.SendError(Client, 500);
return;
}

string Headers =


quot;HTTP/1.1 200 OKnContent-Type: {ContentType}nContent-Length: {FS.Length}nn";
byte[] HeadersBuffer = Encoding.ASCII.GetBytes(Headers);
Client.GetStream().Write(HeadersBuffer, 0, HeadersBuffer.Length);

while (FS.Position < FS.Length)
{
Count = FS.Read(Buffer, 0, Buffer.Length);
Client.GetStream().Write(Buffer, 0, Count);
}

FS.Close();
Client.Close();
}
}
}

و اولین پایگاه داده ساخته شده بر روی SQL محلی:

using System;
using System.Data.Linq;
namespace ClearServer
{
    class DatabaseWorker
    {

        private static DatabaseWorker instance;

        public static DatabaseWorker GetInstance
        {
            get
            {
                if (instance == null)
                    instance = new DatabaseWorker();
                return instance;
            }
        }


        private DatabaseWorker()
        {
            string connectionStr = databasePath;
            using (DataContext db = new DataContext(connectionStr))
            {
                Table<User> users = db.GetTable<User>();
                foreach (var item in users)
                {
                    Console.WriteLine(

quot;{item.login} {item.password}");
}
}
}
}
}

همانطور که می بینید، این نسخه تفاوت کمی با نسخه موجود در مقاله دارد. در واقع، در اینجا ما فقط بارگذاری صفحات را از یک پوشه در رایانه و یک پایگاه داده اضافه کردیم (که اتفاقاً به دلیل معماری اتصال نادرست در این نسخه کار نکرد).

فصل 2. پیچ کردن چرخ ها

پس از آزمایش سرور، به این نتیجه رسیدم که این یک راه حل عالی خواهد بود(اسپویلر: نه، برای خدمات ما، بنابراین پروژه شروع به کسب منطق کرد.
گام به گام، ماژول های جدید ظاهر شدند و عملکرد سرور گسترش یافت. سرور یک دامنه آزمایشی و رمزگذاری اتصال SSL به دست آورده است.

کد کمی بیشتر که منطق پردازش سرور و کلاینت را توصیف می کند
نسخه به روز شده سرور که شامل استفاده از گواهی است.

using System;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Security.Permissions;
using System.Security.Policy;
using System.Threading;


namespace ClearServer
{

    sealed class Server
    {
        readonly bool ServerRunning = true;
        readonly TcpListener sslListner;
        public static X509Certificate serverCertificate = null;
        Server()
        {
            serverCertificate = X509Certificate.CreateFromSignedFile(@"C:sslitinder.online.crt");
            sslListner = new TcpListener(IPAddress.Any, 443);
            sslListner.Start();
            Console.WriteLine("Starting server.." + serverCertificate.Subject + "n" + Assembly.GetExecutingAssembly().Location);
            while (ServerRunning)
            {
                TcpClient SslClient = sslListner.AcceptTcpClient();
                Thread SslThread = new Thread(new ParameterizedThreadStart(ClientThread));
                SslThread.Start(SslClient);
            }
            
        }
        static void ClientThread(Object StateInfo)
        {
            new Client((TcpClient)StateInfo);
        }

        ~Server()
        {
            if (sslListner != null)
            {
                sslListner.Stop();
            }
        }

        public static void Main(string[] args)
        {
            if (AppDomain.CurrentDomain.IsDefaultAppDomain())
            {
                Console.WriteLine("Switching another domain");
                new AppDomainSetup
                {
                    ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase
                };
                var current = AppDomain.CurrentDomain;
                var strongNames = new StrongName[0];
                var domain = AppDomain.CreateDomain(
                    "ClearServer", null,
                    current.SetupInformation, new PermissionSet(PermissionState.Unrestricted),
                    strongNames);
                domain.ExecuteAssembly(Assembly.GetExecutingAssembly().Location);
            }
            new Server();
        }
    }
}

و همچنین یک کنترل کننده مشتری جدید با مجوز SSL:

using ClearServer.Core.Requester;
using System;
using System.Net.Security;
using System.Net.Sockets;

namespace ClearServer
{
    public class Client
    {
        public Client(TcpClient Client)
        {
            SslStream SSlClientStream = new SslStream(Client.GetStream(), false);
            try
            {
                SSlClientStream.AuthenticateAsServer(Server.serverCertificate, clientCertificateRequired: false, checkCertificateRevocation: true);
            }
            catch (Exception e)
            {
                Console.WriteLine(
                    "---------------------------------------------------------------------n" +


quot;|{DateTime.Now:g}n|------------n|{Client.Client.RemoteEndPoint}n|------------n|Exception: {e.Message}n|------------n|Authentication failed - closing the connection.n" +
"---------------------------------------------------------------------n");
SSlClientStream.Close();
Client.Close();
}
new RequestContext(SSlClientStream, Client);
}

}
}

اما از آنجایی که سرور منحصراً بر روی یک اتصال TCP اجرا می شود، لازم است ماژولی ایجاد شود که بتواند زمینه درخواست را تشخیص دهد. من تصمیم گرفتم که تجزیه‌کننده‌ای در اینجا مناسب باشد که درخواست مشتری را به بخش‌های جداگانه تقسیم کند تا بتوانم با آن تعامل داشته باشم تا پاسخ‌های لازم را به مشتری بدهم.

تجزیه کننده

using ClearServer.Core.UserController;
using ReServer.Core.Classes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;

namespace ClearServer.Core.Requester
{
    public class RequestContext
    {
        public string Message = "";
        private readonly byte[] buffer = new byte[1024];
        public string RequestMethod;
        public string RequestUrl;
        public User RequestProfile;
        public User CurrentUser = null;
        public List<RequestValues> HeadersValues;
        public List<RequestValues> FormValues;
        private TcpClient TcpClient;

        private event Action<SslStream, RequestContext> OnRead = RequestHandler.OnHandle;

        DatabaseWorker databaseWorker = new DatabaseWorker();

        public RequestContext(SslStream ClientStream, TcpClient Client)
        {

            this.TcpClient = Client;
            try
            {
                ClientStream.BeginRead(buffer, 0, buffer.Length, ClientRead, ClientStream);
            }
            catch { return; }
        }
        private void ClientRead(IAsyncResult ar)
        {
            SslStream ClientStream = (SslStream)ar.AsyncState;

            if (ar.IsCompleted)
            {
                Message = Encoding.UTF8.GetString(buffer);
                Message = Uri.UnescapeDataString(Message);
                Console.WriteLine(

quot;n{DateTime.Now:g} Client IP:{TcpClient.Client.RemoteEndPoint}n{Message}");
RequestParse();
HeadersValues = HeaderValues();
FormValues = ContentValues();
UserParse();
ProfileParse();
OnRead?.Invoke(ClientStream, this);
}
}

private void RequestParse()
{
Match methodParse = Regex.Match(Message, @"(^w+)s+([^s?]+)[^s]*s+HTTP/.*|");
RequestMethod = methodParse.Groups[1].Value.Trim();
RequestUrl = methodParse.Groups[2].Value.Trim();
}
private void UserParse()
{
string cookie;
try
{
if (HeadersValues.Any(x => x.Name.Contains("Cookie")))
{
cookie = HeadersValues.FirstOrDefault(x => x.Name.Contains("Cookie")).Value;
try
{
CurrentUser = databaseWorker.CookieValidate(cookie);
}
catch { }
}
}
catch { }

}
private List<RequestValues> HeaderValues()
{
var values = new List<RequestValues>();
var parse = Regex.Matches(Message, @"(.*?): (.*?)n");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim()
});
}
return values;
}

private void ProfileParse()
{
if (RequestUrl.Contains("@"))
{
RequestProfile = databaseWorker.FindUser(RequestUrl.Substring(2));
RequestUrl = "/profile";
}
}
private List<RequestValues> ContentValues()
{
var values = new List<RequestValues>();
var output = Message.Trim('n').Split().Last();
var parse = Regex.Matches(output, @"([^&].*?)=([^&]*b)");
foreach (Match match in parse)
{
values.Add(new RequestValues()
{
Name = match.Groups[1].Value.Trim(),
Value = match.Groups[2].Value.Trim().Replace('+', ' ')
});
}
return values;
}
}
}

ماهیت آن این است که درخواست را با استفاده از عبارات منظم به قطعات تقسیم کنید. ما یک پیام از مشتری دریافت می کنیم، خط اول را انتخاب می کنیم که حاوی روش و آدرس درخواست است. سپس سرفصل‌ها را می‌خوانیم که در آرایه‌ای به شکل HeaderName=Content قرار می‌دهیم، و همچنین در صورت وجود، محتوای همراه (مثلاً querystring) را پیدا می‌کنیم که آن‌ها را نیز در یک آرایه مشابه قرار می‌دهیم. علاوه بر این، تجزیه کننده متوجه می شود که آیا مشتری فعلی مجاز است یا خیر و داده های او را ذخیره می کند. تمام درخواست‌های مشتریان مجاز شامل یک هش مجوز است که در کوکی‌ها ذخیره می‌شود، به لطف آن می‌توان منطق عملیاتی بیشتری را برای دو نوع مشتری جدا کرد و به آنها پاسخ‌های صحیح داد.

خوب، یک ویژگی کوچک و خوب که ارزش گذاشتن در یک ماژول جداگانه را دارد، تبدیل پرس و جوهایی مانند "site.com/@UserName" به صفحات کاربر ایجاد شده به صورت پویا. پس از پردازش درخواست، ماژول های زیر وارد عمل می شوند.

فصل 3. نصب فرمان، روغن کاری زنجیر

به محض اینکه تجزیه کننده کار خود را کامل کرد، هندلر وارد بازی می شود و دستورات بیشتری به سرور می دهد و کنترل را به دو قسمت تقسیم می کند.

کنترل کننده ساده

using ClearServer.Core.UserController;
using System.Net.Security;
namespace ClearServer.Core.Requester
{
    public class RequestHandler
    {
        public static void OnHandle(SslStream ClientStream, RequestContext context)
        {

            if (context.CurrentUser != null)
            {
                new AuthUserController(ClientStream, context);
            }
            else 
            {
                new NonAuthUserController(ClientStream, context);
            };
        }
    }
}

در واقع فقط یک بررسی برای مجوز کاربر وجود دارد که پس از آن پردازش درخواست آغاز می شود.

کنترل کننده های مشتری
اگر کاربر مجاز نباشد، عملکرد برای او فقط بر اساس نمایش پروفایل های کاربر و پنجره ثبت مجوز است. کد یک کاربر مجاز تقریباً یکسان است، بنابراین دلیلی برای تکرار آن نمی بینم.

کاربر غیر مجاز

using ClearServer.Core.Requester;
using System.IO;
using System.Net.Security;

namespace ClearServer.Core.UserController
{
    internal class NonAuthUserController
    {
        private readonly SslStream ClientStream;
        private readonly RequestContext Context;
        private readonly WriteController WriteController;
        private readonly AuthorizationController AuthorizationController;

        private readonly string ViewPath = "C:/Users/drdre/source/repos/ClearServer/View";

        public NonAuthUserController(SslStream clientStream, RequestContext context)
        {
            this.ClientStream = clientStream;
            this.Context = context;
            this.WriteController = new WriteController(clientStream);
            this.AuthorizationController = new AuthorizationController(clientStream, context);
            ResourceLoad();
        }

        void ResourceLoad()
        {
            string[] blockextension = new string[] {"cshtml", "html", "htm"};
            bool block = false;
            foreach (var item in blockextension)
            {
                if (Context.RequestUrl.Contains(item))
                {
                    block = true;
                    break;
                }
            }
            string FilePath = "";
            string Header = "";
            var RazorController = new RazorController(Context, ClientStream);
            
            switch (Context.RequestMethod)
            {
                case "GET":
                    switch (Context.RequestUrl)
                    {
                        case "/":
                            FilePath = ViewPath + "/loginForm.html";
                            Header =

quot;HTTP/1.1 200 OKnContent-Type: text/html";
WriteController.DefaultWriter(Header, FilePath);
break;
case "/profile":
RazorController.ProfileLoader(ViewPath);
break;
default:
//в данном блоке кода происходит отсечение запросов к серверу по прямому адресу страницы вида site.com/page.html
if (!File.Exists(ViewPath + Context.RequestUrl) | block)
{
RazorController.ErrorLoader(404);

}
else if (Path.HasExtension(Context.RequestUrl) && File.Exists(ViewPath + Context.RequestUrl))
{
Header = WriteController.ContentType(Context.RequestUrl);
FilePath = ViewPath + Context.RequestUrl;
WriteController.DefaultWriter(Header, FilePath);
}
break;
}
break;

case "POST":
AuthorizationController.MethodRecognizer();
break;

}

}

}
}

و البته کاربر باید نوعی محتوای صفحه را دریافت کند، بنابراین برای پاسخ ها ماژول زیر وجود دارد که وظیفه پاسخگویی به درخواست های منابع را بر عهده دارد.

WriterController

using System;
using System.IO;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    public class WriteController
    {
        SslStream ClientStream;
        public WriteController(SslStream ClientStream)
        {
            this.ClientStream = ClientStream;
        }

        public void DefaultWriter(string Header, string FilePath)
        {
            FileStream fileStream;
            try
            {
                fileStream = new FileStream(FilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite);
                Header =

quot;{Header}nContent-Length: {fileStream.Length}nn";
ClientStream.Write(Encoding.UTF8.GetBytes(Header));
byte[] response = new byte[fileStream.Length];
fileStream.BeginRead(response, 0, response.Length, OnFileRead, response);
}
catch { }
}

public string ContentType(string Uri)
{
string extension = Path.GetExtension(Uri);
string Header = "HTTP/1.1 200 OKnContent-Type:";
switch (extension)
{
case ".html":
case ".htm":
return


quot;{Header} text/html";
case ".css":
return


quot;{Header} text/css";
case ".js":
return


quot;{Header} text/javascript";
case ".jpg":
case ".jpeg":
case ".png":
case ".gif":
return


quot;{Header} image/{extension}";
default:
if (extension.Length > 1)
{
return


quot;{Header} application/" + extension.Substring(1);
}
else
{
return


quot;{Header} application/unknown";
}
}
}

public void OnFileRead(IAsyncResult ar)
{
if (ar.IsCompleted)
{
var file = (byte[])ar.AsyncState;
ClientStream.BeginWrite(file, 0, file.Length, OnClientSend, null);
}
}

public void OnClientSend(IAsyncResult ar)
{
if (ar.IsCompleted)
{
ClientStream.Close();
}
}
}

اما برای اینکه پروفایل خود و سایر کاربران را به کاربر نشان دهم تصمیم گرفتم از RazorEngine یا بهتر است بگوییم بخشی از آن استفاده کنم. همچنین شامل پردازش درخواست‌های نامعتبر و صدور کد خطای مناسب می‌شود.

RazorController

using ClearServer.Core.Requester;
using RazorEngine;
using RazorEngine.Templating;
using System;
using System.IO;
using System.Net;
using System.Net.Security;

namespace ClearServer.Core.UserController
{
    internal class RazorController
    {
        private RequestContext Context;
        private SslStream ClientStream;
        dynamic PageContent;


        public RazorController(RequestContext context, SslStream clientStream)
        {
            this.Context = context;
            this.ClientStream = clientStream;

        }

        public void ProfileLoader(string ViewPath)
        {
            string Filepath = ViewPath + "/profile.cshtml";
            if (Context.RequestProfile != null)
            {
                if (Context.CurrentUser != null && Context.RequestProfile.login == Context.CurrentUser.login)
                {
                    try
                    {
                        PageContent = new { isAuth = true, Name = Context.CurrentUser.name, Login = Context.CurrentUser.login, Skills = Context.CurrentUser.skills };
                        ClientSend(Filepath, Context.CurrentUser.login);
                    }
                    catch (Exception e) { Console.WriteLine(e); }

                }
                else
                {
                    try
                    {
                        PageContent = new { isAuth = false, Name = Context.RequestProfile.name, Login = Context.RequestProfile.login, Skills = Context.RequestProfile.skills };
                        ClientSend(Filepath, "PublicProfile:"+ Context.RequestProfile.login);
                    }
                    catch (Exception e) { Console.WriteLine(e); }
                }
            }
            else
            {
                ErrorLoader(404);
            }


        }

        public void ErrorLoader(int Code)
        {
            try
            {
                PageContent = new { ErrorCode = Code, Message = ((HttpStatusCode)Code).ToString() };
                string ErrorPage = "C:/Users/drdre/source/repos/ClearServer/View/Errors/ErrorPage.cshtml";
                ClientSend(ErrorPage, Code.ToString());
            }
            catch { }

        }

        private void ClientSend(string FilePath, string Key)
        {
            var template = File.ReadAllText(FilePath);
            var result = Engine.Razor.RunCompile(template, Key, null, (object)PageContent);
            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(result);
            ClientStream.BeginWrite(buffer, 0, buffer.Length, OnClientSend, ClientStream);
        }

        private void OnClientSend(IAsyncResult ar)
        {
            if (ar.IsCompleted)
            {
                ClientStream.Close();
            }
        }
    }
}

و البته برای اینکه تایید کاربران مجاز کار کند، مجوز لازم است. ماژول مجوز با پایگاه داده تعامل دارد. داده های دریافتی از فرم های موجود در سایت از متن تجزیه می شود، کاربر ذخیره می شود و در ازای آن کوکی ها و دسترسی به سرویس را دریافت می کند.

ماژول مجوز

using ClearServer.Core.Cookies;
using ClearServer.Core.Requester;
using ClearServer.Core.Security;
using System;
using System.Linq;
using System.Net.Security;
using System.Text;

namespace ClearServer.Core.UserController
{
    internal class AuthorizationController
    {
        private SslStream ClientStream;
        private RequestContext Context;
        private UserCookies cookies;
        private WriteController WriteController;
        DatabaseWorker DatabaseWorker;
        RazorController RazorController;
        PasswordHasher PasswordHasher;
        public AuthorizationController(SslStream clientStream, RequestContext context)
        {
            ClientStream = clientStream;
            Context = context;
            DatabaseWorker = new DatabaseWorker();
            WriteController = new WriteController(ClientStream);
            RazorController = new RazorController(context, clientStream);
            PasswordHasher = new PasswordHasher();
        }

        internal void MethodRecognizer()
        {
            if (Context.FormValues.Count == 2 && Context.FormValues.Any(x => x.Name == "password")) Authorize();
            else if (Context.FormValues.Count == 3 && Context.FormValues.Any(x => x.Name == "regPass")) Registration();
            else
            {
                RazorController.ErrorLoader(401);
            }
        }

        private void Authorize()
        {
            var values = Context.FormValues;
            var user = new User()
            {
                login = values[0].Value,
                password = PasswordHasher.PasswordHash(values[1].Value)
            };
            user = DatabaseWorker.UserAuth(user);
            if (user != null)
            {
                cookies = new UserCookies(user.login, user.password);
                user.cookie = cookies.AuthCookie;
                DatabaseWorker.UserUpdate(user);
                var response = Encoding.UTF8.GetBytes(

quot;HTTP/1.1 301 Moved PermanentlynLocation: /@{user.login}nSet-Cookie: {cookies.AuthCookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);

}
else
{
RazorController.ErrorLoader(401);

}
}

private void Registration()
{
var values = Context.FormValues;
var user = new User()
{
name = values[0].Value,
login = values[1].Value,
password = PasswordHasher.PasswordHash(values[2].Value),
};
cookies = new UserCookies(user.login, user.password);
user.cookie = cookies.AuthCookie;
if (DatabaseWorker.LoginValidate(user.login))
{
Console.WriteLine("User ready");
Console.WriteLine(


quot;{user.password} {user.password.Trim().Length}");
DatabaseWorker.UserRegister(user);
var response = Encoding.UTF8.GetBytes(


quot;HTTP/1.1 301 Moved PermanentlynLocation: /@{user.login}nSet-Cookie: {user.cookie}; Expires={DateTime.Now.AddDays(2):R}; Secure; HttpOnlynn");
ClientStream.BeginWrite(response, 0, response.Length, WriteController.OnClientSend, null);
}
else
{
RazorController.ErrorLoader(401);
}
}
}
}

و این چیزی است که پردازش پایگاه داده به نظر می رسد:

پایگاه داده

using ClearServer.Core.UserController;
using System;
using System.Data.Linq;
using System.Linq;

namespace ClearServer
{
    class DatabaseWorker
    {

        private readonly Table<User> users = null;
        private readonly DataContext DataBase = null;
        private const string connectionStr = @"путькбазе";

        public DatabaseWorker()
        {
            DataBase = new DataContext(connectionStr);
            users = DataBase.GetTable<User>();
        }

        public User UserAuth(User User)
        {
            try
            {
                var user = users.SingleOrDefault(t => t.login.ToLower() == User.login.ToLower() && t.password == User.password);
                if (user != null)
                    return user;
                else
                    return null;
            }
            catch (Exception)
            {
                return null;
            }

        }

        public void UserRegister(User user)
        {
            try
            {
                users.InsertOnSubmit(user);
                DataBase.SubmitChanges();
                Console.WriteLine(

quot;User{user.name} with id {user.uid} added");
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
catch (Exception e)
{
Console.WriteLine(e);
}

}

public bool LoginValidate(string login)
{
if (users.Any(x => x.login.ToLower() == login.ToLower()))
{
Console.WriteLine("Login already exists");
return false;
}
return true;
}
public void UserUpdate(User user)
{
var UserToUpdate = users.FirstOrDefault(x => x.uid == user.uid);
UserToUpdate = user;
DataBase.SubmitChanges();
Console.WriteLine(


quot;User {UserToUpdate.name} with id {UserToUpdate.uid} updated");
foreach (var item in users)
{
Console.WriteLine(item.login + "n");
}
}
public User CookieValidate(string CookieInput)
{
User user = null;
try
{
user = users.SingleOrDefault(x => x.cookie == CookieInput);
}
catch
{
return null;
}
if (user != null) return user;
else return null;
}
public User FindUser(string login)
{
User user = null;
try
{
user = users.Single(x => x.login.ToLower() == login.ToLower());
if (user != null)
{
return user;
}
else
{
return null;
}
}
catch (Exception)
{
return null;
}
}
}
}


و همه چیز مانند ساعت کار، مجوز و کار ثبت نام کار می کند، حداقل عملکرد برای دسترسی به سرویس از قبل وجود دارد و زمان نوشتن یک برنامه و گره زدن کل کار با عملکردهای اصلی که همه چیز برای آنها انجام می شود فرا رسیده است.

فصل 4. دور انداختن دوچرخه

برای کاهش هزینه های کار نوشتن دو برنامه برای دو پلتفرم، تصمیم گرفتم یک پلتفرم متقابل در Xamarin.Forms ایجاد کنم. باز هم به لطف این واقعیت که در سی شارپ است. با ساخت یک برنامه آزمایشی که به سادگی داده ها را به سرور ارسال می کند، به نکته جالبی برخوردم. برای درخواست از یک دستگاه، برای سرگرمی، آن را روی HttpClient پیاده‌سازی کردم و به سرور HttpRequestMessage ارسال کردم که حاوی داده‌های فرم مجوز با فرمت json است. بدون اینکه انتظار خاصی داشته باشم، گزارش سرور را باز کردم و در آنجا درخواستی از دستگاه با تمام داده ها مشاهده کردم. گیجی خفیف، آگاهی از همه چیزهایی که در طول 3 هفته گذشته در یک عصر بی‌حال انجام شده است. برای بررسی صحت داده های ارسالی، یک سرور آزمایشی در HttpListner مونتاژ کردم. با دریافت درخواست دیگری از قبل، آن را در چند خط کد جدا کردم و یک KeyValuePair داده از فرم دریافت کردم. تجزیه پرس و جو به دو خط کاهش یافت.

من شروع به آزمایش بیشتر کردم، قبلاً ذکر نشده بود، اما در سرور قبلی یک چت ساخته شده بر روی وب سوکت ها را نیز اجرا کردم. خیلی خوب کار می کرد، اما اصل تعامل از طریق Tcp ناامیدکننده بود؛ کارهای غیرضروری زیادی باید انجام می شد تا تعامل دو کاربر با یک گزارش مکاتبات به درستی ایجاد شود. این شامل تجزیه درخواست برای تغییر اتصال و جمع آوری پاسخ با استفاده از پروتکل RFC 6455 است. بنابراین، در سرور آزمایشی، تصمیم گرفتم یک اتصال وب سوکت ساده ایجاد کنم. فقط برای سرگرمی

به چت متصل شوید

 private static async void HandleWebsocket(HttpListenerContext context)
        {
            var socketContext = await context.AcceptWebSocketAsync(null);
            var socket = socketContext.WebSocket;
            Locker.EnterWriteLock();
            try
            {
                Clients.Add(socket);
            }
            finally
            {
                Locker.ExitWriteLock();
            }

            while (true)
            {
                var buffer = new ArraySegment<byte>(new byte[1024]);
                var result = await socket.ReceiveAsync(buffer, CancellationToken.None);
                var str = Encoding.Default.GetString(buffer);
                Console.WriteLine(str);

                for (int i = 0; i < Clients.Count; i++)
                {
                    WebSocket client = Clients[i];

                    try
                    {
                        if (client.State == WebSocketState.Open)
                        {
                            
                            await client.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                        }
                    }
                    catch (ObjectDisposedException)
                    {
                        Locker.EnterWriteLock();
                        try
                        {
                            Clients.Remove(client);
                            i--;
                        }
                        finally
                        {
                            Locker.ExitWriteLock();
                        }
                    }
                }
            }
        }

و کار کرد. خود سرور اتصال را پیکربندی کرده و یک کلید پاسخ تولید می کند. حتی مجبور نبودم ثبت نام سرور را به طور جداگانه از طریق SSL پیکربندی کنم؛ فقط کافی بود که سیستم قبلاً یک گواهی روی پورت مورد نیاز نصب کرده بود.

در سمت دستگاه و در سمت سایت، دو مشتری پیام رد و بدل کردند، همه اینها ثبت شد. هیچ تجزیه کننده بزرگی باعث کاهش سرعت سرور نمی شود، هیچ کدام از اینها مورد نیاز نبود. زمان پاسخگویی از 200 میلی ثانیه به 40-30 میلی ثانیه کاهش یافته است. و من به تنها تصمیم درست رسیدم.

دوچرخه‌سواری پیشرفته یا برنامه مشتری-سرور مبتنی بر چارچوب C#.Net

اجرای سرور فعلی در Tcp را دور بریزید و همه چیز را تحت Http بازنویسی کنید. اکنون پروژه در مرحله طراحی مجدد است، اما بر اساس اصول کاملا متفاوت تعامل. عملکرد دستگاه ها و سایت به صورت همزمان و دیباگ شده است و مفهومی مشترک دارد، تنها با این تفاوت که نیازی به تولید صفحات HTML برای دستگاه ها نیست.

نتیجه

"اگر فورد را نمی شناسید، داخل آب نرو" فکر می‌کنم قبل از شروع کار، باید اهداف و مقاصد واضح‌تری داشته باشم و همچنین به مطالعه فناوری‌ها و روش‌های لازم برای اجرای آنها بر روی مشتریان مختلف بپردازم. این پروژه در حال تکمیل شدن است، اما شاید بازگردم تا در مورد چگونگی ذخیره برخی چیزها دوباره صحبت کنم. من در طول فرآیند توسعه چیزهای زیادی یاد گرفتم، اما در آینده چیزهای بیشتری برای یادگیری وجود دارد. اگر تا اینجا خوانده اید، ممنون می شوم که این کار را انجام دهید.

منبع: www.habr.com