تطبيق التدوير المتقدم أو خادم العميل استنادًا إلى إطار عمل C # .Net

دخول

بدأ كل شيء عندما اقترح أحد الزملاء أن أقوم بإنشاء خدمة ويب صغيرة. كان من المفترض أن يكون شيئًا مثل Tinder ، ولكن من أجل جلسة Hangout لتكنولوجيا المعلومات. الوظيفة بسيطة للغاية ، يمكنك التسجيل وملء ملف التعريف والانتقال إلى النقطة الرئيسية ، وهي العثور على محاور وتوسيع اتصالاتك وتكوين معارف جديدة.

هنا يجب أن أستطرد وأخبر قليلاً عن نفسي ، حتى يكون من الواضح في المستقبل لماذا اتخذت مثل هذه الخطوات في التنمية.

في الوقت الحالي ، أشغل منصب فنان تقني في استوديو ألعاب ، وكانت تجربتي في البرمجة C # تعتمد فقط على كتابة البرامج النصية والأدوات المساعدة لـ Unity ، بالإضافة إلى إنشاء مكونات إضافية للعمل منخفض المستوى مع أجهزة Android. خارج هذا العالم ، لم أختار بعد هذه الفرصة ثم أتيحت لي.

الجزء 1. إطار النماذج

بعد أن قررت كيف ستكون هذه الخدمة ، بدأت في البحث عن خيارات للتنفيذ. أسهل طريقة هي العثور على نوع من الحلول الجاهزة ، والتي ، مثل البومة على الكرة الأرضية ، يمكنك سحب ميكانيكا لدينا ووضع كل شيء للرقابة العامة.
لكن هذا ليس مثيرًا للاهتمام ، لم أر أي تحدٍ أو معنى في هذا ، وبالتالي بدأت بدراسة تقنيات الويب وطرق التفاعل معها.

بدأت الدراسة بمشاهدة المقالات والوثائق على C # .Net. وجدت هنا طرقًا مختلفة لإنجاز المهمة. هناك العديد من الآليات للتفاعل مع الشبكة ، بدءًا من الحلول الكاملة مثل خدمات ASP.Net أو Azure ، إلى التفاعل المباشر مع اتصالات TcpHttp.

بعد إجراء المحاولة الأولى مع ASP ، قمت بإلغائها على الفور ، في رأيي أنه كان قرارًا صعبًا للغاية بالنسبة لخدمتنا. لن نستخدم حتى ثلث إمكانيات هذه المنصة ، لذلك تابعت بحثي. نشأ الاختيار بين خادم العميل TCP و Http. هنا ، في حبري ، صادفت مقالًا عن خادم متعدد الخيوطبعد أن جمعت واختبرت ذلك ، قررت التركيز على التفاعل مع اتصالات TCP ، لسبب ما اعتقدت أن http لن يسمح لي بإنشاء حل عبر الأنظمة الأساسية.

تضمن الإصدار الأول من الخادم التعامل مع الاتصالات ، وخدمة محتوى صفحة الويب الثابتة ، بما في ذلك قاعدة بيانات المستخدم. بالنسبة للمبتدئين ، قررت إنشاء وظيفة للعمل مع الموقع ، حتى أتمكن لاحقًا من ربط معالجة التطبيق على android و 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;
}
}
}

يكمن جوهرها في حقيقة أنه بمساعدة التعبيرات العادية لتقسيم الطلب إلى أجزاء. نتلقى رسالة من العميل ، حدد السطر الأول الذي يحتوي على الطريقة وعنوان URL للطلب. ثم نقرأ الرؤوس ، التي نقودها إلى مصفوفة من النموذج HeaderName = Content ، ونجد أيضًا ، إن وجد ، المحتوى المصاحب (على سبيل المثال ، سلسلة الاستعلام) الذي ندخله أيضًا في مصفوفة مماثلة. بالإضافة إلى ذلك ، يكتشف المحلل اللغوي ما إذا كان العميل الحالي مفوضًا ويحفظ بياناته. تحتوي جميع الطلبات الواردة من العملاء المعتمدين على تجزئة تفويض ، يتم تخزينها في ملفات تعريف الارتباط ، والتي بفضلها يمكن فصل منطق عمل إضافي لنوعين من العملاء ومنحهم الإجابات الصحيحة.

حسنًا ، ميزة صغيرة وجميلة يجب نقلها إلى وحدة منفصلة ، وتحويل الطلبات مثل "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. مرة أخرى ، بفضل حقيقة أنه موجود في C #. بعد أن أنشأت تطبيقًا اختباريًا يرسل البيانات ببساطة إلى الخادم ، مررت بلحظة واحدة مثيرة للاهتمام. لطلب من الجهاز ، للتسلية ، قمت بتطبيقه على HttpClient وألقيته على الخادم HttpRequestMessage الذي يحتوي على بيانات من نموذج التفويض بتنسيق json. خاصةً دون توقع أي شيء ، فتحت سجل الخادم ورأيت طلبًا من الجهاز بكل البيانات الموجودة هناك. ذهول خفيف ، إدراك لكل ما تم القيام به خلال الأسابيع الثلاثة الماضية من المساء الضعيف. للتحقق من صحة البيانات المرسلة ، قمت بتجميع خادم اختبار على 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